From e8eb4635408f6a106c7cf6afa59f5368f35f8bd6 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 11 May 2026 02:37:36 +0200 Subject: [PATCH] feat: add extern type / extern fn parsing and codegen (closes #42) extern was not a recognised keyword, causing a parse error at character 1 of any file containing extern declarations. Adds: - Token.EXTERN in lib/token.ml - ("extern", EXTERN) in the lexer keyword table (lib/lexer.ml) - TopExternType { et_name } and TopExternFn { ef_name; ef_params; ef_ret_ty } variants in the AST (lib/ast.ml) - extern_type_decl and extern_fn_decl parser rules in lib/parser.mly; both added to the top_level production - gen_decl cases in lib/codegen.ml: TopExternType is a no-op (opaque type for the type-checker); TopExternFn adds a Wasm import with module "env" and registers the name in func_indices so call sites resolve correctly Co-Authored-By: Claude Sonnet 4.6 --- lib/ast.ml | 8 ++++++++ lib/codegen.ml | 22 ++++++++++++++++++++++ lib/lexer.ml | 1 + lib/parser.mly | 14 +++++++++++++- lib/token.ml | 2 ++ 5 files changed, 46 insertions(+), 1 deletion(-) diff --git a/lib/ast.ml b/lib/ast.ml index 3ab2798..a25960c 100644 --- a/lib/ast.ml +++ b/lib/ast.ml @@ -367,6 +367,14 @@ type top_level = tc_ty : type_expr; tc_value : expr; } + | TopExternType of { + et_name : ident; + } + | TopExternFn of { + ef_name : ident; + ef_params : param list; + ef_ret_ty : type_expr option; + } [@@deriving show, eq] (** Complete program *) diff --git a/lib/codegen.ml b/lib/codegen.ml index 20bef89..9acdb1c 100644 --- a/lib/codegen.ml +++ b/lib/codegen.ml @@ -1871,6 +1871,28 @@ let gen_decl (ctx : context) (decl : top_level) : context result = (* These declarations don't generate code *) Ok ctx + | TopExternType _ -> + (* Opaque host type — no code generated; type is available to the type-checker *) + Ok ctx + + | TopExternFn ef -> + (* Add a WebAssembly import for the extern fn declaration. + Module name defaults to "env" (conventional host environment namespace). *) + let param_types = List.map (fun _ -> I32) ef.ef_params in + let result_types = match ef.ef_ret_ty with + | None -> [] + | Some _ -> [I32] + in + let func_type = { ft_params = param_types; ft_results = result_types } in + let type_idx = List.length ctx.types in + let ctx_with_type = { ctx with types = ctx.types @ [func_type] } in + let func_idx = import_func_count ctx_with_type in + let import_entry = { i_module = "env"; i_name = ef.ef_name.name; + i_desc = ImportFunc type_idx } in + Ok { ctx_with_type with + imports = ctx_with_type.imports @ [import_entry]; + func_indices = (ef.ef_name.name, func_idx) :: ctx_with_type.func_indices } + (** Generate WASM module from AffineScript program *) let generate_module (prog : program) : wasm_module result = let ctx = create_context () in diff --git a/lib/lexer.ml b/lib/lexer.ml index 445f859..cc4f120 100644 --- a/lib/lexer.ml +++ b/lib/lexer.ml @@ -41,6 +41,7 @@ let () = ("use", USE); ("pub", PUB); ("as", AS); + ("extern", EXTERN); ("unsafe", UNSAFE); ("assume", ASSUME); ("transmute", TRANSMUTE); diff --git a/lib/parser.mly b/lib/parser.mly index cb140b1..16346c4 100644 --- a/lib/parser.mly +++ b/lib/parser.mly @@ -39,7 +39,7 @@ let mk_ident name startpos endpos = %token FN LET CONST MUT OWN REF TYPE STRUCT ENUM TRAIT IMPL %token EFFECT HANDLE RESUME MATCH IF ELSE WHILE FOR %token RETURN BREAK CONTINUE IN WHERE TOTAL MODULE USE -%token PUB AS UNSAFE ASSUME TRANSMUTE FORGET TRY CATCH FINALLY +%token PUB AS EXTERN UNSAFE ASSUME TRANSMUTE FORGET TRY CATCH FINALLY /* Built-in types */ %token NAT INT_T BOOL FLOAT_T STRING_T CHAR_T TYPE_K ROW NEVER @@ -121,12 +121,24 @@ top_level: | tr = trait_decl { TopTrait tr } | i = impl_block { TopImpl i } | c = const_decl { c } + | e = extern_type_decl { e } + | e = extern_fn_decl { e } const_decl: | vis = visibility? CONST name = ident COLON ty = type_expr EQ value = expr SEMICOLON { TopConst { tc_vis = Option.value vis ~default:Private; tc_name = name; tc_ty = ty; tc_value = value } } +extern_type_decl: + | EXTERN TYPE name = upper_ident SEMICOLON + { TopExternType { et_name = name } } + +extern_fn_decl: + | EXTERN FN name = ident LPAREN params = separated_list(COMMA, param) RPAREN SEMICOLON + { TopExternFn { ef_name = name; ef_params = params; ef_ret_ty = None } } + | EXTERN FN name = ident LPAREN params = separated_list(COMMA, param) RPAREN ARROW ret = type_expr SEMICOLON + { TopExternFn { ef_name = name; ef_params = params; ef_ret_ty = Some ret } } + /* ========== Functions ========== */ fn_decl: diff --git a/lib/token.ml b/lib/token.ml index d36a009..993e69b 100644 --- a/lib/token.ml +++ b/lib/token.ml @@ -47,6 +47,7 @@ type t = | USE | PUB | AS + | EXTERN | UNSAFE | ASSUME | SELF_KW (** self receiver keyword *) @@ -164,6 +165,7 @@ let to_string = function | USE -> "use" | PUB -> "pub" | AS -> "as" + | EXTERN -> "extern" | UNSAFE -> "unsafe" | ASSUME -> "assume" | SELF_KW -> "self"