Appearance
| 1 | namespace Syntax.Process is | |
| 2 | use Source | |
| 3 | use Trees | |
| 4 | ||
| 5 | // Synthesises the private CLR-facing `_entry` wrapper a hand-written | |
| 6 | // entry function needs when it declares a `Ghul.Environment` parameter. | |
| 7 | // | |
| 8 | // A named function's signature is always fully explicit (GHUL.md), so | |
| 9 | // recognising the two argument shapes - a `string[]` and a | |
| 10 | // `Ghul.Environment` - needs nothing but the written type expressions, | |
| 11 | // and this runs at the same syntax-tree-rewriting stage as | |
| 12 | // synthesise-top-level-entry and rewrite-primary-constructors, before | |
| 13 | // any symbol exists. | |
| 14 | // | |
| 15 | // Only a function whose name matches the configured entry-point name | |
| 16 | // is considered - the same name select-entry-point itself matches - | |
| 17 | // and only when every one of its one or two parameters is one of the | |
| 18 | // two recognised shapes, with a `Ghul.Environment` one actually among | |
| 19 | // them. Anything else (a lone `string[]`, no parameters, an | |
| 20 | // unrecognised shape) is left untouched: select-entry-point's existing | |
| 21 | // signature check decides its fate exactly as it always has. | |
| 22 | // | |
| 23 | // The wrapper takes the real Main-compatible shape - no parameters, or | |
| 24 | // one `string[]` when the original declared one - and its body is a | |
| 25 | // single call back to the original function, with `Ghul.ENVIRONMENT` | |
| 26 | // .instance` in the environment slot and its own `args` parameter (if | |
| 27 | // any) in the other, in whichever order the original declared them. | |
| 28 | // The wrapper's own return type is a copy of the original's, so its | |
| 29 | // tail - the call itself, with no explicit `return` - is handled by | |
| 30 | // the ordinary ghul tail-return rule regardless of whether that type | |
| 31 | // is `int` or void. | |
| 32 | // | |
| 33 | // The original function is marked so select-entry-point excludes it | |
| 34 | // from candidacy outright: its own signature is never Main-compatible, | |
| 35 | // and the wrapper is what stands in for it. | |
| 36 | class SYNTHESISE_ENTRY_ENVIRONMENT_WRAPPER(_ir_context: IR.CONTEXT) is | |
| 37 | _WRAPPER_NAME: string static => "_entry" | |
| 38 | _ARGS_PARAMETER_NAME: string static => "args" | |
| 39 | ||
| 40 | apply(node: Node) is | |
| 41 | if let list = cast Definitions.LIST?(node) then | |
| 42 | _process_list(list) | |
| 43 | fi | |
| 44 | si | |
| 45 | ||
| 46 | _process_list(list: Definitions.LIST) is | |
| 47 | let to_add = Collections.LIST[Definitions.FUNCTION]() | |
| 48 | ||
| 49 | // `use` applies only within the namespace block it is written | |
| 50 | // in (GHUL.md), so each list's own uses are what a bare name | |
| 51 | // written in its own definitions can have come from. | |
| 52 | let uses = list.uses | |
| 53 | ||
| 54 | let has_named_entry = _has_hand_written_entry(list) | |
| 55 | ||
| 56 | for d in list do | |
| 57 | let unwrapped = d.without_pragmas | |
| 58 | ||
| 59 | if let `namespace = cast Definitions.NAMESPACE?(unwrapped) then | |
| 60 | _process_list(`namespace.body) | |
| 61 | elif let function = cast Definitions.FUNCTION?(unwrapped) then | |
| 62 | if let wrapper = _maybe_synthesise_wrapper(function, uses, has_named_entry) then | |
| 63 | to_add.add(wrapper) | |
| 64 | fi | |
| 65 | fi | |
| 66 | od | |
| 67 | ||
| 68 | for wrapper in to_add do | |
| 69 | list.add(wrapper) | |
| 70 | od | |
| 71 | si | |
| 72 | ||
| 73 | // Whether a hand-written function in this list matches the | |
| 74 | // entry-point name. A file may carry top-level statements and | |
| 75 | // global definitions together, and the hand-written entry outranks | |
| 76 | // the synthesised one in select-entry-point; giving both a wrapper | |
| 77 | // would declare `_entry` twice. | |
| 78 | _has_hand_written_entry(list: Definitions.LIST) -> bool is | |
| 79 | for d in list do | |
| 80 | if let function = cast Definitions.FUNCTION?(d.without_pragmas) then | |
| 81 | if !function.is_top_level_entry /\ function.name? /\ | |
| 82 | function.name.name =~ _ir_context.entry_point_name | |
| 83 | then | |
| 84 | return true | |
| 85 | fi | |
| 86 | fi | |
| 87 | od | |
| 88 | ||
| 89 | return false | |
| 90 | si | |
| 91 | ||
| 92 | _maybe_synthesise_wrapper(function: Definitions.FUNCTION, uses: Collections.List[Definitions.USE], has_named_entry: bool) -> Definitions.FUNCTION? is | |
| 93 | if function.is_top_level_entry /\ has_named_entry then | |
| 94 | return null | |
| 95 | fi | |
| 96 | ||
| 97 | let name = function.name | |
| 98 | ||
| 99 | if !name? \/ !(name.name =~ _ir_context.entry_point_name) then | |
| 100 | return null | |
| 101 | fi | |
| 102 | ||
| 103 | let variables = function.arguments.variables | |
| 104 | ||
| 105 | if variables.count < 1 \/ variables.count > 2 then | |
| 106 | return null | |
| 107 | fi | |
| 108 | ||
| 109 | let args_variable: Variables.VARIABLE? mut = null | |
| 110 | let env_variable: Variables.VARIABLE? mut = null | |
| 111 | ||
| 112 | for variable in variables do | |
| 113 | if _is_args_shaped(variable.type_expression) then | |
| 114 | if args_variable? then | |
| 115 | return null | |
| 116 | fi | |
| 117 | ||
| 118 | args_variable = variable | |
| 119 | elif _is_environment_shaped(variable.type_expression, uses) then | |
| 120 | if env_variable? then | |
| 121 | return null | |
| 122 | fi | |
| 123 | ||
| 124 | env_variable = variable | |
| 125 | else | |
| 126 | return null | |
| 127 | fi | |
| 128 | od | |
| 129 | ||
| 130 | if !env_variable? then | |
| 131 | return null | |
| 132 | fi | |
| 133 | ||
| 134 | function.wrapped_by_synthesized_main = true | |
| 135 | ||
| 136 | return _synthesise_wrapper(function, args_variable?, env_variable, function.is_top_level_entry) | |
| 137 | si | |
| 138 | ||
| 139 | _is_args_shaped(type_expression: TypeExpressions.TypeExpression) -> bool is | |
| 140 | if let array = cast TypeExpressions.ARRAY_?(type_expression) then | |
| 141 | return _is_bare_named(array.element, "string") | |
| 142 | fi | |
| 143 | ||
| 144 | return false | |
| 145 | si | |
| 146 | ||
| 147 | // Fully-qualified `Ghul.Environment` always matches. A bare name | |
| 148 | // also matches when this namespace block's own `use` declarations | |
| 149 | // (GHUL.md: a `use` applies only within the block it is written | |
| 150 | // in) bring `Ghul.Environment` into scope under that name - the | |
| 151 | // three ordinary (non-type-alias) import shapes: `use Ghul;`, | |
| 152 | // `use Ghul.Environment;`, and `use X = Ghul.Environment;`. | |
| 153 | _is_environment_shaped(type_expression: TypeExpressions.TypeExpression, uses: Collections.List[Definitions.USE]) -> bool is | |
| 154 | if let named = cast TypeExpressions.NAMED?(type_expression) then | |
| 155 | let id = named.name | |
| 156 | ||
| 157 | if _names_ghul_environment(id) then | |
| 158 | return true | |
| 159 | fi | |
| 160 | ||
| 161 | if id.is_qualified then | |
| 162 | return false | |
| 163 | fi | |
| 164 | ||
| 165 | for u in uses do | |
| 166 | if let imported = u.`use then | |
| 167 | if let alias = u.name then | |
| 168 | if alias.name =~ id.name /\ _names_ghul_environment(imported) then | |
| 169 | return true | |
| 170 | fi | |
| 171 | elif imported.is_qualified then | |
| 172 | if imported.name =~ id.name /\ _names_ghul_environment(imported) then | |
| 173 | return true | |
| 174 | fi | |
| 175 | elif imported.name =~ "Ghul" /\ id.name =~ "Environment" then | |
| 176 | return true | |
| 177 | fi | |
| 178 | fi | |
| 179 | od | |
| 180 | fi | |
| 181 | ||
| 182 | return false | |
| 183 | si | |
| 184 | ||
| 185 | _names_ghul_environment(id: Identifiers.Identifier) -> bool => | |
| 186 | id.name =~ "Environment" /\ | |
| 187 | id.qualifier? /\ | |
| 188 | id.qualifier.name =~ "Ghul" /\ | |
| 189 | !id.qualifier.qualifier? | |
| 190 | ||
| 191 | _is_bare_named(type_expression: TypeExpressions.TypeExpression, name: string) -> bool is | |
| 192 | if let named = cast TypeExpressions.NAMED?(type_expression) then | |
| 193 | return !named.name.is_qualified /\ named.name.name =~ name | |
| 194 | fi | |
| 195 | ||
| 196 | return false | |
| 197 | si | |
| 198 | ||
| 199 | _synthesise_wrapper( | |
| 200 | original: Definitions.FUNCTION, | |
| 201 | has_args: bool, | |
| 202 | env_variable: Variables.VARIABLE, | |
| 203 | wraps_top_level: bool | |
| 204 | ) -> Definitions.FUNCTION is | |
| 205 | // The wrapper stands where the function it wraps stands, but | |
| 206 | // nothing inside it does: the parts carry an internal location, | |
| 207 | // so they record no use a hover or a completion in the wrapped | |
| 208 | // function could land on. | |
| 209 | let location = original.location | |
| 210 | let internal = LOCATION.internal | |
| 211 | let arg_vars = Collections.LIST[Variables.VARIABLE]() | |
| 212 | ||
| 213 | if has_args then | |
| 214 | let args_type = TypeExpressions.ARRAY_(internal, TypeExpressions.NAMED(internal, Identifiers.Identifier(internal, "string"))) | |
| 215 | let args_param = Variables.VARIABLE(internal, Identifiers.Identifier(internal, _ARGS_PARAMETER_NAME), args_type, false, true, null) | |
| 216 | ||
| 217 | args_param.mark_argument() | |
| 218 | arg_vars.add(args_param) | |
| 219 | fi | |
| 220 | ||
| 221 | let arg_list = Variables.LIST(internal, arg_vars) | |
| 222 | let call_args = Collections.LIST[Expressions.Expression]() | |
| 223 | ||
| 224 | for variable in original.arguments.variables do | |
| 225 | if variable == env_variable then | |
| 226 | call_args.add(_environment_instance_expression(internal)) | |
| 227 | else | |
| 228 | call_args.add(Expressions.IDENTIFIER(internal, Identifiers.Identifier(internal, _ARGS_PARAMETER_NAME))) | |
| 229 | fi | |
| 230 | od | |
| 231 | ||
| 232 | let call = | |
| 233 | Expressions.CALL( | |
| 234 | location, | |
| 235 | Expressions.IDENTIFIER(internal, Identifiers.Identifier(internal, _ir_context.entry_point_name)), | |
| 236 | Expressions.LIST(internal, call_args) | |
| 237 | ) | |
| 238 | ||
| 239 | let body_statements = Collections.LIST[Statements.Statement]() | |
| 240 | ||
| 241 | body_statements.add(Statements.EXPRESSION(internal, call)) | |
| 242 | ||
| 243 | let body = Bodies.BLOCK(internal, Statements.LIST(internal, body_statements)) | |
| 244 | let modifiers = Modifiers.LIST(internal, null, null) | |
| 245 | ||
| 246 | let wrapper = | |
| 247 | Definitions.FUNCTION( | |
| 248 | location, | |
| 249 | Identifiers.Identifier(LOCATION.internal, _WRAPPER_NAME), | |
| 250 | TypeExpressions.LIST(location, Collections.LIST[TypeExpressions.TypeExpression](0)), | |
| 251 | arg_list, | |
| 252 | original.type_expression.copy(), | |
| 253 | modifiers, | |
| 254 | body | |
| 255 | ) | |
| 256 | ||
| 257 | wrapper.mark_synthesized() | |
| 258 | wrapper.is_synthesized_main_wrapper = true | |
| 259 | wrapper.wraps_top_level_entry = wraps_top_level | |
| 260 | ||
| 261 | return wrapper | |
| 262 | si | |
| 263 | ||
| 264 | _environment_instance_expression(location: LOCATION) -> Expressions.Expression is | |
| 265 | let ghul = Expressions.IDENTIFIER(location, Identifiers.Identifier(location, "Ghul")) | |
| 266 | let environment_class = Expressions.MEMBER(location, ghul, Identifiers.Identifier(location, "ENVIRONMENT"), location) | |
| 267 | ||
| 268 | return Expressions.MEMBER(location, environment_class, Identifiers.Identifier(location, "instance"), location) | |
| 269 | si | |
| 270 | si | |
| 271 | si |