def resolve_str(s, mark): parts = [] i = 0 while i < len(s): if s[i] == '\\': if i + 1 >= len(s): raise base.Error([mark], f'Incomplete escape') if s[i + 1].isdigit(): j = i + 1 while j < len(s) and s[j].isdigit(): j += 1 parts.append(chr(int(s[i + 1:j], 8))) i = j elif s[i + 1] in _ESCAPE_MAP: parts.append(_ESCAPE_MAP[s[i + 1]]) i += 2 else: raise base.Error([mark], f'Invalid escape {s[i + 1]}') else: j = i + 1 while j < len(s) and s[j] != '\\': j += 1 parts.append(s[i:j]) i = j return ''.join(parts)
def get_func_def(scope, name, mark): if name not in scope: raise base.Error([mark], f'{name} is not defined') if not isinstance(scope[name], ast.Function): raise base.Error([scope[name].mark, mark], f'{name} is not a function') return scope[name]
def function_callback(m): native = m.value[0] rtype = m.value[1] name = m.value[2] params = m.value[3] body_thunk = m.value[4] if body_thunk is None: body = None else: scope = Scope(module_scope) for param in params: scope[param.name] = param body = body_thunk(scope) if validate: if native and body: raise base.Error([m.mark], 'Native functions cannot have a body') if not native and not body: raise base.Error([m.mark], 'Non-native functions must have a body') return ast.Function( mark=m.mark, rtype=rtype, name=name, params=params, body=body, )
def fcall(mark, f, args): if len(f.params) != len(args): raise base.Error( [f.mark, mark], f'Expected {len(f.params)} args but got {len(args)}') for param, arg in zip(f.params, args): if not types.convertible(arg.type, param.type, global_dict): raise base.Error( [param.mark, arg.mark], f'Expected type {param.type} but got {arg.type}') return ast.FunctionCall(mark=mark, f=f, args=args)
def check_class_name(m): name = m.value if global_dict is not None: if name not in global_dict: raise base.Error( [m.mark], f'{repr(name)} is not defined', ) if not isinstance(global_dict[name], ast.Class): raise base.Error( [m.mark, global_dict[name].mark], f'{repr(name)} is not a type', ) return name
def process_indents_adapter(tokens): stack = [''] for token in tokens: if token.type == 'EOF': while len(stack) > 1: stack.pop() yield base.Token(token.mark, 'DEDENT', None) if token.type == 'NEWLINE': yield base.Token(token.mark, 'NEWLINE', None) indent = token.value if indent != stack[-1]: if indent.startswith(stack[-1]): yield base.Token(token.mark, 'INDENT', None) stack.append(indent) elif stack[-1].startswith(indent): while (stack[-1] != indent and stack[-1].startswith(indent)): stack.pop() yield base.Token(token.mark, 'DEDENT', None) if indent != stack[-1]: raise base.Error([token.mark], 'Invalid indent') else: yield token
def _flatten(module_table): global_dict = {} for module in module_table.values(): for node in module.vars + module.funcs + module.clss: if '_' in node.name: raise base.Error( [node.mark], 'Global names with underscores are not yet supported') global_dict[node.name] = node return global_dict
def _get_all_globals_without_prelude(header: ast.Module, global_dict): if header.name not in global_dict['@modules']: for imp in header.imports: _get_all_globals_without_prelude(load_header(imp.module), global_dict) for node in header.vars + header.funcs + header.clss: if node.name in global_dict: raise base.Error( [node.mark, global_dict[node.name].mark], f'Duplicate definition of {node.name}', ) global_dict[node.name] = node global_dict['@modules'].add(header.name) return global_dict
def class_callback(m): native = m.value[0] class_name = m.value[2] declared_base = m.value[3] field_thunks = m.value[5] method_thunks = m.value[6] base = (ast.OBJECT if declared_base is None and class_name != ast.OBJECT else declared_base) scope = Scope(module_scope) scope['@class_name'] = class_name fields = tuple(ft(scope) for ft in field_thunks) methods = tuple(mt(scope) for mt in method_thunks) if validate: for method in methods: if native and method.body: raise base.Error( [m.mark, method.mark], 'Native classes cannot have method bodies', ) if not native and not method.body: raise base.Error( [m.mark, method.mark], 'Non-native classes cannot have native methods', ) if native and fields: raise base.Error( [m.mark, fields[0].mark], 'Native classes cannot have fields', ) return ast.Class( mark=m.mark, native=native, name=class_name, base=base, fields=fields, methods=methods, )
def module_callback(m): imports = [] vars = [] funcs = [] clss = [] for kind, node in m.value: if kind == 'import': imports.append(node) elif kind == 'var': vars.append(node) elif kind == 'func': funcs.append(node) elif kind == 'cls': clss.append(node) else: raise base.Error([node.mark], f'FUBAR: {kind}: {node}') return ast.Module( mark=m.mark, name=module_name, imports=tuple(imports), vars=tuple(vars), funcs=tuple(funcs), clss=tuple(clss), )
def ensure_global_exists(m): name = m.value if name not in global_dict: raise base.Error([m.mark], f'Name {repr(name)} does not exist') return name
def _make_source_parser(module_name: str, header: ast.Module, global_dict): def ensure_global_exists(m): name = m.value if name not in global_dict: raise base.Error([m.mark], f'Name {repr(name)} does not exist') return name import_map = _make_import_map( imports=header.imports, module_name=module_name, ) importable_id = _make_importable_id( module_name=module_name, import_map=import_map, ).fatmap(ensure_global_exists) exportable_id = _make_exportable_id( module_name=module_name, import_map=import_map, ).fatmap(ensure_global_exists) type_ = _make_type_parser( importable_id=importable_id, global_dict=global_dict, ) module_scope = { key: global_dict[val] for key, val in _prelude_table.items() } for imp in header.imports: if imp.name in global_dict: module_scope[imp.name] = global_dict[imp.name] else: raise base.Error( [imp.mark], f'{repr(imp.name)} is not defined', ) def get_func_def(scope, name, mark): if name not in scope: raise base.Error([mark], f'{name} is not defined') if not isinstance(scope[name], ast.Function): raise base.Error([scope[name].mark, mark], f'{name} is not a function') return scope[name] def fcall(mark, f, args): if len(f.params) != len(args): raise base.Error( [f.mark, mark], f'Expected {len(f.params)} args but got {len(args)}') for param, arg in zip(f.params, args): if not types.convertible(arg.type, param.type, global_dict): raise base.Error( [param.mark, arg.mark], f'Expected type {param.type} but got {arg.type}') return ast.FunctionCall(mark=mark, f=f, args=args) # TODO expression = Forward(lambda: atom) args = All( '(', expression.join(',').fatmap( lambda m: lambda scope: tuple(e(scope) for e in m.value)), Any(')').required(), ).map(lambda args: args[1]) atom = Any( Any('INT').fatmap(lambda m: lambda scope: ast.IntLiteral( mark=m.mark, value=m.value)), Any('STR').fatmap(lambda m: lambda scope: ast.StringLiteral( mark=m.mark, value=m.value, )), All('ID', args).fatmap( lambda m: lambda scope: fcall( mark=m.mark, f=get_func_def(scope=scope, name=m.value[0], mark=m.mark), args=m.value[1](scope), ), ), ) statement = Forward(lambda: Any( block, All('return', expression, ';').fatmap( lambda m: lambda scope: ast.Return( mark=m.mark, expr=m.value[1](scope), ), ), All(expression, ';').fatmap(lambda m: lambda scope: ast. ExpressionStatement( mark=m.mark, expr=m.value[0](scope), )), )) block = (All('{', statement.repeat(), Any('}').required()).fatmap(lambda m: lambda scope: ast.Block( mark=m.mark, stmts=tuple(stmt(scope) for stmt in m.value[1]), ))) return _make_combined_parser( module_name=module_name, importable_id=importable_id, exportable_id=exportable_id, type_=type_, expression=expression, block=block, global_dict=global_dict, module_scope=module_scope, )
def invalid_char_literal(m, mark): raise base.Error([mark], 'Multi-character char literal')