mirror of
https://github.com/johnkerl/miller.git
synced 2026-07-18 00:45:47 +00:00
321 lines
8.8 KiB
Go
321 lines
8.8 KiB
Go
// Checks for things that are syntax errors but not done in the AST for
|
|
// pragmatic reasons. For example, $anything in begin/end blocks;
|
|
// begin/end/func not at top level; etc.
|
|
|
|
package cst
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/johnkerl/miller/v6/pkg/lib"
|
|
|
|
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
|
|
)
|
|
|
|
func ValidateAST(
|
|
ast *asts.AST,
|
|
dslInstanceType DSLInstanceType, // mlr put, mlr filter, mlr repl
|
|
) error {
|
|
atTopLevel := true
|
|
inLoop := false
|
|
inBeginOrEnd := false
|
|
inUDF := false
|
|
inUDS := false
|
|
isMainBlockLastStatement := false
|
|
isAssignmentLHS := false
|
|
isUnset := false
|
|
|
|
// They can do mlr put '': there are simply zero statements.
|
|
// But filter '' is an error.
|
|
if len(ast.RootNode.Children) == 0 {
|
|
if dslInstanceType == DSLInstanceTypeFilter {
|
|
return fmt.Errorf("filter statement must not be empty")
|
|
}
|
|
}
|
|
|
|
for _, astChild := range ast.RootNode.Children {
|
|
err := validateASTAux(
|
|
astChild,
|
|
dslInstanceType,
|
|
atTopLevel,
|
|
inLoop,
|
|
inBeginOrEnd,
|
|
inUDF,
|
|
inUDS,
|
|
isMainBlockLastStatement,
|
|
isAssignmentLHS,
|
|
isUnset,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func validateASTAux(
|
|
astNode *asts.ASTNode,
|
|
dslInstanceType DSLInstanceType, // mlr put, mlr filter, mlr repl
|
|
atTopLevel bool,
|
|
inLoop bool,
|
|
inBeginOrEnd bool,
|
|
inUDF bool,
|
|
inUDS bool,
|
|
isMainBlockLastStatement bool, // TODO -- keep this or not ...
|
|
isAssignmentLHS bool,
|
|
isUnset bool,
|
|
) error {
|
|
nextLevelAtTopLevel := false
|
|
nextLevelInLoop := inLoop
|
|
nextLevelInBeginOrEnd := inBeginOrEnd
|
|
nextLevelInUDF := inUDF
|
|
nextLevelInUDS := inUDS
|
|
var nextLevelIsAssignmentLHS bool
|
|
var nextLevelIsUnset bool
|
|
|
|
if astNode.Type == asts.NodeType(NodeTypeFilterStatement) {
|
|
if dslInstanceType == DSLInstanceTypeFilter {
|
|
return fmt.Errorf(
|
|
`filter expressions must not also contain the "filter" keyword`,
|
|
)
|
|
}
|
|
}
|
|
|
|
// Check: begin/end/func/subr must be at top-level
|
|
if astNode.Type == asts.NodeType(NodeTypeBeginBlock) {
|
|
if !atTopLevel {
|
|
return fmt.Errorf(
|
|
"begin blocks can only be at top level",
|
|
)
|
|
}
|
|
nextLevelInBeginOrEnd = true
|
|
} else if astNode.Type == asts.NodeType(NodeTypeEndBlock) {
|
|
if !atTopLevel {
|
|
return fmt.Errorf(
|
|
"end blocks can only be at top level",
|
|
)
|
|
}
|
|
nextLevelInBeginOrEnd = true
|
|
} else if astNode.Type == asts.NodeType(NodeTypeNamedFunctionDefinition) {
|
|
if !atTopLevel {
|
|
return fmt.Errorf(
|
|
"func blocks can only be at top level",
|
|
)
|
|
}
|
|
nextLevelInUDF = true
|
|
} else if astNode.Type == asts.NodeType(NodeTypeUnnamedFunctionDefinition) {
|
|
nextLevelInUDF = true
|
|
} else if astNode.Type == asts.NodeType(NodeTypeSubroutineDefinition) {
|
|
if !atTopLevel {
|
|
return fmt.Errorf(
|
|
"subr blocks can only be at top level",
|
|
)
|
|
}
|
|
nextLevelInUDS = true
|
|
} else if astNode.Type == asts.NodeType(NodeTypeForLoopTwoVariable) {
|
|
err := validateForLoopTwoVariableUniqueNames(astNode)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else if astNode.Type == asts.NodeType(NodeTypeForLoopMultivariable) {
|
|
err := validateForLoopMultivariableUniqueNames(astNode)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Check: $-anything cannot be in begin/end
|
|
if inBeginOrEnd {
|
|
if astNode.Type == asts.NodeType(NodeTypeDirectFieldValue) ||
|
|
astNode.Type == asts.NodeType(NodeTypeIndirectFieldValue) ||
|
|
astNode.Type == asts.NodeType(NodeTypeFullSrec) {
|
|
return fmt.Errorf(
|
|
"begin/end blocks cannot refer to records via $x, $*, etc",
|
|
)
|
|
}
|
|
}
|
|
|
|
// Check: break/continue outside of loop
|
|
if !inLoop {
|
|
if astNode.Type == asts.NodeType(NodeTypeBreakStatement) {
|
|
return fmt.Errorf(
|
|
"break statements are only valid within for/do/while loops",
|
|
)
|
|
}
|
|
}
|
|
|
|
if !inLoop {
|
|
if astNode.Type == asts.NodeType(NodeTypeContinueStatement) {
|
|
return fmt.Errorf(
|
|
"break statements are only valid within for/do/while loops",
|
|
)
|
|
}
|
|
}
|
|
|
|
if astNode.Type == asts.NodeType(NodeTypeWhileLoop) ||
|
|
astNode.Type == asts.NodeType(NodeTypeDoWhileLoop) ||
|
|
astNode.Type == asts.NodeType(NodeTypeForLoopOneVariable) ||
|
|
astNode.Type == asts.NodeType(NodeTypeForLoopTwoVariable) ||
|
|
astNode.Type == asts.NodeType(NodeTypeForLoopMultivariable) ||
|
|
astNode.Type == asts.NodeType(NodeTypeTripleForLoop) {
|
|
nextLevelInLoop = true
|
|
}
|
|
|
|
// Check: return outside of func/subr
|
|
if !inUDF && !inUDS {
|
|
if astNode.Type == asts.NodeType(NodeTypeReturnStatement) {
|
|
return fmt.Errorf(
|
|
"return statements are only valid within func/subr blocks",
|
|
)
|
|
}
|
|
}
|
|
|
|
// Check: enforce return-value iff in a function; return-void iff in a subroutine
|
|
if astNode.Type == asts.NodeType(NodeTypeReturnStatement) {
|
|
if inUDF {
|
|
if len(astNode.Children) != 1 {
|
|
return fmt.Errorf(
|
|
"return statements in func blocks must return a value",
|
|
)
|
|
}
|
|
}
|
|
if inUDS {
|
|
if len(astNode.Children) != 0 {
|
|
return fmt.Errorf(
|
|
"return statements in subr blocks must not return a value",
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check: prohibit NR etc at LHS; 1+2=3+4; etc
|
|
if isAssignmentLHS {
|
|
ok := VALID_LHS_NODE_TYPES[string(astNode.Type)]
|
|
if !ok {
|
|
return fmt.Errorf(
|
|
"%s is not valid on the left-hand side of an assignment",
|
|
astNode.Type,
|
|
)
|
|
}
|
|
}
|
|
|
|
// Check: prohibit NR etc at LHS; 1+2=3+4; etc
|
|
if isUnset {
|
|
ok := VALID_LHS_NODE_TYPES[string(astNode.Type)]
|
|
if !ok {
|
|
return fmt.Errorf(
|
|
"%s is not valid for unset statement",
|
|
astNode.Type,
|
|
)
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
// Treewalk
|
|
|
|
for i, astChild := range astNode.Children {
|
|
nextLevelIsAssignmentLHS = (astNode.Type == asts.NodeType(NodeTypeAssignment) || astNode.Type == asts.NodeType(NodeTypeCompoundAssignment)) && i == 0
|
|
nextLevelIsUnset = astNode.Type == asts.NodeType(NodeTypeUnset)
|
|
err := validateASTAux(
|
|
astChild,
|
|
dslInstanceType,
|
|
nextLevelAtTopLevel,
|
|
nextLevelInLoop,
|
|
nextLevelInBeginOrEnd,
|
|
nextLevelInUDF,
|
|
nextLevelInUDS,
|
|
isMainBlockLastStatement,
|
|
nextLevelIsAssignmentLHS,
|
|
nextLevelIsUnset,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Check against 'for (a, a in $*)' -- repeated 'a'.
|
|
// AST:
|
|
// * statement block
|
|
// * double-variable for-loop "for"
|
|
// * local variable "a"
|
|
// * local variable "a"
|
|
// * full record "$*"
|
|
// * statement block
|
|
|
|
func validateForLoopTwoVariableUniqueNames(astNode *asts.ASTNode) error {
|
|
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeForLoopTwoVariable))
|
|
lib.InternalCodingErrorIf(len(astNode.Children) != 4)
|
|
keyVarNode := astNode.Children[0]
|
|
valVarNode := astNode.Children[1]
|
|
lib.InternalCodingErrorIf(keyVarNode.Type != asts.NodeType(NodeTypeLocalVariable))
|
|
lib.InternalCodingErrorIf(valVarNode.Type != asts.NodeType(NodeTypeLocalVariable))
|
|
keyVarName := tokenLit(keyVarNode)
|
|
valVarName := tokenLit(valVarNode)
|
|
if keyVarName == valVarName {
|
|
return fmt.Errorf("redefinition of variable %s in the same scope", keyVarName)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Check against 'for ((a,a), b in $*)' or 'for ((a,b), a in $*)' -- repeated 'a'.
|
|
// AST:
|
|
// * statement block
|
|
// - multi-variable for-loop "for"
|
|
// - parameter list
|
|
// - local variable "a"
|
|
// - local variable "b"
|
|
// - local variable "a"
|
|
// - full record "$*"
|
|
// - statement block
|
|
func validateForLoopMultivariableUniqueNames(astNode *asts.ASTNode) error {
|
|
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeForLoopMultivariable))
|
|
keyVarsNode := astNode.Children[0]
|
|
valVarNode := astNode.Children[1]
|
|
// PGPG produces MultiIndex; legacy produced ParameterList. Both have LocalVariable children.
|
|
lib.InternalCodingErrorIf(keyVarsNode.Type != asts.NodeType(NodeTypeParameterList) &&
|
|
keyVarsNode.Type != asts.NodeType(NodeTypeMultiIndex))
|
|
lib.InternalCodingErrorIf(valVarNode.Type != asts.NodeType(NodeTypeLocalVariable))
|
|
|
|
seen := make(map[string]bool)
|
|
|
|
for _, keyVarNode := range keyVarsNode.Children {
|
|
lib.InternalCodingErrorIf(keyVarNode.Type != asts.NodeType(NodeTypeLocalVariable))
|
|
name := tokenLit(keyVarNode)
|
|
_, present := seen[name]
|
|
if present {
|
|
return fmt.Errorf("redefinition of variable %s in the same scope", name)
|
|
}
|
|
seen[name] = true
|
|
}
|
|
|
|
valVarName := tokenLit(valVarNode)
|
|
if seen[valVarName] {
|
|
return fmt.Errorf("redefinition of variable %s in the same scope", valVarName)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
var VALID_LHS_NODE_TYPES = map[string]bool{
|
|
NodeTypeArrayOrMapIndexAccess: true,
|
|
NodeTypeDotOperator: true,
|
|
NodeTypeArraySliceLoHi: true,
|
|
NodeTypeArraySliceHiOnly: true,
|
|
NodeTypeArraySliceLoOnly: true,
|
|
NodeTypeArraySliceFull: true,
|
|
NodeTypeDirectFieldValue: true,
|
|
NodeTypeBracedFieldValue: true, // ${foo}, ${x+y}
|
|
NodeTypeEnvironmentVariable: true, // ENV["FOO"] = "bar"
|
|
NodeTypeDirectOosvarValue: true,
|
|
NodeTypeBracedOosvarValue: true, // @{foo}, @{variable.name}
|
|
NodeTypeFullOosvar: true,
|
|
NodeTypeFullSrec: true,
|
|
NodeTypeIndirectFieldValue: true, // includes $[[n]] and $[[[n]]]
|
|
NodeTypeIndirectOosvarValue: true,
|
|
NodeTypeLocalVariable: true,
|
|
"TypedeclLocalVariable": true, // int a = 5; etc
|
|
}
|