1// ================================================================ 2// Shows warnings for things like uninitialized variables. These are things 3// that are statically computable from the AST by itself -- it confines itself 4// to local-variable analysis. There are other uninitialization issues 5// detectable only at runtime, which would benefit from a 'strict mode'. 6// ================================================================ 7 8package cst 9 10import ( 11 "fmt" 12 "os" 13 14 "miller/src/dsl" 15 "miller/src/lib" 16) 17 18// ---------------------------------------------------------------- 19// Returns true if there are no warnings. 20func WarnOnAST( 21 ast *dsl.AST, 22) bool { 23 variableNamesWrittenTo := make(map[string]bool) 24 inAssignment := false 25 ok := true 26 27 if ast.RootNode.Children != nil { 28 for _, astChild := range ast.RootNode.Children { 29 ok1 := warnOnASTAux( 30 astChild, 31 variableNamesWrittenTo, 32 inAssignment, 33 ) 34 // Don't end early on first warning; tree-walk to list them all. 35 ok = ok1 && ok 36 } 37 } 38 39 return ok 40} 41 42// ---------------------------------------------------------------- 43// Example ASTs: 44// 45// $ mlr -n put -v 'z = x + y' 46// DSL EXPRESSION: 47// z = x + y 48// 49// AST: 50// * statement block 51// * assignment "=" 52// * local variable "z" 53// * operator "+" 54// * local variable "x" 55// * local variable "y" 56// 57// $ mlr -n put -v 'z[i] = x + y' 58// DSL EXPRESSION: 59// z[i] = x + y 60// 61// AST: 62// * statement block 63// * assignment "=" 64// * array or map index access "[]" 65// * local variable "z" 66// * local variable "i" 67// * operator "+" 68// * local variable "x" 69// * local variable "y" 70// 71// $ mlr -n put -v 'func f(n) { return n+1}' 72// DSL EXPRESSION: 73// func f(n) { return n+1} 74// 75// AST: 76// * statement block 77// * function definition "f" 78// * parameter list 79// * parameter 80// * parameter name "n" 81// * statement block 82// * return "return" 83// * operator "+" 84// * local variable "n" 85// * int literal "1" 86 87// Returns true if there are no warnings 88func warnOnASTAux( 89 astNode *dsl.ASTNode, 90 variableNamesWrittenTo map[string]bool, 91 inAssignment bool, 92) bool { 93 94 ok := true 95 96 // Check local-variable references, and see if they're reads or writes 97 // based on the AST parenting of this node. 98 if astNode.Type == dsl.NodeTypeLocalVariable { 99 variableName := string(astNode.Token.Lit) 100 if inAssignment { 101 variableNamesWrittenTo[variableName] = true 102 } else { 103 if !variableNamesWrittenTo[variableName] { 104 // TODO: this would be much more useful with line numbers. :( 105 // That would be a big of work with the parser. Fortunately, 106 // Miller is designed around low-keystroke little expressions 107 // -- not thousands of lines of Miller-DSL source code -- so 108 // people can look at their few lines of Miller-DSL code and 109 // spot their error. 110 fmt.Fprintf( 111 os.Stderr, 112 "Variable name %s might not have been assigned yet.\n", 113 variableName, 114 ) 115 ok = false 116 } 117 } 118 119 } else if astNode.Type == dsl.NodeTypeBeginBlock { 120 // Locals are confined to begin/end blocks and func/subr blocks. 121 // Reset for this part of the treewalk. 122 variableNamesWrittenTo = make(map[string]bool) 123 } else if astNode.Type == dsl.NodeTypeEndBlock { 124 // Locals are confined to begin/end blocks and func/subr blocks. 125 // Reset for this part of the treewalk. 126 variableNamesWrittenTo = make(map[string]bool) 127 128 } else if astNode.Type == dsl.NodeTypeFunctionDefinition { 129 // Locals are confined to begin/end blocks and func/subr blocks. Reset 130 // for this part of the treewalk, except mark the parameters as 131 // defined. 132 variableNamesWrittenTo = noteParametersForWarnings(astNode) 133 } else if astNode.Type == dsl.NodeTypeSubroutineDefinition { 134 // Locals are confined to begin/end blocks and func/subr blocks. Reset 135 // for this part of the treewalk, except mark the parameters as 136 // defined. 137 variableNamesWrittenTo = noteParametersForWarnings(astNode) 138 } 139 140 // Treewalk to check the rest of the AST below this node. 141 142 if astNode.Children != nil { 143 for i, astChild := range astNode.Children { 144 childInAssignment := inAssignment 145 146 if astNode.Type == dsl.NodeTypeAssignment && i == 0 { 147 // LHS of assignment statements 148 childInAssignment = true 149 } else if astNode.Type == dsl.NodeTypeForLoopOneVariable && i == 0 { 150 // The 'k' in 'for (k in $*)' 151 childInAssignment = true 152 } else if astNode.Type == dsl.NodeTypeForLoopTwoVariable && (i == 0 || i == 1) { 153 // The 'k' and 'v' in 'for (k,v in $*)' 154 childInAssignment = true 155 } else if astNode.Type == dsl.NodeTypeForLoopMultivariable && (i == 0 || i == 1) { 156 // The 'k1', 'k2', and 'v' in 'for ((k1,k2),v in $*)' 157 childInAssignment = true 158 } else if astNode.Type == dsl.NodeTypeParameterList { 159 childInAssignment = true 160 } else if inAssignment && astNode.Type == dsl.NodeTypeArrayOrMapIndexAccess { 161 // In 'z[i] = 1', the 'i' is a read and the 'z' is a write. 162 // 163 // mlr --from r put -v -W 'z[i] = 1' 164 // DSL EXPRESSION: 165 // z[i]=1 166 // 167 // AST: 168 // * statement block 169 // * assignment "=" 170 // * array or map index access "[]" 171 // * local variable "z" 172 // * local variable "i" 173 // * int literal "1" 174 if i == 0 { 175 childInAssignment = true 176 } else { 177 childInAssignment = false 178 } 179 } 180 ok1 := warnOnASTAux( 181 astChild, 182 variableNamesWrittenTo, 183 childInAssignment, 184 ) 185 // Don't end early on first error; tree-walk to list them all. 186 ok = ok1 && ok 187 } 188 } 189 190 return ok 191} 192 193// ---------------------------------------------------------------- 194// Given a func/subr block, find the names of its parameters. All the 195// lib.InternalCodingErrorIf parts are shape-assertions to make sure this code 196// is in sync with the BNF grammar which builds the AST from a Miller-DSL 197// source string. 198func noteParametersForWarnings( 199 astNode *dsl.ASTNode, 200) map[string]bool { 201 202 variableNamesWrittenTo := make(map[string]bool) 203 204 lib.InternalCodingErrorIf( 205 astNode.Type != dsl.NodeTypeFunctionDefinition && 206 astNode.Type != dsl.NodeTypeSubroutineDefinition) 207 lib.InternalCodingErrorIf(len(astNode.Children) < 1) 208 parameterListNode := astNode.Children[0] 209 210 lib.InternalCodingErrorIf(parameterListNode.Type != dsl.NodeTypeParameterList) 211 212 for _, parameterNode := range parameterListNode.Children { 213 lib.InternalCodingErrorIf(parameterNode.Type != dsl.NodeTypeParameter) 214 lib.InternalCodingErrorIf(len(parameterNode.Children) != 1) 215 parameterNameNode := parameterNode.Children[0] 216 lib.InternalCodingErrorIf(parameterNameNode.Type != dsl.NodeTypeParameterName) 217 parameterName := string(parameterNameNode.Token.Lit) 218 variableNamesWrittenTo[parameterName] = true 219 } 220 221 return variableNamesWrittenTo 222} 223