Appearance
| 1 | namespace Syntax.Parsers.Definitions is | |
| 2 | ||
| 3 | use Source; | |
| 4 | use Logging; | |
| 5 | ||
| 6 | use Ghul.Pipes; | |
| 7 | ||
| 8 | class FUNCTION( | |
| 9 | identifier_function_name_parser: Parser[Trees.Identifiers.Identifier], | |
| 10 | type_parser: Parser[Trees.TypeExpressions.TypeExpression], | |
| 11 | type_list_parser: Parser[Trees.TypeExpressions.LIST], | |
| 12 | modifier_list_parser: Parser[Trees.Modifiers.LIST], | |
| 13 | body_parser: Parser[Trees.Bodies.Body], | |
| 14 | variable_list_parser: Parser[Trees.Variables.LIST] | |
| 15 | ): Base[Trees.Definitions.FUNCTION] is | |
| 16 | super(); | |
| 17 | ||
| 18 | parse_generic_arguments( | |
| 19 | context: CONTEXT | |
| 20 | ) -> Syntax.Trees.TypeExpressions.LIST is | |
| 21 | if context.current.token == Lexical.TOKEN.SQUARE_OPEN then | |
| 22 | context.next_token(); | |
| 23 | ||
| 24 | let generic_arguments = type_list_parser.parse(context)!; | |
| 25 | generic_arguments.check_no_reference_types(context.logger); | |
| 26 | ||
| 27 | let is_poisoned mut = generic_arguments.is_poisoned; | |
| 28 | ||
| 29 | if !is_poisoned \/ context.current.token == Lexical.TOKEN.SQUARE_CLOSE then | |
| 30 | is_poisoned = !context.next_token(Lexical.TOKEN.SQUARE_CLOSE) \/ is_poisoned; | |
| 31 | fi | |
| 32 | ||
| 33 | generic_arguments.poison(is_poisoned); | |
| 34 | ||
| 35 | return generic_arguments; | |
| 36 | fi | |
| 37 | return Syntax.Trees.TypeExpressions.LIST(context.location, Collections.LIST[Syntax.Trees.TypeExpressions.TypeExpression](0)); | |
| 38 | si | |
| 39 | ||
| 40 | parse_formal_arguments( | |
| 41 | context: CONTEXT, | |
| 42 | start: Source.LOCATION, | |
| 43 | name: Syntax.Trees.Identifiers.Identifier?, | |
| 44 | generic_arguments: Syntax.Trees.TypeExpressions.LIST | |
| 45 | ) -> | |
| 46 | (arguments: Trees.Variables.LIST, function: Trees.Definitions.FUNCTION?) | |
| 47 | is | |
| 48 | let is_poisoned mut = false; | |
| 49 | let arguments_open_parenthesis_location = context.location; | |
| 50 | ||
| 51 | context.next_token(Lexical.TOKEN.PAREN_OPEN); | |
| 52 | ||
| 53 | let use arguments_tokenizer_snapshot = context.tokenizer_speculate_then_commit(); | |
| 54 | let use arguments_logger_snaphot = context.logger_speculate_then_commit(); | |
| 55 | ||
| 56 | let arguments: Trees.Variables.LIST mut = Trees.Variables.LIST(context.location, Collections.LIST[Trees.Variables.VARIABLE](0)); | |
| 57 | let function: Trees.Definitions.FUNCTION? mut = null; | |
| 58 | ||
| 59 | let any_bad_arguments mut = false; | |
| 60 | ||
| 61 | if context.current_token != Lexical.TOKEN.PAREN_CLOSE then | |
| 62 | if | |
| 63 | context.current.token != Lexical.TOKEN.IDENTIFIER /\ | |
| 64 | context.current.token != Lexical.TOKEN.AT | |
| 65 | then | |
| 66 | is_poisoned = true; | |
| 67 | fi | |
| 68 | ||
| 69 | let in_init = name? /\ name.name =~ "init"; | |
| 70 | let previous_in_init_arguments = context.in_init_arguments; | |
| 71 | let previous_in_formal_arguments = context.in_formal_arguments; | |
| 72 | ||
| 73 | if in_init then | |
| 74 | context.in_init_arguments = true; | |
| 75 | fi | |
| 76 | ||
| 77 | context.in_formal_arguments = true; | |
| 78 | ||
| 79 | try | |
| 80 | arguments = variable_list_parser.parse(context)!; | |
| 81 | finally | |
| 82 | context.in_init_arguments = previous_in_init_arguments; | |
| 83 | context.in_formal_arguments = previous_in_formal_arguments; | |
| 84 | yrt | |
| 85 | ||
| 86 | any_bad_arguments = arguments.is_poisoned; | |
| 87 | ||
| 88 | let last_valid_argument_line mut = 0; | |
| 89 | ||
| 90 | for (index, a) in arguments |> index() do | |
| 91 | if !a.is_splice then | |
| 92 | a.mark_argument(); | |
| 93 | a.left.mark_argument_recursive(); | |
| 94 | fi | |
| 95 | ||
| 96 | if a.is_poisoned then | |
| 97 | is_poisoned = true; | |
| 98 | any_bad_arguments = true; | |
| 99 | ||
| 100 | elif a.is_splice then | |
| 101 | // The `..` splice marker carries no type and | |
| 102 | // no name. The rewrite-primary-constructors | |
| 103 | // pass expands it into the surrounding class's | |
| 104 | // primary parameters; skip the | |
| 105 | // explicit-argument-type check for it. The | |
| 106 | // variable parser already rejected `..` in any | |
| 107 | // non-init position via `in_init_arguments`, | |
| 108 | // so reaching here means we're in an init. | |
| 109 | last_valid_argument_line = a.location.start_line; | |
| 110 | elif | |
| 111 | !a.left.is_simple_name /\ | |
| 112 | a.left.has_named_group | |
| 113 | then | |
| 114 | context.error(a.location, "named destructuring is not supported in a formal argument list"); | |
| 115 | a.poison(true); | |
| 116 | is_poisoned = true; | |
| 117 | any_bad_arguments = true; | |
| 118 | elif a.type_expression.is_inferred then | |
| 119 | context.error(a.location, "explicit argument type required"); | |
| 120 | a.poison(true); | |
| 121 | is_poisoned = true; | |
| 122 | any_bad_arguments = true; | |
| 123 | else | |
| 124 | if a.initializer? /\ !isa Trees.Expressions.DEFAULT(a.initializer) then | |
| 125 | context.error(a.initializer.location, "default value of an argument must be _"); | |
| 126 | fi | |
| 127 | ||
| 128 | last_valid_argument_line = | |
| 129 | if a.left.is_simple_name then a.name!.location.start_line else a.location.start_line fi; | |
| 130 | fi | |
| 131 | od | |
| 132 | ||
| 133 | if | |
| 134 | context.current_token == Lexical.TOKEN.PAREN_OPEN \/ | |
| 135 | (context.current_token == Lexical.TOKEN.IDENTIFIER /\ context.location.start_line > arguments.location.end_line) | |
| 136 | then | |
| 137 | // we might have run off the end of an incomplete function | |
| 138 | // signature into the start of a following function | |
| 139 | // signature | |
| 140 | ||
| 141 | let could_be_next_function mut = false; | |
| 142 | ||
| 143 | // TODO not sure all these conditions are necessary/meaningful | |
| 144 | if arguments.variables.count == 0 then | |
| 145 | could_be_next_function = true; | |
| 146 | elif any_bad_arguments /\ context.location.start_line > arguments.location.end_line then | |
| 147 | could_be_next_function = true; | |
| 148 | elif last_valid_argument_line < context.location.start_line then | |
| 149 | could_be_next_function = true; | |
| 150 | fi | |
| 151 | ||
| 152 | if could_be_next_function then | |
| 153 | arguments_logger_snaphot.backtrack(); | |
| 154 | ||
| 155 | for a in arguments do | |
| 156 | a.poison(true); | |
| 157 | od | |
| 158 | ||
| 159 | let roll_forwards_to_line = context.location.start_line; | |
| 160 | ||
| 161 | arguments_tokenizer_snapshot.backtrack(); | |
| 162 | ||
| 163 | while context.location.start_line < roll_forwards_to_line do | |
| 164 | context.next_token(); | |
| 165 | od | |
| 166 | ||
| 167 | function = | |
| 168 | Trees.Definitions.FUNCTION( | |
| 169 | start::(name?.location ?? context.location), | |
| 170 | name, | |
| 171 | generic_arguments, | |
| 172 | arguments, | |
| 173 | Trees.TypeExpressions.INFER(context.location), | |
| 174 | Trees.Modifiers.LIST(context.location, null, null), | |
| 175 | Trees.Bodies.NULL(context.location) | |
| 176 | ); | |
| 177 | ||
| 178 | context.logger.error( | |
| 179 | if arguments.variables.count > 0 then | |
| 180 | arguments.location | |
| 181 | else | |
| 182 | arguments_open_parenthesis_location | |
| 183 | fi, | |
| 184 | "syntax error: incomplete formal arguments" | |
| 185 | ); | |
| 186 | ||
| 187 | return (arguments, function); | |
| 188 | fi | |
| 189 | fi | |
| 190 | fi | |
| 191 | ||
| 192 | arguments_tokenizer_snapshot.commit(); | |
| 193 | arguments_logger_snaphot.commit(); | |
| 194 | ||
| 195 | // TODO: could recognize that no closing parenthesis followed by a line then | |
| 196 | // an identifier and an opening paren is probably another function signature | |
| 197 | ||
| 198 | if !is_poisoned \/ context.current.token == Lexical.TOKEN.PAREN_CLOSE then | |
| 199 | is_poisoned = !context.next_token(Lexical.TOKEN.PAREN_CLOSE) \/ is_poisoned; | |
| 200 | fi | |
| 201 | ||
| 202 | arguments.poison(is_poisoned); | |
| 203 | ||
| 204 | return (arguments, function); | |
| 205 | si | |
| 206 | ||
| 207 | parse_return_type( | |
| 208 | context: CONTEXT, | |
| 209 | start: Source.LOCATION, | |
| 210 | name: Syntax.Trees.Identifiers.Identifier?, | |
| 211 | generic_arguments: Syntax.Trees.TypeExpressions.LIST, | |
| 212 | arguments: Trees.Variables.LIST | |
| 213 | ) -> | |
| 214 | (type_expression: Trees.TypeExpressions.TypeExpression, function: Trees.Definitions.FUNCTION?) | |
| 215 | is | |
| 216 | let is_poisoned mut = false; | |
| 217 | let type_expression: Trees.TypeExpressions.TypeExpression mut; | |
| 218 | let function: Trees.Definitions.FUNCTION? mut = null; | |
| 219 | ||
| 220 | if context.current.token == Lexical.TOKEN.ARROW_THIN then | |
| 221 | let arrow_location = context.location; | |
| 222 | context.next_token(); | |
| 223 | ||
| 224 | let use return_type_tokenizer_snapshot = context.tokenizer_speculate_then_commit(); | |
| 225 | ||
| 226 | type_expression = type_parser.parse(context)!; | |
| 227 | type_expression.check_is_not_reference(context.logger, "function cannot return a reference"); | |
| 228 | ||
| 229 | is_poisoned = is_poisoned \/ type_expression.is_poisoned; | |
| 230 | ||
| 231 | if context.current_token == Lexical.TOKEN.PAREN_OPEN then | |
| 232 | return_type_tokenizer_snapshot.backtrack(); | |
| 233 | ||
| 234 | function = | |
| 235 | Trees.Definitions.FUNCTION( | |
| 236 | start::name!.location, | |
| 237 | name, | |
| 238 | generic_arguments, | |
| 239 | arguments, | |
| 240 | Trees.TypeExpressions.INFER(context.location), | |
| 241 | Trees.Modifiers.LIST(context.location, null, null), | |
| 242 | Trees.Bodies.NULL(context.location) | |
| 243 | ); | |
| 244 | ||
| 245 | context.logger.error(arrow_location, "syntax error: incomplete return type"); | |
| 246 | ||
| 247 | return (type_expression, function); | |
| 248 | fi | |
| 249 | ||
| 250 | return_type_tokenizer_snapshot.commit(); | |
| 251 | else | |
| 252 | type_expression = Trees.TypeExpressions.INFER(context.location); | |
| 253 | fi | |
| 254 | ||
| 255 | return (type_expression, function); | |
| 256 | si | |
| 257 | ||
| 258 | parse(context: CONTEXT) -> Trees.Definitions.FUNCTION is | |
| 259 | if context.in_classy then | |
| 260 | context.in_member = true; | |
| 261 | context.member_indent = context.location.start_column | |
| 262 | else | |
| 263 | context.in_global_function = true; | |
| 264 | context.global_indent = context.location.start_column; | |
| 265 | fi | |
| 266 | ||
| 267 | try | |
| 268 | let is_poisoned mut = false; | |
| 269 | let start = context.location; | |
| 270 | let name = identifier_function_name_parser.parse(context); | |
| 271 | ||
| 272 | let generic_arguments = parse_generic_arguments(context); | |
| 273 | ||
| 274 | is_poisoned = generic_arguments.is_poisoned; | |
| 275 | ||
| 276 | let af = parse_formal_arguments(context, start, name, generic_arguments); | |
| 277 | ||
| 278 | if af.function? then | |
| 279 | return af.function!; | |
| 280 | fi | |
| 281 | ||
| 282 | let arguments = af.arguments; | |
| 283 | ||
| 284 | is_poisoned = is_poisoned \/ arguments.is_poisoned; | |
| 285 | ||
| 286 | let rt = parse_return_type(context, start, name, generic_arguments, arguments); | |
| 287 | ||
| 288 | if rt.function? then | |
| 289 | return rt.function!; | |
| 290 | fi | |
| 291 | ||
| 292 | let type_expression = rt.type_expression; | |
| 293 | ||
| 294 | is_poisoned = is_poisoned \/ type_expression.is_poisoned; | |
| 295 | ||
| 296 | let modifiers = modifier_list_parser.parse(context)!; | |
| 297 | let expect_semicolon = context.current.token != Lexical.TOKEN.IS; | |
| 298 | ||
| 299 | let body: Trees.Bodies.Body mut = Trees.Bodies.NULL(context.location); | |
| 300 | ||
| 301 | try | |
| 302 | body = body_parser.parse(context)! | |
| 303 | catch ue: UNWIND_TO_MEMBER_EXCEPTION | |
| 304 | body = Trees.Bodies.NULL(context.location); | |
| 305 | yrt | |
| 306 | ||
| 307 | let result = | |
| 308 | Trees.Definitions.FUNCTION( | |
| 309 | start::body.location, | |
| 310 | name, | |
| 311 | generic_arguments, | |
| 312 | arguments, | |
| 313 | type_expression, | |
| 314 | modifiers, | |
| 315 | body | |
| 316 | ); | |
| 317 | ||
| 318 | if expect_semicolon then | |
| 319 | if !is_poisoned \/ context.current.token == Lexical.TOKEN.SEMICOLON then | |
| 320 | is_poisoned = !context.next_token(Lexical.TOKEN.SEMICOLON) \/ is_poisoned; | |
| 321 | fi | |
| 322 | fi | |
| 323 | ||
| 324 | result.poison(is_poisoned); | |
| 325 | ||
| 326 | return result; | |
| 327 | ||
| 328 | finally | |
| 329 | if context.in_classy then | |
| 330 | context.in_member = false; | |
| 331 | else | |
| 332 | context.in_global_function = false; | |
| 333 | fi | |
| 334 | yrt | |
| 335 | si | |
| 336 | si | |
| 337 | si |