mirror of
https://github.com/johnkerl/miller.git
synced 2026-01-23 02:14:13 +00:00
* Update package version * Update makefile targets * Update readme packages * Remaining old packages via rg/sd
213 lines
6.4 KiB
Go
213 lines
6.4 KiB
Go
// ================================================================
|
|
// Shows warnings for things like uninitialized variables. These are things
|
|
// that are statically computable from the AST by itself -- it confines itself
|
|
// to local-variable analysis. There are other uninitialization issues
|
|
// detectable only at runtime, which would benefit from a 'strict mode'.
|
|
// ================================================================
|
|
|
|
package cst
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/johnkerl/miller/v6/pkg/dsl"
|
|
"github.com/johnkerl/miller/v6/pkg/lib"
|
|
)
|
|
|
|
// ----------------------------------------------------------------
|
|
// Returns true if there are no warnings.
|
|
func WarnOnAST(
|
|
ast *dsl.AST,
|
|
) bool {
|
|
variableNamesWrittenTo := make(map[string]bool)
|
|
inAssignment := false
|
|
ok := true
|
|
|
|
for _, astChild := range ast.RootNode.Children {
|
|
ok1 := warnOnASTAux(
|
|
astChild,
|
|
variableNamesWrittenTo,
|
|
inAssignment,
|
|
)
|
|
// Don't end early on first warning; tree-walk to list them all.
|
|
ok = ok1 && ok
|
|
}
|
|
|
|
return ok
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
// Example ASTs:
|
|
//
|
|
// $ mlr -n put -v 'z = x + y'
|
|
// DSL EXPRESSION:
|
|
// z = x + y
|
|
//
|
|
// AST:
|
|
// * statement block
|
|
// * assignment "="
|
|
// * local variable "z"
|
|
// * operator "+"
|
|
// * local variable "x"
|
|
// * local variable "y"
|
|
//
|
|
// $ mlr -n put -v 'z[i] = x + y'
|
|
// DSL EXPRESSION:
|
|
// z[i] = x + y
|
|
//
|
|
// AST:
|
|
// * statement block
|
|
// * assignment "="
|
|
// * array or map index access "[]"
|
|
// * local variable "z"
|
|
// * local variable "i"
|
|
// * operator "+"
|
|
// * local variable "x"
|
|
// * local variable "y"
|
|
//
|
|
// $ mlr -n put -v 'func f(n) { return n+1}'
|
|
// DSL EXPRESSION:
|
|
// func f(n) { return n+1}
|
|
//
|
|
// AST:
|
|
// * statement block
|
|
// * function definition "f"
|
|
// * parameter list
|
|
// * parameter
|
|
// * parameter name "n"
|
|
// * statement block
|
|
// * return "return"
|
|
// * operator "+"
|
|
// * local variable "n"
|
|
// * int literal "1"
|
|
|
|
// Returns true if there are no warnings
|
|
func warnOnASTAux(
|
|
astNode *dsl.ASTNode,
|
|
variableNamesWrittenTo map[string]bool,
|
|
inAssignment bool,
|
|
) bool {
|
|
|
|
ok := true
|
|
|
|
// Check local-variable references, and see if they're reads or writes
|
|
// based on the AST parenting of this node.
|
|
if astNode.Type == dsl.NodeTypeLocalVariable {
|
|
variableName := string(astNode.Token.Lit)
|
|
if inAssignment {
|
|
variableNamesWrittenTo[variableName] = true
|
|
} else {
|
|
if !variableNamesWrittenTo[variableName] {
|
|
fmt.Fprintf(
|
|
os.Stderr,
|
|
"Variable name %s might not have been assigned yet%s.\n",
|
|
variableName,
|
|
dsl.TokenToLocationInfo(astNode.Token),
|
|
)
|
|
ok = false
|
|
}
|
|
}
|
|
|
|
} else if astNode.Type == dsl.NodeTypeBeginBlock {
|
|
// Locals are confined to begin/end blocks and func/subr blocks.
|
|
// Reset for this part of the treewalk.
|
|
variableNamesWrittenTo = make(map[string]bool)
|
|
} else if astNode.Type == dsl.NodeTypeEndBlock {
|
|
// Locals are confined to begin/end blocks and func/subr blocks.
|
|
// Reset for this part of the treewalk.
|
|
variableNamesWrittenTo = make(map[string]bool)
|
|
|
|
} else if astNode.Type == dsl.NodeTypeNamedFunctionDefinition {
|
|
// Locals are confined to begin/end blocks and func/subr blocks. Reset
|
|
// for this part of the treewalk, except mark the parameters as
|
|
// defined.
|
|
variableNamesWrittenTo = noteParametersForWarnings(astNode)
|
|
} else if astNode.Type == dsl.NodeTypeSubroutineDefinition {
|
|
// Locals are confined to begin/end blocks and func/subr blocks. Reset
|
|
// for this part of the treewalk, except mark the parameters as
|
|
// defined.
|
|
variableNamesWrittenTo = noteParametersForWarnings(astNode)
|
|
}
|
|
|
|
// Treewalk to check the rest of the AST below this node.
|
|
|
|
for i, astChild := range astNode.Children {
|
|
childInAssignment := inAssignment
|
|
|
|
if astNode.Type == dsl.NodeTypeAssignment && i == 0 {
|
|
// LHS of assignment statements
|
|
childInAssignment = true
|
|
} else if astNode.Type == dsl.NodeTypeForLoopOneVariable && i == 0 {
|
|
// The 'k' in 'for (k in $*)'
|
|
childInAssignment = true
|
|
} else if astNode.Type == dsl.NodeTypeForLoopTwoVariable && (i == 0 || i == 1) {
|
|
// The 'k' and 'v' in 'for (k,v in $*)'
|
|
childInAssignment = true
|
|
} else if astNode.Type == dsl.NodeTypeForLoopMultivariable && (i == 0 || i == 1) {
|
|
// The 'k1', 'k2', and 'v' in 'for ((k1,k2),v in $*)'
|
|
childInAssignment = true
|
|
} else if astNode.Type == dsl.NodeTypeParameterList {
|
|
childInAssignment = true
|
|
} else if inAssignment && astNode.Type == dsl.NodeTypeArrayOrMapIndexAccess {
|
|
// In 'z[i] = 1', the 'i' is a read and the 'z' is a write.
|
|
//
|
|
// mlr --from r put -v -W 'z[i] = 1'
|
|
// DSL EXPRESSION:
|
|
// z[i]=1
|
|
//
|
|
// AST:
|
|
// * statement block
|
|
// * assignment "="
|
|
// * array or map index access "[]"
|
|
// * local variable "z"
|
|
// * local variable "i"
|
|
// * int literal "1"
|
|
if i == 0 {
|
|
childInAssignment = true
|
|
} else {
|
|
childInAssignment = false
|
|
}
|
|
}
|
|
ok1 := warnOnASTAux(
|
|
astChild,
|
|
variableNamesWrittenTo,
|
|
childInAssignment,
|
|
)
|
|
// Don't end early on first error; tree-walk to list them all.
|
|
ok = ok1 && ok
|
|
}
|
|
|
|
return ok
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
// Given a func/subr block, find the names of its parameters. All the
|
|
// lib.InternalCodingErrorIf parts are shape-assertions to make sure this code
|
|
// is in sync with the BNF grammar which builds the AST from a Miller-DSL
|
|
// source string.
|
|
func noteParametersForWarnings(
|
|
astNode *dsl.ASTNode,
|
|
) map[string]bool {
|
|
|
|
variableNamesWrittenTo := make(map[string]bool)
|
|
|
|
lib.InternalCodingErrorIf(
|
|
astNode.Type != dsl.NodeTypeNamedFunctionDefinition &&
|
|
astNode.Type != dsl.NodeTypeSubroutineDefinition)
|
|
lib.InternalCodingErrorIf(len(astNode.Children) < 1)
|
|
parameterListNode := astNode.Children[0]
|
|
|
|
lib.InternalCodingErrorIf(parameterListNode.Type != dsl.NodeTypeParameterList)
|
|
|
|
for _, parameterNode := range parameterListNode.Children {
|
|
lib.InternalCodingErrorIf(parameterNode.Type != dsl.NodeTypeParameter)
|
|
lib.InternalCodingErrorIf(len(parameterNode.Children) != 1)
|
|
parameterNameNode := parameterNode.Children[0]
|
|
lib.InternalCodingErrorIf(parameterNameNode.Type != dsl.NodeTypeParameterName)
|
|
parameterName := string(parameterNameNode.Token.Lit)
|
|
variableNamesWrittenTo[parameterName] = true
|
|
}
|
|
|
|
return variableNamesWrittenTo
|
|
}
|