diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b47cb4e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "chat.tools.terminal.autoApprove": { + "dune": true, + "ocamlobjinfo": true + } +} \ No newline at end of file diff --git a/_build/.actions/default/test/runtest-test_spooky-95b7c3def6e74394d89edf8a77d85a3c b/_build/.actions/default/test/runtest-test_spooky-95b7c3def6e74394d89edf8a77d85a3c new file mode 100644 index 0000000..e69de29 diff --git a/_build/.db b/_build/.db index 7f2c7d1..d46c565 100644 Binary files a/_build/.db and b/_build/.db differ diff --git a/_build/.digest-db b/_build/.digest-db index f418ec5..cf2e0cf 100644 Binary files a/_build/.digest-db and b/_build/.digest-db differ diff --git a/_build/.sandbox/.hg/requires b/_build/.sandbox/.hg/requires new file mode 100644 index 0000000..387e851 --- /dev/null +++ b/_build/.sandbox/.hg/requires @@ -0,0 +1 @@ +Escaping the Dune sandbox \ No newline at end of file diff --git a/_build/default/bin/.main.eobjs/native/dune__exe__Main.cmx b/_build/default/bin/.main.eobjs/native/dune__exe__Main.cmx index 84565e8..c270bad 100644 Binary files a/_build/default/bin/.main.eobjs/native/dune__exe__Main.cmx and b/_build/default/bin/.main.eobjs/native/dune__exe__Main.cmx differ diff --git a/_build/default/bin/.main.eobjs/native/dune__exe__Main.o b/_build/default/bin/.main.eobjs/native/dune__exe__Main.o index c27ba2b..770f015 100644 Binary files a/_build/default/bin/.main.eobjs/native/dune__exe__Main.o and b/_build/default/bin/.main.eobjs/native/dune__exe__Main.o differ diff --git a/_build/default/bin/.merlin-conf/exe-main b/_build/default/bin/.merlin-conf/exe-main index bf115bf..9e715f3 100644 Binary files a/_build/default/bin/.merlin-conf/exe-main and b/_build/default/bin/.merlin-conf/exe-main differ diff --git a/_build/default/bin/main.exe b/_build/default/bin/main.exe index bb7ffa0..557db63 100755 Binary files a/_build/default/bin/main.exe and b/_build/default/bin/main.exe differ diff --git a/_build/default/bin/main.ml b/_build/default/bin/main.ml index 7bf6048..4b4feb7 100644 --- a/_build/default/bin/main.ml +++ b/_build/default/bin/main.ml @@ -1 +1,34 @@ -let () = print_endline "Hello, World!" +let sample_program = + {| +struct Point { + int x; + int y; +}; + +int sum_array(int[] arr) { + int total = 0; + foreach (int n in arr) { + total = total + n; + } + return total; +} + +int main() { + struct Point p; + int[] nums; + int x = 1 + 2 * 3; + p.x = x; + if (x > 0) { + x = x + p.x; + } else { + x = 0; + } + x = sum_array(nums); + return x; +} +|} + +let () = + match Spooky.parse_and_type_check sample_program with + | Ok _ -> print_endline "Program parsed and type-checked successfully." + | Error msg -> Printf.printf "Error: %s\n" msg diff --git a/_build/default/lib/.merlin-conf/lib-spooky b/_build/default/lib/.merlin-conf/lib-spooky index 308fa3c..473155d 100644 Binary files a/_build/default/lib/.merlin-conf/lib-spooky and b/_build/default/lib/.merlin-conf/lib-spooky differ diff --git a/_build/default/lib/.spooky.objs/byte/spooky.cmi b/_build/default/lib/.spooky.objs/byte/spooky.cmi index 46e2539..cbcd5c7 100644 Binary files a/_build/default/lib/.spooky.objs/byte/spooky.cmi and b/_build/default/lib/.spooky.objs/byte/spooky.cmi differ diff --git a/_build/default/lib/.spooky.objs/byte/spooky.cmo b/_build/default/lib/.spooky.objs/byte/spooky.cmo index b0b708a..f00b4a7 100644 Binary files a/_build/default/lib/.spooky.objs/byte/spooky.cmo and b/_build/default/lib/.spooky.objs/byte/spooky.cmo differ diff --git a/_build/default/lib/.spooky.objs/byte/spooky.cmt b/_build/default/lib/.spooky.objs/byte/spooky.cmt index 1184b5f..5d256e4 100644 Binary files a/_build/default/lib/.spooky.objs/byte/spooky.cmt and b/_build/default/lib/.spooky.objs/byte/spooky.cmt differ diff --git a/_build/default/lib/.spooky.objs/byte/spooky.cmti b/_build/default/lib/.spooky.objs/byte/spooky.cmti new file mode 100644 index 0000000..4d7abac Binary files /dev/null and b/_build/default/lib/.spooky.objs/byte/spooky.cmti differ diff --git a/_build/default/lib/.spooky.objs/native/spooky.cmx b/_build/default/lib/.spooky.objs/native/spooky.cmx index 25d7d13..f867437 100644 Binary files a/_build/default/lib/.spooky.objs/native/spooky.cmx and b/_build/default/lib/.spooky.objs/native/spooky.cmx differ diff --git a/_build/default/lib/.spooky.objs/native/spooky.o b/_build/default/lib/.spooky.objs/native/spooky.o index 89da11d..d07c845 100644 Binary files a/_build/default/lib/.spooky.objs/native/spooky.o and b/_build/default/lib/.spooky.objs/native/spooky.o differ diff --git a/_build/default/lib/spooky.a b/_build/default/lib/spooky.a index cdec4df..3cabf78 100644 Binary files a/_build/default/lib/spooky.a and b/_build/default/lib/spooky.a differ diff --git a/_build/default/lib/spooky.cma b/_build/default/lib/spooky.cma index 17303e3..b622c3d 100644 Binary files a/_build/default/lib/spooky.cma and b/_build/default/lib/spooky.cma differ diff --git a/_build/default/lib/spooky.cmxa b/_build/default/lib/spooky.cmxa index 80765ca..481bdbd 100644 Binary files a/_build/default/lib/spooky.cmxa and b/_build/default/lib/spooky.cmxa differ diff --git a/_build/default/lib/spooky.cmxs b/_build/default/lib/spooky.cmxs index e22f9bd..8bdc305 100755 Binary files a/_build/default/lib/spooky.cmxs and b/_build/default/lib/spooky.cmxs differ diff --git a/_build/default/lib/spooky.ml b/_build/default/lib/spooky.ml new file mode 100644 index 0000000..dd8a47a --- /dev/null +++ b/_build/default/lib/spooky.ml @@ -0,0 +1,806 @@ +module StringMap = Map.Make (String) + +exception Parse_error of string +exception Type_error of string + +type typ = + | TInt + | TBool + | TVoid + | TStruct of string + | TArray of typ + +type binop = + | Add + | Sub + | Mul + | Div + | Mod + | And + | Or + | Eq + | Ne + | Lt + | Le + | Gt + | Ge + +type unop = Neg | Not + +type expr = + | IntLit of int + | BoolLit of bool + | Var of string + | Binop of binop * expr * expr + | Unop of unop * expr + | Assign of expr * expr + | Call of expr * expr list + | ArrayGet of expr * expr + | StructGet of expr * string + +type stmt = + | VarDecl of typ * string * expr option + | Expr of expr + | If of expr * stmt list * stmt list + | ForEach of typ * string * expr * stmt list + | Return of expr option + | Block of stmt list + +type func = { + name : string; + params : (typ * string) list; + ret : typ; + body : stmt list; +} + +type struct_def = { + sname : string; + fields : (typ * string) list; +} + +type top = + | TopStruct of struct_def + | TopFunc of func + | TopGlobalVar of typ * string * expr option + +type program = top list + +let string_of_typ = + let rec go = function + | TInt -> "int" + | TBool -> "bool" + | TVoid -> "void" + | TStruct n -> "struct " ^ n + | TArray t -> go t ^ "[]" + in + go + +let rec equal_typ a b = + match (a, b) with + | TInt, TInt | TBool, TBool | TVoid, TVoid -> true + | TStruct x, TStruct y -> String.equal x y + | TArray x, TArray y -> equal_typ x y + | _ -> false + +type token = + | TIntKw + | TBoolKw + | TVoidKw + | TStructKw + | TIf + | TElse + | TFor + | TEach + | TForEach + | TIn + | TReturn + | TTrue + | TFalse + | TIdent of string + | TIntLit of int + | TLParen + | TRParen + | TLBrace + | TRBrace + | TLBracket + | TRBracket + | TSemicolon + | TComma + | TDot + | TAssign + | TPlus + | TMinus + | TStar + | TSlash + | TPercent + | TAndAnd + | TOrOr + | TBang + | TEqEq + | TNe + | TLt + | TLe + | TGt + | TGe + | TEOF + +let is_space = function ' ' | '\t' | '\r' | '\n' -> true | _ -> false + +let is_digit c = c >= '0' && c <= '9' + +let is_ident_start c = + (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c = '_' + +let is_ident_char c = is_ident_start c || is_digit c + +let keyword_or_ident s = + match s with + | "int" -> TIntKw + | "bool" -> TBoolKw + | "void" -> TVoidKw + | "struct" -> TStructKw + | "if" -> TIf + | "else" -> TElse + | "for" -> TFor + | "each" -> TEach + | "foreach" -> TForEach + | "in" -> TIn + | "return" -> TReturn + | "true" -> TTrue + | "false" -> TFalse + | _ -> TIdent s + +let lex (src : string) : token list = + let n = String.length src in + let rec skip_line_comment i = + if i >= n then i + else if src.[i] = '\n' then i + 1 + else skip_line_comment (i + 1) + in + let rec skip_block_comment i = + if i + 1 >= n then raise (Parse_error "unterminated block comment") + else if src.[i] = '*' && src.[i + 1] = '/' then i + 2 + else skip_block_comment (i + 1) + in + let rec read_number i j = + if j < n && is_digit src.[j] then read_number i (j + 1) + else + let s = String.sub src i (j - i) in + (TIntLit (int_of_string s), j) + in + let rec read_ident i j = + if j < n && is_ident_char src.[j] then read_ident i (j + 1) + else + let s = String.sub src i (j - i) in + (keyword_or_ident s, j) + in + let rec loop i acc = + if i >= n then List.rev (TEOF :: acc) + else if is_space src.[i] then loop (i + 1) acc + else + match src.[i] with + | '/' when i + 1 < n && src.[i + 1] = '/' -> loop (skip_line_comment (i + 2)) acc + | '/' when i + 1 < n && src.[i + 1] = '*' -> loop (skip_block_comment (i + 2)) acc + | '(' -> loop (i + 1) (TLParen :: acc) + | ')' -> loop (i + 1) (TRParen :: acc) + | '{' -> loop (i + 1) (TLBrace :: acc) + | '}' -> loop (i + 1) (TRBrace :: acc) + | '[' -> loop (i + 1) (TLBracket :: acc) + | ']' -> loop (i + 1) (TRBracket :: acc) + | ';' -> loop (i + 1) (TSemicolon :: acc) + | ',' -> loop (i + 1) (TComma :: acc) + | '.' -> loop (i + 1) (TDot :: acc) + | '+' -> loop (i + 1) (TPlus :: acc) + | '-' -> loop (i + 1) (TMinus :: acc) + | '*' -> loop (i + 1) (TStar :: acc) + | '%' -> loop (i + 1) (TPercent :: acc) + | '/' -> loop (i + 1) (TSlash :: acc) + | '!' when i + 1 < n && src.[i + 1] = '=' -> loop (i + 2) (TNe :: acc) + | '!' -> loop (i + 1) (TBang :: acc) + | '=' when i + 1 < n && src.[i + 1] = '=' -> loop (i + 2) (TEqEq :: acc) + | '=' -> loop (i + 1) (TAssign :: acc) + | '&' when i + 1 < n && src.[i + 1] = '&' -> loop (i + 2) (TAndAnd :: acc) + | '|' when i + 1 < n && src.[i + 1] = '|' -> loop (i + 2) (TOrOr :: acc) + | '<' when i + 1 < n && src.[i + 1] = '=' -> loop (i + 2) (TLe :: acc) + | '<' -> loop (i + 1) (TLt :: acc) + | '>' when i + 1 < n && src.[i + 1] = '=' -> loop (i + 2) (TGe :: acc) + | '>' -> loop (i + 1) (TGt :: acc) + | c when is_digit c -> + let tok, j = read_number i (i + 1) in + loop j (tok :: acc) + | c when is_ident_start c -> + let tok, j = read_ident i (i + 1) in + loop j (tok :: acc) + | c -> + let msg = Printf.sprintf "unexpected character: %c" c in + raise (Parse_error msg) + in + loop 0 [] + +type parser_state = { + toks : token array; + mutable i : int; +} + +let mk_state toks = { toks = Array.of_list toks; i = 0 } + +let peek st = if st.i < Array.length st.toks then st.toks.(st.i) else TEOF + +let consume st = + let t = peek st in + st.i <- st.i + 1; + t + +let expect st tok = + match (tok, consume st) with + | TLParen, TLParen + | TRParen, TRParen + | TLBrace, TLBrace + | TRBrace, TRBrace + | TLBracket, TLBracket + | TRBracket, TRBracket + | TSemicolon, TSemicolon + | TComma, TComma + | TDot, TDot + | TAssign, TAssign + | TPlus, TPlus + | TMinus, TMinus + | TStar, TStar + | TSlash, TSlash + | TPercent, TPercent + | TAndAnd, TAndAnd + | TOrOr, TOrOr + | TBang, TBang + | TEqEq, TEqEq + | TNe, TNe + | TLt, TLt + | TLe, TLe + | TGt, TGt + | TGe, TGe + | TIf, TIf + | TElse, TElse + | TForEach, TForEach + | TFor, TFor + | TEach, TEach + | TIn, TIn + | TReturn, TReturn + | TIntKw, TIntKw + | TBoolKw, TBoolKw + | TVoidKw, TVoidKw + | TStructKw, TStructKw + | TEOF, TEOF -> () + | _ -> raise (Parse_error "unexpected token") + +let expect_ident st = + match consume st with + | TIdent s -> s + | _ -> raise (Parse_error "expected identifier") + +let starts_type = function TIntKw | TBoolKw | TVoidKw | TStructKw -> true | _ -> false + +let rec parse_type st = + let base = + match consume st with + | TIntKw -> TInt + | TBoolKw -> TBool + | TVoidKw -> TVoid + | TStructKw -> TStruct (expect_ident st) + | _ -> raise (Parse_error "expected type") + in + let rec arrays t = + match peek st with + | TLBracket -> + expect st TLBracket; + expect st TRBracket; + arrays (TArray t) + | _ -> t + in + arrays base + +let rec parse_program st = + let rec loop acc = + match peek st with + | TEOF -> List.rev acc + | _ -> loop (parse_top st :: acc) + in + loop [] + +and parse_top st = + match peek st with + | TStructKw -> + expect st TStructKw; + let sname = expect_ident st in + (match peek st with + | TLBrace -> + expect st TLBrace; + let rec fields acc = + match peek st with + | TRBrace -> List.rev acc + | _ -> + let t = parse_type st in + let n = expect_ident st in + expect st TSemicolon; + fields ((t, n) :: acc) + in + let fs = fields [] in + expect st TRBrace; + expect st TSemicolon; + TopStruct { sname; fields = fs } + | _ -> + let ty = TStruct sname in + parse_top_after_type st ty) + | _ -> + let ty = parse_type st in + parse_top_after_type st ty + +and parse_top_after_type st ty = + let name = expect_ident st in + match peek st with + | TLParen -> + expect st TLParen; + let params = parse_params st in + expect st TRParen; + let body = parse_stmt_as_block st in + TopFunc { name; params; ret = ty; body } + | _ -> + let init = + match peek st with + | TAssign -> + expect st TAssign; + Some (parse_expr st) + | _ -> None + in + expect st TSemicolon; + TopGlobalVar (ty, name, init) + +and parse_params st = + match peek st with + | TRParen -> [] + | _ -> + let rec loop acc = + let t = parse_type st in + let n = expect_ident st in + match peek st with + | TComma -> + expect st TComma; + loop ((t, n) :: acc) + | _ -> List.rev ((t, n) :: acc) + in + loop [] + +and parse_stmt_as_block st = + match peek st with + | TLBrace -> + expect st TLBrace; + let rec loop acc = + match peek st with + | TRBrace -> + expect st TRBrace; + List.rev acc + | _ -> loop (parse_stmt st :: acc) + in + loop [] + | _ -> [ parse_stmt st ] + +and parse_stmt st = + match peek st with + | TLBrace -> Block (parse_stmt_as_block st) + | TIf -> + expect st TIf; + expect st TLParen; + let cond = parse_expr st in + expect st TRParen; + let then_body = parse_stmt_as_block st in + let else_body = + match peek st with + | TElse -> + expect st TElse; + parse_stmt_as_block st + | _ -> [] + in + If (cond, then_body, else_body) + | TForEach | TFor -> + (match peek st with + | TForEach -> expect st TForEach + | TFor -> + expect st TFor; + expect st TEach + | _ -> ()); + expect st TLParen; + let it_t = parse_type st in + let it_name = expect_ident st in + expect st TIn; + let iterable = parse_expr st in + expect st TRParen; + let body = parse_stmt_as_block st in + ForEach (it_t, it_name, iterable, body) + | TReturn -> + expect st TReturn; + let v = + match peek st with + | TSemicolon -> None + | _ -> Some (parse_expr st) + in + expect st TSemicolon; + Return v + | t when starts_type t -> + let ty = parse_type st in + let n = expect_ident st in + let init = + match peek st with + | TAssign -> + expect st TAssign; + Some (parse_expr st) + | _ -> None + in + expect st TSemicolon; + VarDecl (ty, n, init) + | _ -> + let e = parse_expr st in + expect st TSemicolon; + Expr e + +and parse_expr st = parse_assignment st + +and parse_assignment st = + let lhs = parse_or st in + match peek st with + | TAssign -> + expect st TAssign; + let rhs = parse_assignment st in + Assign (lhs, rhs) + | _ -> lhs + +and parse_or st = + let rec loop left = + match peek st with + | TOrOr -> + expect st TOrOr; + loop (Binop (Or, left, parse_and st)) + | _ -> left + in + loop (parse_and st) + +and parse_and st = + let rec loop left = + match peek st with + | TAndAnd -> + expect st TAndAnd; + loop (Binop (And, left, parse_equality st)) + | _ -> left + in + loop (parse_equality st) + +and parse_equality st = + let rec loop left = + match peek st with + | TEqEq -> + expect st TEqEq; + loop (Binop (Eq, left, parse_rel st)) + | TNe -> + expect st TNe; + loop (Binop (Ne, left, parse_rel st)) + | _ -> left + in + loop (parse_rel st) + +and parse_rel st = + let rec loop left = + match peek st with + | TLt -> + expect st TLt; + loop (Binop (Lt, left, parse_add st)) + | TLe -> + expect st TLe; + loop (Binop (Le, left, parse_add st)) + | TGt -> + expect st TGt; + loop (Binop (Gt, left, parse_add st)) + | TGe -> + expect st TGe; + loop (Binop (Ge, left, parse_add st)) + | _ -> left + in + loop (parse_add st) + +and parse_add st = + let rec loop left = + match peek st with + | TPlus -> + expect st TPlus; + loop (Binop (Add, left, parse_mul st)) + | TMinus -> + expect st TMinus; + loop (Binop (Sub, left, parse_mul st)) + | _ -> left + in + loop (parse_mul st) + +and parse_mul st = + let rec loop left = + match peek st with + | TStar -> + expect st TStar; + loop (Binop (Mul, left, parse_unary st)) + | TSlash -> + expect st TSlash; + loop (Binop (Div, left, parse_unary st)) + | TPercent -> + expect st TPercent; + loop (Binop (Mod, left, parse_unary st)) + | _ -> left + in + loop (parse_unary st) + +and parse_unary st = + match peek st with + | TMinus -> + expect st TMinus; + Unop (Neg, parse_unary st) + | TBang -> + expect st TBang; + Unop (Not, parse_unary st) + | _ -> parse_postfix st + +and parse_postfix st = + let rec loop e = + match peek st with + | TLParen -> + expect st TLParen; + let args = parse_args st in + expect st TRParen; + loop (Call (e, args)) + | TLBracket -> + expect st TLBracket; + let idx = parse_expr st in + expect st TRBracket; + loop (ArrayGet (e, idx)) + | TDot -> + expect st TDot; + let fld = expect_ident st in + loop (StructGet (e, fld)) + | _ -> e + in + loop (parse_primary st) + +and parse_args st = + match peek st with + | TRParen -> [] + | _ -> + let rec loop acc = + let e = parse_expr st in + match peek st with + | TComma -> + expect st TComma; + loop (e :: acc) + | _ -> List.rev (e :: acc) + in + loop [] + +and parse_primary st = + match consume st with + | TIntLit n -> IntLit n + | TTrue -> BoolLit true + | TFalse -> BoolLit false + | TIdent s -> Var s + | TLParen -> + let e = parse_expr st in + expect st TRParen; + e + | _ -> raise (Parse_error "expected expression") + +let parse_string src = + try + let st = mk_state (lex src) in + Ok (parse_program st) + with Parse_error msg -> Error msg + +type func_sig = { + fparams : typ list; + fret : typ; +} + +type tc_ctx = { + structs : (typ StringMap.t) StringMap.t; + funcs : func_sig StringMap.t; + globals : typ StringMap.t; +} + +let fail_type msg = raise (Type_error msg) + +let expect_type got want = + if not (equal_typ got want) then + fail_type (Printf.sprintf "type mismatch: got %s, expected %s" (string_of_typ got) (string_of_typ want)) + +let rec validate_type (structs : (typ StringMap.t) StringMap.t) (allow_void : bool) = function + | TVoid when allow_void -> () + | TVoid -> fail_type "void is not a valid variable type" + | TStruct n -> + if not (StringMap.mem n structs) then fail_type ("unknown struct type: " ^ n) + | TArray t -> + if equal_typ t TVoid then fail_type "array element type cannot be void"; + validate_type structs false t + | TInt | TBool -> () + +let collect_ctx (prog : program) : tc_ctx = + let rec collect tops structs funcs globals = + match tops with + | [] -> { structs; funcs; globals } + | TopStruct s :: tl -> + if StringMap.mem s.sname structs then fail_type ("duplicate struct: " ^ s.sname); + let fields = + List.fold_left + (fun acc (t, n) -> + if StringMap.mem n acc then fail_type ("duplicate field " ^ n ^ " in struct " ^ s.sname); + StringMap.add n t acc) + StringMap.empty s.fields + in + collect tl (StringMap.add s.sname fields structs) funcs globals + | TopFunc f :: tl -> + if StringMap.mem f.name funcs then fail_type ("duplicate function: " ^ f.name); + let sig_ = { fparams = List.map fst f.params; fret = f.ret } in + collect tl structs (StringMap.add f.name sig_ funcs) globals + | TopGlobalVar (t, n, _) :: tl -> + if StringMap.mem n globals then fail_type ("duplicate global variable: " ^ n); + collect tl structs funcs (StringMap.add n t globals) + in + collect prog StringMap.empty StringMap.empty StringMap.empty + +let lookup_var env x = + match StringMap.find_opt x env with + | Some t -> t + | None -> fail_type ("unknown variable: " ^ x) + +let lookup_struct_field ctx sname fname = + match StringMap.find_opt sname ctx.structs with + | None -> fail_type ("unknown struct: " ^ sname) + | Some fields -> + (match StringMap.find_opt fname fields with + | None -> fail_type ("unknown field " ^ fname ^ " on struct " ^ sname) + | Some t -> t) + +let rec infer_expr ctx env = function + | IntLit _ -> TInt + | BoolLit _ -> TBool + | Var x -> lookup_var env x + | Unop (Neg, e) -> + expect_type (infer_expr ctx env e) TInt; + TInt + | Unop (Not, e) -> + expect_type (infer_expr ctx env e) TBool; + TBool + | Binop (op, a, b) -> + let ta = infer_expr ctx env a in + let tb = infer_expr ctx env b in + (match op with + | Add | Sub | Mul | Div | Mod -> + expect_type ta TInt; + expect_type tb TInt; + TInt + | And | Or -> + expect_type ta TBool; + expect_type tb TBool; + TBool + | Lt | Le | Gt | Ge -> + expect_type ta TInt; + expect_type tb TInt; + TBool + | Eq | Ne -> + if not (equal_typ ta tb) then fail_type "equality operands must have same type"; + TBool) + | Assign (lhs, rhs) -> + (match lhs with Var _ | ArrayGet _ | StructGet _ -> () | _ -> fail_type "left side of assignment is not assignable"); + let tl = infer_expr ctx env lhs in + let tr = infer_expr ctx env rhs in + expect_type tr tl; + tl + | ArrayGet (arr, idx) -> + expect_type (infer_expr ctx env idx) TInt; + (match infer_expr ctx env arr with + | TArray t -> t + | t -> fail_type ("indexing requires array, got " ^ string_of_typ t)) + | StructGet (obj, fld) -> + (match infer_expr ctx env obj with + | TStruct sname -> lookup_struct_field ctx sname fld + | t -> fail_type ("field access requires struct, got " ^ string_of_typ t)) + | Call (callee, args) -> + let fname = + match callee with + | Var n -> n + | _ -> fail_type "only direct function calls are supported" + in + let sig_ = + match StringMap.find_opt fname ctx.funcs with + | Some s -> s + | None -> fail_type ("unknown function: " ^ fname) + in + if List.length args <> List.length sig_.fparams then + fail_type + (Printf.sprintf "function %s expects %d arguments, got %d" fname (List.length sig_.fparams) + (List.length args)); + List.iter2 (fun arg pty -> expect_type (infer_expr ctx env arg) pty) args sig_.fparams; + sig_.fret + +let rec check_stmt ctx ret env = function + | VarDecl (t, n, init) -> + validate_type ctx.structs false t; + (match init with None -> () | Some e -> expect_type (infer_expr ctx env e) t); + StringMap.add n t env + | Expr e -> + ignore (infer_expr ctx env e); + env + | If (cond, tbranch, ebranch) -> + expect_type (infer_expr ctx env cond) TBool; + ignore (check_block ctx ret env tbranch); + ignore (check_block ctx ret env ebranch); + env + | ForEach (it_t, it_name, iterable, body) -> + validate_type ctx.structs false it_t; + (match infer_expr ctx env iterable with + | TArray elem_t -> expect_type elem_t it_t + | t -> fail_type ("foreach expects array iterable, got " ^ string_of_typ t)); + let env' = StringMap.add it_name it_t env in + ignore (check_block ctx ret env' body); + env + | Return None -> + expect_type TVoid ret; + env + | Return (Some e) -> + expect_type (infer_expr ctx env e) ret; + env + | Block stmts -> + ignore (check_block ctx ret env stmts); + env + +and check_block ctx ret env stmts = List.fold_left (check_stmt ctx ret) env stmts + +let rec has_return_stmt = function + | Return _ -> true + | If (_, t, e) -> List.exists has_return_stmt t || List.exists has_return_stmt e + | ForEach (_, _, _, body) | Block body -> List.exists has_return_stmt body + | VarDecl _ | Expr _ -> false + +let check_program (prog : program) = + let ctx = collect_ctx prog in + StringMap.iter (fun _ t -> validate_type ctx.structs false t) ctx.globals; + StringMap.iter + (fun _ sig_ -> + List.iter (validate_type ctx.structs false) sig_.fparams; + validate_type ctx.structs true sig_.fret) + ctx.funcs; + List.iter + (function + | TopStruct s -> + List.iter (fun (t, _) -> validate_type ctx.structs false t) s.fields + | TopGlobalVar (t, _, init) -> + validate_type ctx.structs false t; + let env = ctx.globals in + (match init with None -> () | Some e -> expect_type (infer_expr ctx env e) t) + | TopFunc f -> + let env_with_globals = ctx.globals in + let env = + List.fold_left + (fun acc (t, n) -> + validate_type ctx.structs false t; + if StringMap.mem n acc then fail_type ("duplicate parameter name: " ^ n); + StringMap.add n t acc) + env_with_globals f.params + in + ignore (check_block ctx f.ret env f.body); + if (not (equal_typ f.ret TVoid)) && not (List.exists has_return_stmt f.body) then + fail_type ("non-void function " ^ f.name ^ " must return a value")) + prog + +let type_check (prog : program) = + try + check_program prog; + Ok () + with Type_error msg -> Error msg + +let parse_and_type_check src = + match parse_string src with + | Error e -> Error ("Parse error: " ^ e) + | Ok prog -> + (match type_check prog with + | Error e -> Error ("Type error: " ^ e) + | Ok () -> Ok prog) diff --git a/_build/default/lib/spooky.ml-gen b/_build/default/lib/spooky.ml-gen deleted file mode 100644 index 71d4a7c..0000000 --- a/_build/default/lib/spooky.ml-gen +++ /dev/null @@ -1 +0,0 @@ -(* generated by dune *) diff --git a/_build/default/lib/spooky.mli b/_build/default/lib/spooky.mli new file mode 100644 index 0000000..a62ed34 --- /dev/null +++ b/_build/default/lib/spooky.mli @@ -0,0 +1,66 @@ +type typ = + | TInt + | TBool + | TVoid + | TStruct of string + | TArray of typ + +type binop = + | Add + | Sub + | Mul + | Div + | Mod + | And + | Or + | Eq + | Ne + | Lt + | Le + | Gt + | Ge + +type unop = Neg | Not + +type expr = + | IntLit of int + | BoolLit of bool + | Var of string + | Binop of binop * expr * expr + | Unop of unop * expr + | Assign of expr * expr + | Call of expr * expr list + | ArrayGet of expr * expr + | StructGet of expr * string + +type stmt = + | VarDecl of typ * string * expr option + | Expr of expr + | If of expr * stmt list * stmt list + | ForEach of typ * string * expr * stmt list + | Return of expr option + | Block of stmt list + +type func = { + name : string; + params : (typ * string) list; + ret : typ; + body : stmt list; +} + +type struct_def = { + sname : string; + fields : (typ * string) list; +} + +type top = + | TopStruct of struct_def + | TopFunc of func + | TopGlobalVar of typ * string * expr option + +type program = top list + +val string_of_typ : typ -> string +val parse_string : string -> (program, string) result +val type_check : program -> (unit, string) result +val parse_and_type_check : string -> (program, string) result diff --git a/_build/default/test/.merlin-conf/exe-test_spooky b/_build/default/test/.merlin-conf/exe-test_spooky index b7e2c14..a84ed15 100644 Binary files a/_build/default/test/.merlin-conf/exe-test_spooky and b/_build/default/test/.merlin-conf/exe-test_spooky differ diff --git a/_build/default/test/.test_spooky.eobjs/native/dune__exe__Test_spooky.cmx b/_build/default/test/.test_spooky.eobjs/native/dune__exe__Test_spooky.cmx index aca7f20..2994701 100644 Binary files a/_build/default/test/.test_spooky.eobjs/native/dune__exe__Test_spooky.cmx and b/_build/default/test/.test_spooky.eobjs/native/dune__exe__Test_spooky.cmx differ diff --git a/_build/default/test/.test_spooky.eobjs/native/dune__exe__Test_spooky.o b/_build/default/test/.test_spooky.eobjs/native/dune__exe__Test_spooky.o index f02ef86..1a5ed76 100644 Binary files a/_build/default/test/.test_spooky.eobjs/native/dune__exe__Test_spooky.o and b/_build/default/test/.test_spooky.eobjs/native/dune__exe__Test_spooky.o differ diff --git a/_build/default/test/test_spooky.exe b/_build/default/test/test_spooky.exe index 331ad3d..8ff9ec4 100755 Binary files a/_build/default/test/test_spooky.exe and b/_build/default/test/test_spooky.exe differ diff --git a/_build/default/test/test_spooky.ml b/_build/default/test/test_spooky.ml index e69de29..37c3639 100644 --- a/_build/default/test/test_spooky.ml +++ b/_build/default/test/test_spooky.ml @@ -0,0 +1,52 @@ +let valid_program = + {| +struct Item { + int value; +}; + +int fold(int[] xs) { + int total = 0; + foreach (int x in xs) { + total = total + x; + } + return total; +} + +int main() { + int[] xs; + struct Item it; + int y = 2 + 3 * 4; + it.value = y; + if (y >= 0) { + y = fold(xs); + } else { + y = 0; + } + return y; +} +|} + +let invalid_program = + {| +int main() { + bool flag = true; + int x = 1; + x = flag; + return x; +} +|} + +let test_valid_program () = + match Spooky.parse_and_type_check valid_program with + | Ok _ -> () + | Error msg -> failwith ("expected valid program, got: " ^ msg) + +let test_invalid_program () = + match Spooky.parse_and_type_check invalid_program with + | Ok _ -> failwith "expected type error, but got success" + | Error _ -> () + +let () = + test_valid_program (); + test_invalid_program (); + print_endline "All parser/type-check tests passed." diff --git a/_build/trace.csexp b/_build/trace.csexp index fd0719d..3591f11 100644 --- a/_build/trace.csexp +++ b/_build/trace.csexp @@ -1 +1 @@ -(6:config4:init19:1777395035280731177(7:version6:3.22.2)(9:build_dir6:_build)(4:argv(4:dune5:build))(3:env(140:CAML_LD_LIBRARY_PATH=/home/steve/.opam/default/lib/stublibs:/home/steve/.opam/default/lib/ocaml/stublibs:/home/steve/.opam/default/lib/ocaml11:VISUAL=nvim80:OPAM_LAST_ENV=/home/steve/.opam/.last-env/env-550cde22b571c5ee951c5aa1091061dc-037:STARSHIP_SESSION_KEY=294282196542446219:TERM_PROGRAM=vscode28:MPS_JDK=/opt/MPS 2021.3/jbr/70:GK_GL_PATH=/tmp/gitkraken/gitlens/gitlens-ipc-server-390794-40789.json34:SSH_CLIENT=84.115.232.107 53704 2219:SYSTEMD_EDITOR=nvim136:GIT_ASKPASS=/home/steve/.vscode-server/cli/servers/Stable-10c8e557c8b9f9ed0a87f61f1c9a44bde731c409/server/extensions/git/dist/askpass.sh17:XDG_SESSION_ID=1920:XDG_SESSION_TYPE=tty7:SHLVL=387:VSCODE_IPC_HOOK_CLI=/run/user/1000/vscode-ipc-47f613c2-5cef-4cd9-a5e9-ced8e2c4d44b.sock124:BROWSER=/home/steve/.vscode-server/cli/servers/Stable-10c8e557c8b9f9ed0a87f61f1c9a44bde731c409/server/bin/helpers/browser.sh18:DIRENV_LOG_FORMAT=65:FZF_DEFAULT_COMMAND=rg --files --hidden --follow --glob "!.git/*"22:XDG_SESSION_CLASS=user19:TERM=xterm-256color30:XDG_RUNTIME_DIR=/run/user/100013:LOGNAME=steve122:VSCODE_GIT_ASKPASS_NODE=/home/steve/.vscode-server/cli/servers/Stable-10c8e557c8b9f9ed0a87f61f1c9a44bde731c409/server/node19:COLORTERM=truecolor37:PWD=/home/steve/projects/ocaml/spooky64:XDG_DATA_DIRS=/usr/local/share:/usr/share:/var/lib/snapd/desktop31:FLYCTL_INSTALL=/home/steve/.fly19:STARSHIP_SHELL=fish44:OPAM_SWITCH_PREFIX=/home/steve/.opam/default60:OCAMLTOP_INCLUDE_PATH=/home/steve/.opam/default/lib/toplevel52:SSH_CONNECTION=84.115.232.107 53704 65.21.205.199 2253:DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus11:EDITOR=nvim63:VSCODE_GIT_IPC_HANDLE=/run/user/1000/vscode-git-7fc91d943b.sock19:SHELL=/usr/bin/fish34:VSCODE_PYTHON_AUTOACTIVATE_GUARD=116:HOME=/home/steve153:VSCODE_GIT_ASKPASS_MAIN=/home/steve/.vscode-server/cli/servers/Stable-10c8e557c8b9f9ed0a87f61f1c9a44bde731c409/server/extensions/git/dist/askpass-main.js28:TERM_PROGRAM_VERSION=1.117.033:OLDPWD=/home/steve/.vscode-server33:ANDROID_NDK_HOME=/opt/android-ndk16:LANG=en_US.UTF-830:VSCODE_GIT_ASKPASS_EXTRA_ARGS=20:OPAMNOENVNOTICE=true2511:PATH=/home/steve/.opam/default/bin:/home/steve/.vscode-server/extensions/vadimcn.vscode-lldb-1.12.1/bin:/home/steve/.vscode-server/data/User/globalStorage/github.copilot-chat/debugCommand:/home/steve/.vscode-server/data/User/globalStorage/github.copilot-chat/copilotCli:/home/steve/.vscode-server/data/User/globalStorage/github.copilot-chat/debugCommand:/home/steve/.vscode-server/data/User/globalStorage/github.copilot-chat/copilotCli:/home/steve/.vscode-server/extensions/vadimcn.vscode-lldb-1.12.1/bin:/home/steve/.vscode-server/cli/servers/Stable-10c8e557c8b9f9ed0a87f61f1c9a44bde731c409/server/bin/remote-cli:/usr/local/sbin:/usr/local/bin:/usr/bin:/opt/riscv/bin:/home/steve/.npm/bin:/home/steve/.cargo/bin:/home/steve/scripts:/home/steve/bin:/home/steve/go/bin:/usr/local/go/bin:/home/steve/.fluvio/bin:/home/steve/.local/share/ponyup/bin:/home/steve/NuSMV-2.6.0-Linux/bin/:/home/steve/.local/bin:/home/steve/.vino/bin:/home/steve/projects/odin/Odin:/home/steve/j90x/bin:/home/steve/opt/GNAT/2021/bin:/home/steve/opt/GNAT/2021-arm-elf/bin:/home/steve/opt/GNAT/arm-gnueabihf//bin:/home/steve/.fly/bin:/home/steve/.platformio/penv/bin:/home/steve/.ghcup/bin/:/home/steve/.wasmer/bin/:/home/steve/.roswell/bin:/home/steve/.emacs.d/bin:/var/lib/snapd/snap/bin:/opt/riscv/bin:/home/steve/.npm/bin:/home/steve/.cargo/bin:/home/steve/scripts:/home/steve/bin:/home/steve/go/bin:/usr/local/go/bin:/home/steve/.fluvio/bin:/home/steve/.local/share/ponyup/bin:/home/steve/NuSMV-2.6.0-Linux/bin/:/home/steve/.local/bin:/home/steve/.vino/bin:/home/steve/projects/odin/Odin:/home/steve/j90x/bin:/home/steve/opt/GNAT/2021/bin:/home/steve/opt/GNAT/2021-arm-elf/bin:/home/steve/opt/GNAT/arm-gnueabihf//bin:/home/steve/.fly/bin:/home/steve/.platformio/penv/bin:/home/steve/.ghcup/bin/:/home/steve/.wasmer/bin/:/home/steve/.roswell/bin:/home/steve/.emacs.d/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/usr/lib/rustup/bin:/opt/riscv/bin:/home/steve/.npm/bin:/home/steve/.cargo/bin:/home/steve/scripts:/home/steve/bin:/home/steve/go/bin:/usr/local/go/bin:/home/steve/.fluvio/bin:/home/steve/.local/share/ponyup/bin:/home/steve/NuSMV-2.6.0-Linux/bin/:/home/steve/.local/bin:/home/steve/.vino/bin:/home/steve/projects/odin/Odin:/home/steve/j90x/bin:/home/steve/opt/GNAT/2021/bin:/home/steve/opt/GNAT/2021-arm-elf/bin:/home/steve/opt/GNAT/arm-gnueabihf//bin:/home/steve/.fly/bin:/home/steve/.platformio/penv/bin:/home/steve/.ghcup/bin/:/home/steve/.wasmer/bin/:/home/steve/.roswell/bin:/home/steve/.emacs.d/bin29:ANDROID_HOME=/opt/android-sdk38:MANPATH=:/home/steve/.opam/default/man26:MAIL=/var/spool/mail/steve14:MOTD_SHOWN=pam58:OCAML_TOPLEVEL_PATH=/home/steve/.opam/default/lib/toplevel33:GK_GL_ADDR=http://127.0.0.1:40789150:CODELLDB_LAUNCH_CONNECT_FILE=/home/steve/.vscode-server/data/User/workspaceStorage/803f4dd44aeb4117289049d18054ecce/vadimcn.vscode-lldb/rpcaddress.txt10:USER=steve18:VSCODE_INJECTION=1))(4:root33:/home/steve/projects/ocaml/spooky)(3:pid6:427889)(11:initial_cwd33:/home/steve/projects/ocaml/spooky)(5:start19:1777395035274310407))(3:log4:info19:1777395035280763447(7:message10:ocamlparam)(10:OCAMLPARAM5:unset))(6:thread12:spawn_thread19:1777395035280800918(4:name7:console))(3:log4:info19:1777395035280910849(7:message20:Shared cache enabled)(13:cache_enabled25:Enabled_except_user_rules))(3:log4:info19:1777395035280922609(7:message21:Shared cache location)(8:root_dir26:/home/steve/.cache/dune/db))(3:log4:info19:1777395035280995019(7:message14:Workspace root)(4:root33:/home/steve/projects/ocaml/spooky))(3:log4:info19:1777395035281771447(7:message25:Auto-detected concurrency)(11:concurrency2:12))(6:thread12:spawn_thread19:1777395035281793807(4:name14:signal-watcher))(7:process15:signal_received19:1777395035281880288(6:signal4:CHLD))(3:log4:info19:1777395035282200031(7:message12:Dune context)(7:context((4:name7:default)(4:kind7:default)(7:profile3:Dev)(6:merlin4:true)(14:fdo_target_exe())(9:build_dir(12:In_build_dir7:default))(15:instrument_with()))))(5:rules9:Dune load(19:17773950352822277516:126821)(3:dir1:.))(5:build7:targets19:1777395035282481903(7:aliases(((3:dir1:.)(4:name7:default)(9:recursive5:false)(8:contexts(7:default))))))(7:process5:start19:1777395035282666825(12:process_args(7:-config))(3:pid6:427894)(10:categories())(6:queued3:850)(4:prog40:/home/steve/.opam/default/bin/ocamlc.opt)(3:dir1:.))(7:process15:signal_received19:1777395035286759944(6:signal4:CHLD))(7:process6:finish(19:17773950352826668257:4139279)(12:process_args(7:-config))(3:pid6:427894)(10:categories())(4:prog40:/home/steve/.opam/default/bin/ocamlc.opt)(3:dir1:.)(4:exit1:0)(6:rusage((13:user_cpu_time7:1999000)(15:system_cpu_time7:2001000)(6:maxrss5:27212)(6:minflt4:1140)(6:majflt1:0)(7:inblock1:0)(7:oublock1:0)(5:nvcsw1:1)(6:nivcsw1:0))))(6:action10:write-file(19:17773950352895781205:24900)(4:file33:_build/default/.dune/configurator)(4:size4:2187))(6:action10:write-file(19:17773950352896891715:18460)(4:file36:_build/default/.dune/configurator.v2)(4:size4:2375))(6:action10:write-file(19:17773950352898637435:15820)(4:file26:_build/default/META.spooky)(4:size1:0))(6:action10:write-file(19:17773950352899393435:21471)(4:file34:_build/default/spooky.dune-package)(4:size3:120))(6:action10:write-file(19:17773950352900037445:19260)(4:file26:_build/default/spooky.opam)(4:size3:786))(7:promote7:promote19:1777395035290060325(3:src26:_build/default/spooky.opam)(3:dst11:spooky.opam))(6:action10:write-file(19:17773950353073697975:22151)(4:file40:_build/default/bin/.merlin-conf/exe-main)(4:size3:722))(6:action10:write-file(19:17773950353074997295:20920)(4:file42:_build/default/lib/.merlin-conf/lib-spooky)(4:size3:657))(6:action10:write-file(19:17773950353075674895:20230)(4:file27:_build/default/bin/main.mli)(4:size2:28))(6:action10:write-file(19:17773950353076317805:19530)(4:file32:_build/default/lib/spooky.ml-gen)(4:size2:24))(6:action10:write-file(19:17773950353078834125:19160)(4:file48:_build/default/test/.merlin-conf/exe-test_spooky)(4:size3:770))(6:action10:write-file(19:17773950353079581935:28550)(4:file35:_build/default/test/test_spooky.mli)(4:size2:28))(7:process5:start19:1777395035308191035(12:process_args(13:-nopervasives9:-nostdlib12:-short-paths10:-keep-locs11:-warn-error2:+a2:-w3:-532:-w3:-492:-g10:-bin-annot22:-bin-annot-occurrences2:-I21:lib/.spooky.objs/byte14:-no-alias-deps7:-opaque2:-o32:lib/.spooky.objs/byte/spooky.cmo2:-c5:-impl17:lib/spooky.ml-gen))(3:pid6:427895)(10:categories())(6:queued3:820)(4:prog40:/home/steve/.opam/default/bin/ocamlc.opt)(3:dir14:_build/default)(12:target_files(47:_build/default/lib/.spooky.objs/byte/spooky.cmi47:_build/default/lib/.spooky.objs/byte/spooky.cmo47:_build/default/lib/.spooky.objs/byte/spooky.cmt)))(7:process15:signal_received19:1777395035313650256(6:signal4:CHLD))(7:process6:finish(19:17773950353081910357:5487902)(12:process_args(13:-nopervasives9:-nostdlib12:-short-paths10:-keep-locs11:-warn-error2:+a2:-w3:-532:-w3:-492:-g10:-bin-annot22:-bin-annot-occurrences2:-I21:lib/.spooky.objs/byte14:-no-alias-deps7:-opaque2:-o32:lib/.spooky.objs/byte/spooky.cmo2:-c5:-impl17:lib/spooky.ml-gen))(3:pid6:427895)(10:categories())(4:prog40:/home/steve/.opam/default/bin/ocamlc.opt)(3:dir14:_build/default)(4:exit1:0)(12:target_files(47:_build/default/lib/.spooky.objs/byte/spooky.cmi47:_build/default/lib/.spooky.objs/byte/spooky.cmo47:_build/default/lib/.spooky.objs/byte/spooky.cmt))(6:rusage((13:user_cpu_time7:1284000)(15:system_cpu_time7:4070000)(6:maxrss5:27852)(6:minflt4:1438)(6:majflt1:0)(7:inblock1:0)(7:oublock2:24)(5:nvcsw1:1)(6:nivcsw1:0))))(7:process5:start19:1777395035313857848(12:process_args(13:-nopervasives9:-nostdlib12:-short-paths10:-keep-locs11:-warn-error2:+a2:-w3:-532:-w3:-492:-g2:-I21:lib/.spooky.objs/byte2:-I23:lib/.spooky.objs/native9:-cmi-file32:lib/.spooky.objs/byte/spooky.cmi14:-no-alias-deps7:-opaque2:-o34:lib/.spooky.objs/native/spooky.cmx2:-c5:-impl17:lib/spooky.ml-gen))(3:pid6:427896)(10:categories())(6:queued3:550)(4:prog42:/home/steve/.opam/default/bin/ocamlopt.opt)(3:dir14:_build/default)(12:target_files(49:_build/default/lib/.spooky.objs/native/spooky.cmx47:_build/default/lib/.spooky.objs/native/spooky.o)))(7:process5:start19:1777395035314073190(12:process_args(12:-short-paths10:-keep-locs11:-warn-error2:+a2:-g2:-a2:-o14:lib/spooky.cma32:lib/.spooky.objs/byte/spooky.cmo))(3:pid6:427897)(10:categories())(6:queued3:380)(4:prog40:/home/steve/.opam/default/bin/ocamlc.opt)(3:dir14:_build/default)(12:target_files(29:_build/default/lib/spooky.cma)))(7:process5:start19:1777395035314323813(12:process_args(12:-short-paths10:-keep-locs11:-warn-error2:+a2:-g10:-bin-annot22:-bin-annot-occurrences2:-I20:bin/.main.eobjs/byte2:-I21:lib/.spooky.objs/byte14:-no-alias-deps7:-opaque2:-o40:bin/.main.eobjs/byte/dune__exe__Main.cmi2:-c5:-intf12:bin/main.mli))(3:pid6:427898)(10:categories())(6:queued3:380)(4:prog40:/home/steve/.opam/default/bin/ocamlc.opt)(3:dir14:_build/default)(12:target_files(55:_build/default/bin/.main.eobjs/byte/dune__exe__Main.cmi56:_build/default/bin/.main.eobjs/byte/dune__exe__Main.cmti)))(7:process5:start19:1777395035314606656(12:process_args(12:-short-paths10:-keep-locs11:-warn-error2:+a2:-g10:-bin-annot22:-bin-annot-occurrences2:-I28:test/.test_spooky.eobjs/byte2:-I21:lib/.spooky.objs/byte14:-no-alias-deps7:-opaque2:-o55:test/.test_spooky.eobjs/byte/dune__exe__Test_spooky.cmi2:-c5:-intf20:test/test_spooky.mli))(3:pid6:427899)(10:categories())(6:queued3:390)(4:prog40:/home/steve/.opam/default/bin/ocamlc.opt)(3:dir14:_build/default)(12:target_files(70:_build/default/test/.test_spooky.eobjs/byte/dune__exe__Test_spooky.cmi71:_build/default/test/.test_spooky.eobjs/byte/dune__exe__Test_spooky.cmti)))(7:process15:signal_received19:1777395035319528752(6:signal4:CHLD))(7:process6:finish(19:17773950353140731907:5518022)(12:process_args(12:-short-paths10:-keep-locs11:-warn-error2:+a2:-g2:-a2:-o14:lib/spooky.cma32:lib/.spooky.objs/byte/spooky.cmo))(3:pid6:427897)(10:categories())(4:prog40:/home/steve/.opam/default/bin/ocamlc.opt)(3:dir14:_build/default)(4:exit1:0)(12:target_files(29:_build/default/lib/spooky.cma))(6:rusage((13:user_cpu_time6:888000)(15:system_cpu_time7:4438000)(6:maxrss5:27980)(6:minflt4:1156)(6:majflt1:0)(7:inblock1:0)(7:oublock1:8)(5:nvcsw1:1)(6:nivcsw1:2))))(7:process15:signal_received19:1777395035321752143(6:signal4:CHLD))(7:process15:signal_received19:1777395035321774733(6:signal4:CHLD))(7:process6:finish(19:17773950353143238137:7472260)(12:process_args(12:-short-paths10:-keep-locs11:-warn-error2:+a2:-g10:-bin-annot22:-bin-annot-occurrences2:-I20:bin/.main.eobjs/byte2:-I21:lib/.spooky.objs/byte14:-no-alias-deps7:-opaque2:-o40:bin/.main.eobjs/byte/dune__exe__Main.cmi2:-c5:-intf12:bin/main.mli))(3:pid6:427898)(10:categories())(4:prog40:/home/steve/.opam/default/bin/ocamlc.opt)(3:dir14:_build/default)(4:exit1:0)(12:target_files(55:_build/default/bin/.main.eobjs/byte/dune__exe__Main.cmi56:_build/default/bin/.main.eobjs/byte/dune__exe__Main.cmti))(6:rusage((13:user_cpu_time7:1113000)(15:system_cpu_time7:6227000)(6:maxrss5:27980)(6:minflt4:1682)(6:majflt1:0)(7:inblock1:0)(7:oublock2:16)(5:nvcsw1:1)(6:nivcsw1:0))))(7:process5:start19:1777395035322046405(12:process_args(12:-short-paths10:-keep-locs11:-warn-error2:+a2:-g2:-I20:bin/.main.eobjs/byte2:-I22:bin/.main.eobjs/native2:-I21:lib/.spooky.objs/byte2:-I23:lib/.spooky.objs/native9:-cmi-file40:bin/.main.eobjs/byte/dune__exe__Main.cmi14:-no-alias-deps7:-opaque2:-o42:bin/.main.eobjs/native/dune__exe__Main.cmx2:-c5:-impl11:bin/main.ml))(3:pid6:427901)(10:categories())(6:queued4:1260)(4:prog42:/home/steve/.opam/default/bin/ocamlopt.opt)(3:dir14:_build/default)(12:target_files(57:_build/default/bin/.main.eobjs/native/dune__exe__Main.cmx55:_build/default/bin/.main.eobjs/native/dune__exe__Main.o)))(7:process6:finish(19:17773950353146066567:7197567)(12:process_args(12:-short-paths10:-keep-locs11:-warn-error2:+a2:-g10:-bin-annot22:-bin-annot-occurrences2:-I28:test/.test_spooky.eobjs/byte2:-I21:lib/.spooky.objs/byte14:-no-alias-deps7:-opaque2:-o55:test/.test_spooky.eobjs/byte/dune__exe__Test_spooky.cmi2:-c5:-intf20:test/test_spooky.mli))(3:pid6:427899)(10:categories())(4:prog40:/home/steve/.opam/default/bin/ocamlc.opt)(3:dir14:_build/default)(4:exit1:0)(12:target_files(70:_build/default/test/.test_spooky.eobjs/byte/dune__exe__Test_spooky.cmi71:_build/default/test/.test_spooky.eobjs/byte/dune__exe__Test_spooky.cmti))(6:rusage((13:user_cpu_time7:3220000)(15:system_cpu_time7:3736000)(6:maxrss5:27980)(6:minflt4:1530)(6:majflt1:0)(7:inblock1:0)(7:oublock2:16)(5:nvcsw1:1)(6:nivcsw1:1))))(7:process5:start19:1777395035322396819(12:process_args(12:-short-paths10:-keep-locs11:-warn-error2:+a2:-g2:-I28:test/.test_spooky.eobjs/byte2:-I30:test/.test_spooky.eobjs/native2:-I21:lib/.spooky.objs/byte2:-I23:lib/.spooky.objs/native9:-cmi-file55:test/.test_spooky.eobjs/byte/dune__exe__Test_spooky.cmi14:-no-alias-deps7:-opaque2:-o57:test/.test_spooky.eobjs/native/dune__exe__Test_spooky.cmx2:-c5:-impl19:test/test_spooky.ml))(3:pid6:427902)(10:categories())(6:queued3:470)(4:prog42:/home/steve/.opam/default/bin/ocamlopt.opt)(3:dir14:_build/default)(12:target_files(72:_build/default/test/.test_spooky.eobjs/native/dune__exe__Test_spooky.cmx70:_build/default/test/.test_spooky.eobjs/native/dune__exe__Test_spooky.o)))(7:process15:signal_received19:1777395035322948534(6:signal4:CHLD))(7:process6:finish(19:17773950353138578487:9117786)(12:process_args(13:-nopervasives9:-nostdlib12:-short-paths10:-keep-locs11:-warn-error2:+a2:-w3:-532:-w3:-492:-g2:-I21:lib/.spooky.objs/byte2:-I23:lib/.spooky.objs/native9:-cmi-file32:lib/.spooky.objs/byte/spooky.cmi14:-no-alias-deps7:-opaque2:-o34:lib/.spooky.objs/native/spooky.cmx2:-c5:-impl17:lib/spooky.ml-gen))(3:pid6:427896)(10:categories())(4:prog42:/home/steve/.opam/default/bin/ocamlopt.opt)(3:dir14:_build/default)(4:exit1:0)(12:target_files(49:_build/default/lib/.spooky.objs/native/spooky.cmx47:_build/default/lib/.spooky.objs/native/spooky.o))(6:rusage((13:user_cpu_time7:1041000)(15:system_cpu_time7:7889000)(6:maxrss5:27980)(6:minflt4:1770)(6:majflt1:0)(7:inblock1:0)(7:oublock2:16)(5:nvcsw1:4)(6:nivcsw1:3))))(7:process5:start19:1777395035323114565(12:process_args(12:-short-paths10:-keep-locs11:-warn-error2:+a2:-g2:-a2:-o15:lib/spooky.cmxa34:lib/.spooky.objs/native/spooky.cmx))(3:pid6:427903)(10:categories())(6:queued3:470)(4:prog42:/home/steve/.opam/default/bin/ocamlopt.opt)(3:dir14:_build/default)(12:target_files(27:_build/default/lib/spooky.a30:_build/default/lib/spooky.cmxa)))(7:process15:signal_received19:1777395035333556924(6:signal4:CHLD))(7:process6:finish(19:17773950353223968198:11203825)(12:process_args(12:-short-paths10:-keep-locs11:-warn-error2:+a2:-g2:-I28:test/.test_spooky.eobjs/byte2:-I30:test/.test_spooky.eobjs/native2:-I21:lib/.spooky.objs/byte2:-I23:lib/.spooky.objs/native9:-cmi-file55:test/.test_spooky.eobjs/byte/dune__exe__Test_spooky.cmi14:-no-alias-deps7:-opaque2:-o57:test/.test_spooky.eobjs/native/dune__exe__Test_spooky.cmx2:-c5:-impl19:test/test_spooky.ml))(3:pid6:427902)(10:categories())(4:prog42:/home/steve/.opam/default/bin/ocamlopt.opt)(3:dir14:_build/default)(4:exit1:0)(12:target_files(72:_build/default/test/.test_spooky.eobjs/native/dune__exe__Test_spooky.cmx70:_build/default/test/.test_spooky.eobjs/native/dune__exe__Test_spooky.o))(6:rusage((13:user_cpu_time7:2195000)(15:system_cpu_time7:8830000)(6:maxrss5:27980)(6:minflt4:2194)(6:majflt1:0)(7:inblock1:0)(7:oublock2:16)(5:nvcsw1:4)(6:nivcsw1:0))))(7:process15:signal_received19:1777395035334380211(6:signal4:CHLD))(7:process6:finish(19:17773950353220464058:12353987)(12:process_args(12:-short-paths10:-keep-locs11:-warn-error2:+a2:-g2:-I20:bin/.main.eobjs/byte2:-I22:bin/.main.eobjs/native2:-I21:lib/.spooky.objs/byte2:-I23:lib/.spooky.objs/native9:-cmi-file40:bin/.main.eobjs/byte/dune__exe__Main.cmi14:-no-alias-deps7:-opaque2:-o42:bin/.main.eobjs/native/dune__exe__Main.cmx2:-c5:-impl11:bin/main.ml))(3:pid6:427901)(10:categories())(4:prog42:/home/steve/.opam/default/bin/ocamlopt.opt)(3:dir14:_build/default)(4:exit1:0)(12:target_files(57:_build/default/bin/.main.eobjs/native/dune__exe__Main.cmx55:_build/default/bin/.main.eobjs/native/dune__exe__Main.o))(6:rusage((13:user_cpu_time7:3995000)(15:system_cpu_time7:8162000)(6:maxrss5:27980)(6:minflt4:2324)(6:majflt1:0)(7:inblock1:0)(7:oublock2:16)(5:nvcsw1:3)(6:nivcsw1:2))))(7:process15:signal_received19:1777395035342635789(6:signal4:CHLD))(7:process6:finish(19:17773950353231145658:19549094)(12:process_args(12:-short-paths10:-keep-locs11:-warn-error2:+a2:-g2:-a2:-o15:lib/spooky.cmxa34:lib/.spooky.objs/native/spooky.cmx))(3:pid6:427903)(10:categories())(4:prog42:/home/steve/.opam/default/bin/ocamlopt.opt)(3:dir14:_build/default)(4:exit1:0)(12:target_files(27:_build/default/lib/spooky.a30:_build/default/lib/spooky.cmxa))(6:rusage((13:user_cpu_time7:5668000)(15:system_cpu_time8:11126000)(6:maxrss6:121284)(6:minflt4:2850)(6:majflt1:0)(7:inblock1:0)(7:oublock2:32)(5:nvcsw1:5)(6:nivcsw1:4))))(7:process5:start19:1777395035342827391(12:process_args(12:-short-paths10:-keep-locs11:-warn-error2:+a2:-g2:-o12:bin/main.exe15:lib/spooky.cmxa42:bin/.main.eobjs/native/dune__exe__Main.cmx))(3:pid6:427907)(10:categories())(6:queued4:1120)(4:prog42:/home/steve/.opam/default/bin/ocamlopt.opt)(3:dir14:_build/default)(12:target_files(27:_build/default/bin/main.exe)))(7:process5:start19:1777395035343078973(12:process_args(12:-short-paths10:-keep-locs11:-warn-error2:+a2:-g7:-shared8:-linkall2:-I3:lib2:-o15:lib/spooky.cmxs15:lib/spooky.cmxa))(3:pid6:427908)(10:categories())(6:queued3:410)(4:prog42:/home/steve/.opam/default/bin/ocamlopt.opt)(3:dir14:_build/default)(12:target_files(30:_build/default/lib/spooky.cmxs)))(7:process5:start19:1777395035343272535(12:process_args(12:-short-paths10:-keep-locs11:-warn-error2:+a2:-g2:-o20:test/test_spooky.exe15:lib/spooky.cmxa57:test/.test_spooky.eobjs/native/dune__exe__Test_spooky.cmx))(3:pid6:427909)(10:categories())(6:queued3:350)(4:prog42:/home/steve/.opam/default/bin/ocamlopt.opt)(3:dir14:_build/default)(12:target_files(35:_build/default/test/test_spooky.exe)))(7:process15:signal_received19:1777395035367317471(6:signal4:CHLD))(7:process6:finish(19:17773950353430789738:24287519)(12:process_args(12:-short-paths10:-keep-locs11:-warn-error2:+a2:-g7:-shared8:-linkall2:-I3:lib2:-o15:lib/spooky.cmxs15:lib/spooky.cmxa))(3:pid6:427908)(10:categories())(4:prog42:/home/steve/.opam/default/bin/ocamlopt.opt)(3:dir14:_build/default)(4:exit1:0)(12:target_files(30:_build/default/lib/spooky.cmxs))(6:rusage((13:user_cpu_time7:9566000)(15:system_cpu_time8:14422000)(6:maxrss5:27980)(6:minflt4:3969)(6:majflt1:4)(7:inblock1:0)(7:oublock2:40)(5:nvcsw2:22)(6:nivcsw1:4))))(7:process15:signal_received19:1777395035422272198(6:signal4:CHLD))(7:process15:signal_received19:1777395035422297708(6:signal4:CHLD))(7:process6:finish(19:17773950353428273918:79489628)(12:process_args(12:-short-paths10:-keep-locs11:-warn-error2:+a2:-g2:-o12:bin/main.exe15:lib/spooky.cmxa42:bin/.main.eobjs/native/dune__exe__Main.cmx))(3:pid6:427907)(10:categories())(4:prog42:/home/steve/.opam/default/bin/ocamlopt.opt)(3:dir14:_build/default)(4:exit1:0)(12:target_files(27:_build/default/bin/main.exe))(6:rusage((13:user_cpu_time8:41885000)(15:system_cpu_time8:37001000)(6:maxrss5:27980)(6:minflt4:6621)(6:majflt1:0)(7:inblock1:0)(7:oublock4:3440)(5:nvcsw2:13)(6:nivcsw2:19))))(7:process6:finish(19:17773950353432725358:79052854)(12:process_args(12:-short-paths10:-keep-locs11:-warn-error2:+a2:-g2:-o20:test/test_spooky.exe15:lib/spooky.cmxa57:test/.test_spooky.eobjs/native/dune__exe__Test_spooky.cmx))(3:pid6:427909)(10:categories())(4:prog42:/home/steve/.opam/default/bin/ocamlopt.opt)(3:dir14:_build/default)(4:exit1:0)(12:target_files(35:_build/default/test/test_spooky.exe))(6:rusage((13:user_cpu_time8:37775000)(15:system_cpu_time8:40505000)(6:maxrss5:27980)(6:minflt4:6093)(6:majflt1:1)(7:inblock1:0)(7:oublock4:3440)(5:nvcsw2:40)(6:nivcsw1:6))))(6:action5:start19:1777395035424335748(4:name16:gen-install-file))(6:action6:finish(19:17773950354243357485:40030)(4:name16:gen-install-file))(5:rules13:Alias builder(19:17773950352885117509:135906178)(3:dir1:.))(7:process15:signal_received19:1777395035424468359(6:signal4:USR1))(10:persistent2:db(19:17773950354311668125:78760)(4:path17:_build/.digest-db)(6:module9:DIGEST-DB)(9:operation4:save))(10:persistent2:db(19:17773950354312608435:29940)(4:path10:_build/.db)(6:module14:INCREMENTAL-DB)(9:operation4:save))(6:config4:exit19:1777395035431296863(2:gc((10:stack_size1:0)(10:heap_words6:409917)(14:top_heap_words6:409917)(11:minor_words8:1179351.)(11:major_words7:343422.)(14:promoted_words7:302852.)(11:compactions1:0)(17:major_collections1:3)(17:minor_collections1:6)))(2:io((10:files_read((5:count2:24)(4:time6:516225)(5:bytes3:538)))(13:files_written((5:count2:14)(4:time1:0)(5:bytes4:7904)))(16:directories_read((5:count1:1)(4:time1:0)))))(6:digest((5:files((5:count2:49)(4:time8:13504077)(5:bytes1:0)))(6:values((5:count3:109)(4:time6:137211)(5:bytes5:23432)))))) \ No newline at end of file +(6:config4:init19:1777475932353986185(7:version6:3.22.2)(9:build_dir6:_build)(4:argv(4:dune4:exec6:spooky))(3:env(18:DIRENV_LOG_FORMAT=65:FZF_DEFAULT_COMMAND=rg --files --hidden --follow --glob "!.git/*"140:CAML_LD_LIBRARY_PATH=/home/steve/.opam/default/lib/stublibs:/home/steve/.opam/default/lib/ocaml/stublibs:/home/steve/.opam/default/lib/ocaml10:USER=steve60:OCAMLTOP_INCLUDE_PATH=/home/steve/.opam/default/lib/toplevel37:PWD=/home/steve/projects/ocaml/spooky153:VSCODE_GIT_ASKPASS_MAIN=/home/steve/.vscode-server/cli/servers/Stable-10c8e557c8b9f9ed0a87f61f1c9a44bde731c409/server/extensions/git/dist/askpass-main.js22:XDG_SESSION_CLASS=user11:VISUAL=nvim28:TERM_PROGRAM_VERSION=1.117.080:OPAM_LAST_ENV=/home/steve/.opam/.last-env/env-550cde22b571c5ee951c5aa1091061dc-017:XDG_SESSION_ID=2787:VSCODE_IPC_HOOK_CLI=/run/user/1000/vscode-ipc-0974db83-8f7b-46f1-a5db-f5ac1fc5b845.sock63:VSCODE_GIT_IPC_HANDLE=/run/user/1000/vscode-git-7fc91d943b.sock19:COLORTERM=truecolor11:EDITOR=nvim150:CODELLDB_LAUNCH_CONNECT_FILE=/home/steve/.vscode-server/data/User/workspaceStorage/803f4dd44aeb4117289049d18054ecce/vadimcn.vscode-lldb/rpcaddress.txt33:ANDROID_NDK_HOME=/opt/android-ndk30:VSCODE_GIT_ASKPASS_EXTRA_ARGS=16:HOME=/home/steve38:MANPATH=:/home/steve/.opam/default/man19:TERM=xterm-256color122:VSCODE_GIT_ASKPASS_NODE=/home/steve/.vscode-server/cli/servers/Stable-10c8e557c8b9f9ed0a87f61f1c9a44bde731c409/server/node136:GIT_ASKPASS=/home/steve/.vscode-server/cli/servers/Stable-10c8e557c8b9f9ed0a87f61f1c9a44bde731c409/server/extensions/git/dist/askpass.sh18:VSCODE_INJECTION=128:MPS_JDK=/opt/MPS 2021.3/jbr/64:XDG_DATA_DIRS=/usr/local/share:/usr/share:/var/lib/snapd/desktop44:OPAM_SWITCH_PREFIX=/home/steve/.opam/default124:BROWSER=/home/steve/.vscode-server/cli/servers/Stable-10c8e557c8b9f9ed0a87f61f1c9a44bde731c409/server/bin/helpers/browser.sh26:MAIL=/var/spool/mail/steve7:SHLVL=331:FLYCTL_INSTALL=/home/steve/.fly19:SHELL=/usr/bin/fish52:SSH_CONNECTION=84.115.232.107 53606 65.21.205.199 222511:PATH=/home/steve/.opam/default/bin:/home/steve/.vscode-server/data/User/globalStorage/github.copilot-chat/debugCommand:/home/steve/.vscode-server/data/User/globalStorage/github.copilot-chat/copilotCli:/home/steve/.vscode-server/extensions/vadimcn.vscode-lldb-1.12.1/bin:/home/steve/.vscode-server/extensions/vadimcn.vscode-lldb-1.12.1/bin:/home/steve/.vscode-server/data/User/globalStorage/github.copilot-chat/debugCommand:/home/steve/.vscode-server/data/User/globalStorage/github.copilot-chat/copilotCli:/home/steve/.vscode-server/cli/servers/Stable-10c8e557c8b9f9ed0a87f61f1c9a44bde731c409/server/bin/remote-cli:/usr/local/sbin:/usr/local/bin:/usr/bin:/opt/riscv/bin:/home/steve/.npm/bin:/home/steve/.cargo/bin:/home/steve/scripts:/home/steve/bin:/home/steve/go/bin:/usr/local/go/bin:/home/steve/.fluvio/bin:/home/steve/.local/share/ponyup/bin:/home/steve/NuSMV-2.6.0-Linux/bin/:/home/steve/.local/bin:/home/steve/.vino/bin:/home/steve/projects/odin/Odin:/home/steve/j90x/bin:/home/steve/opt/GNAT/2021/bin:/home/steve/opt/GNAT/2021-arm-elf/bin:/home/steve/opt/GNAT/arm-gnueabihf//bin:/home/steve/.fly/bin:/home/steve/.platformio/penv/bin:/home/steve/.ghcup/bin/:/home/steve/.wasmer/bin/:/home/steve/.roswell/bin:/home/steve/.emacs.d/bin:/var/lib/snapd/snap/bin:/opt/riscv/bin:/home/steve/.npm/bin:/home/steve/.cargo/bin:/home/steve/scripts:/home/steve/bin:/home/steve/go/bin:/usr/local/go/bin:/home/steve/.fluvio/bin:/home/steve/.local/share/ponyup/bin:/home/steve/NuSMV-2.6.0-Linux/bin/:/home/steve/.local/bin:/home/steve/.vino/bin:/home/steve/projects/odin/Odin:/home/steve/j90x/bin:/home/steve/opt/GNAT/2021/bin:/home/steve/opt/GNAT/2021-arm-elf/bin:/home/steve/opt/GNAT/arm-gnueabihf//bin:/home/steve/.fly/bin:/home/steve/.platformio/penv/bin:/home/steve/.ghcup/bin/:/home/steve/.wasmer/bin/:/home/steve/.roswell/bin:/home/steve/.emacs.d/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/usr/lib/rustup/bin:/opt/riscv/bin:/home/steve/.npm/bin:/home/steve/.cargo/bin:/home/steve/scripts:/home/steve/bin:/home/steve/go/bin:/usr/local/go/bin:/home/steve/.fluvio/bin:/home/steve/.local/share/ponyup/bin:/home/steve/NuSMV-2.6.0-Linux/bin/:/home/steve/.local/bin:/home/steve/.vino/bin:/home/steve/projects/odin/Odin:/home/steve/j90x/bin:/home/steve/opt/GNAT/2021/bin:/home/steve/opt/GNAT/2021-arm-elf/bin:/home/steve/opt/GNAT/arm-gnueabihf//bin:/home/steve/.fly/bin:/home/steve/.platformio/penv/bin:/home/steve/.ghcup/bin/:/home/steve/.wasmer/bin/:/home/steve/.roswell/bin:/home/steve/.emacs.d/bin53:DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus14:MOTD_SHOWN=pam20:XDG_SESSION_TYPE=tty20:OPAMNOENVNOTICE=true13:LOGNAME=steve19:SYSTEMD_EDITOR=nvim30:XDG_RUNTIME_DIR=/run/user/100034:SSH_CLIENT=84.115.232.107 53606 2229:ANDROID_HOME=/opt/android-sdk58:OCAML_TOPLEVEL_PATH=/home/steve/.opam/default/lib/toplevel37:STARSHIP_SESSION_KEY=381514717543912119:STARSHIP_SHELL=fish19:TERM_PROGRAM=vscode16:LANG=en_US.UTF-834:VSCODE_PYTHON_AUTOACTIVATE_GUARD=1))(4:root33:/home/steve/projects/ocaml/spooky)(3:pid7:1715249)(11:initial_cwd33:/home/steve/projects/ocaml/spooky)(5:start19:1777475932350049571))(3:log4:info19:1777475932354011645(7:message10:ocamlparam)(10:OCAMLPARAM5:unset))(6:thread12:spawn_thread19:1777475932354041375(4:name7:console))(3:log4:info19:1777475932354132486(7:message20:Shared cache enabled)(13:cache_enabled25:Enabled_except_user_rules))(3:log4:info19:1777475932354140246(7:message21:Shared cache location)(8:root_dir26:/home/steve/.cache/dune/db))(3:log4:info19:1777475932354235907(7:message14:Workspace root)(4:root33:/home/steve/projects/ocaml/spooky))(3:log4:info19:1777475932355021974(7:message25:Auto-detected concurrency)(11:concurrency2:12))(6:thread12:spawn_thread19:1777475932355044474(4:name14:signal-watcher))(7:process15:signal_received19:1777475932355199305(6:signal4:CHLD))(10:persistent2:db(19:17774759323552869065:54791)(4:path17:_build/.digest-db)(6:module9:DIGEST-DB)(9:operation4:load))(3:log4:info19:1777475932355499258(7:message12:Dune context)(7:context((4:name7:default)(4:kind7:default)(7:profile3:Dev)(6:merlin4:true)(14:fdo_target_exe())(9:build_dir(12:In_build_dir7:default))(15:instrument_with()))))(5:rules9:Dune load(19:17774759323555212286:118811)(3:dir1:.))(7:process5:start19:1777475932355849301(12:process_args(7:-config))(3:pid7:1715254)(10:categories())(6:queued3:810)(4:prog40:/home/steve/.opam/default/bin/ocamlc.opt)(3:dir1:.))(7:process15:signal_received19:1777475932360582522(6:signal4:CHLD))(7:process6:finish(19:17774759323558493017:4783742)(12:process_args(7:-config))(3:pid7:1715254)(10:categories())(4:prog40:/home/steve/.opam/default/bin/ocamlc.opt)(3:dir1:.)(4:exit1:0)(6:rusage((13:user_cpu_time6:921000)(15:system_cpu_time7:3685000)(6:maxrss5:26848)(6:minflt4:1155)(6:majflt1:0)(7:inblock1:0)(7:oublock1:0)(5:nvcsw1:1)(6:nivcsw1:0))))(10:persistent2:db(19:17774759323625502095:21471)(4:path10:_build/.db)(6:module14:INCREMENTAL-DB)(9:operation4:load))(7:process5:start19:1777475932365512925(12:process_args(12:-short-paths10:-keep-locs11:-warn-error2:+a2:-g10:-bin-annot22:-bin-annot-occurrences2:-I20:bin/.main.eobjs/byte2:-I21:lib/.spooky.objs/byte14:-no-alias-deps7:-opaque2:-o40:bin/.main.eobjs/byte/dune__exe__Main.cmi2:-c5:-intf12:bin/main.mli))(3:pid7:1715255)(10:categories())(6:queued3:890)(4:prog40:/home/steve/.opam/default/bin/ocamlc.opt)(3:dir14:_build/default)(12:target_files(55:_build/default/bin/.main.eobjs/byte/dune__exe__Main.cmi56:_build/default/bin/.main.eobjs/byte/dune__exe__Main.cmti)))(7:process15:signal_received19:1777475932372781388(6:signal4:CHLD))(7:process6:finish(19:17774759323655129257:7323364)(12:process_args(12:-short-paths10:-keep-locs11:-warn-error2:+a2:-g10:-bin-annot22:-bin-annot-occurrences2:-I20:bin/.main.eobjs/byte2:-I21:lib/.spooky.objs/byte14:-no-alias-deps7:-opaque2:-o40:bin/.main.eobjs/byte/dune__exe__Main.cmi2:-c5:-intf12:bin/main.mli))(3:pid7:1715255)(10:categories())(4:prog40:/home/steve/.opam/default/bin/ocamlc.opt)(3:dir14:_build/default)(4:exit1:0)(12:target_files(55:_build/default/bin/.main.eobjs/byte/dune__exe__Main.cmi56:_build/default/bin/.main.eobjs/byte/dune__exe__Main.cmti))(6:rusage((13:user_cpu_time7:2168000)(15:system_cpu_time7:4955000)(6:maxrss5:27360)(6:minflt4:1629)(6:majflt1:0)(7:inblock1:0)(7:oublock2:16)(5:nvcsw1:1)(6:nivcsw1:1))))(7:process5:start19:1777475932373080151(12:process_args(12:-short-paths10:-keep-locs11:-warn-error2:+a2:-g2:-I20:bin/.main.eobjs/byte2:-I22:bin/.main.eobjs/native2:-I21:lib/.spooky.objs/byte2:-I23:lib/.spooky.objs/native9:-cmi-file40:bin/.main.eobjs/byte/dune__exe__Main.cmi14:-no-alias-deps7:-opaque2:-o42:bin/.main.eobjs/native/dune__exe__Main.cmx2:-c5:-impl11:bin/main.ml))(3:pid7:1715256)(10:categories())(6:queued3:670)(4:prog42:/home/steve/.opam/default/bin/ocamlopt.opt)(3:dir14:_build/default)(12:target_files(57:_build/default/bin/.main.eobjs/native/dune__exe__Main.cmx55:_build/default/bin/.main.eobjs/native/dune__exe__Main.o)))(7:process15:signal_received19:1777475932387505066(6:signal4:CHLD))(7:process6:finish(19:17774759323730801518:14476406)(12:process_args(12:-short-paths10:-keep-locs11:-warn-error2:+a2:-g2:-I20:bin/.main.eobjs/byte2:-I22:bin/.main.eobjs/native2:-I21:lib/.spooky.objs/byte2:-I23:lib/.spooky.objs/native9:-cmi-file40:bin/.main.eobjs/byte/dune__exe__Main.cmi14:-no-alias-deps7:-opaque2:-o42:bin/.main.eobjs/native/dune__exe__Main.cmx2:-c5:-impl11:bin/main.ml))(3:pid7:1715256)(10:categories())(4:prog42:/home/steve/.opam/default/bin/ocamlopt.opt)(3:dir14:_build/default)(4:exit1:0)(12:target_files(57:_build/default/bin/.main.eobjs/native/dune__exe__Main.cmx55:_build/default/bin/.main.eobjs/native/dune__exe__Main.o))(6:rusage((13:user_cpu_time7:5975000)(15:system_cpu_time7:8299000)(6:maxrss5:27360)(6:minflt4:2477)(6:majflt1:0)(7:inblock1:0)(7:oublock2:24)(5:nvcsw1:4)(6:nivcsw1:1))))(7:process5:start19:1777475932388223672(12:process_args(12:-short-paths10:-keep-locs11:-warn-error2:+a2:-g2:-o12:bin/main.exe15:lib/spooky.cmxa42:bin/.main.eobjs/native/dune__exe__Main.cmx))(3:pid7:1715258)(10:categories())(6:queued3:770)(4:prog42:/home/steve/.opam/default/bin/ocamlopt.opt)(3:dir14:_build/default)(12:target_files(27:_build/default/bin/main.exe)))(7:process15:signal_received19:1777475932520221300(6:signal4:CHLD))(7:process6:finish(19:17774759323882236729:132048958)(12:process_args(12:-short-paths10:-keep-locs11:-warn-error2:+a2:-g2:-o12:bin/main.exe15:lib/spooky.cmxa42:bin/.main.eobjs/native/dune__exe__Main.cmx))(3:pid7:1715258)(10:categories())(4:prog42:/home/steve/.opam/default/bin/ocamlopt.opt)(3:dir14:_build/default)(4:exit1:0)(12:target_files(27:_build/default/bin/main.exe))(6:rusage((13:user_cpu_time8:73077000)(15:system_cpu_time8:58134000)(6:maxrss5:27360)(6:minflt4:8920)(6:majflt1:0)(7:inblock1:0)(7:oublock4:5488)(5:nvcsw2:13)(6:nivcsw1:8))))(10:persistent2:db(19:17774759325377446225:97261)(4:path17:_build/.digest-db)(6:module9:DIGEST-DB)(9:operation4:save))(10:persistent2:db(19:17774759325378673835:45571)(4:path10:_build/.db)(6:module14:INCREMENTAL-DB)(9:operation4:save))(6:config4:exit19:1777475932537922054(2:gc((10:stack_size1:0)(10:heap_words6:373048)(14:top_heap_words6:373048)(11:minor_words7:917495.)(11:major_words7:277000.)(14:promoted_words7:263034.)(11:compactions1:0)(17:major_collections1:3)(17:minor_collections1:5)))(2:io((10:files_read((5:count1:8)(4:time6:159251)(5:bytes3:538)))(13:files_written((5:count1:1)(4:time1:0)(5:bytes1:7)))(16:directories_read((5:count1:1)(4:time1:0)))))(6:digest((5:files((5:count1:9)(4:time7:1832546)(5:bytes1:0)))(6:values((5:count2:52)(4:time5:66590)(5:bytes5:13044)))))) \ No newline at end of file diff --git a/bin/main.ml b/bin/main.ml index 7bf6048..58b561a 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -1 +1,34 @@ -let () = print_endline "Hello, World!" +let sample_program = + {| +struct Point { + int x; + int y; +}; + +int sum_array(int[] arr) { + int total = 0; + foreach (int n in arr) { + total = total + n; + } + return total; +} + +int main() { + struct Point p; + int[] nums; + int x = 1 + 2 * 3; + p.x = x; + if (x > 0) { + x = x + p.x; + } else { + x = 0; + } + x = sum_array(nums); + return x; +} +|} + +let () = + match Spooky.parse_and_type_check sample_program with + | Ok ast -> Printf.printf "Program parsed and type-checked successfully.\n" + | Error msg -> Printf.printf "Error: %s\n" msg diff --git a/lib/spooky.ml b/lib/spooky.ml new file mode 100644 index 0000000..dd8a47a --- /dev/null +++ b/lib/spooky.ml @@ -0,0 +1,806 @@ +module StringMap = Map.Make (String) + +exception Parse_error of string +exception Type_error of string + +type typ = + | TInt + | TBool + | TVoid + | TStruct of string + | TArray of typ + +type binop = + | Add + | Sub + | Mul + | Div + | Mod + | And + | Or + | Eq + | Ne + | Lt + | Le + | Gt + | Ge + +type unop = Neg | Not + +type expr = + | IntLit of int + | BoolLit of bool + | Var of string + | Binop of binop * expr * expr + | Unop of unop * expr + | Assign of expr * expr + | Call of expr * expr list + | ArrayGet of expr * expr + | StructGet of expr * string + +type stmt = + | VarDecl of typ * string * expr option + | Expr of expr + | If of expr * stmt list * stmt list + | ForEach of typ * string * expr * stmt list + | Return of expr option + | Block of stmt list + +type func = { + name : string; + params : (typ * string) list; + ret : typ; + body : stmt list; +} + +type struct_def = { + sname : string; + fields : (typ * string) list; +} + +type top = + | TopStruct of struct_def + | TopFunc of func + | TopGlobalVar of typ * string * expr option + +type program = top list + +let string_of_typ = + let rec go = function + | TInt -> "int" + | TBool -> "bool" + | TVoid -> "void" + | TStruct n -> "struct " ^ n + | TArray t -> go t ^ "[]" + in + go + +let rec equal_typ a b = + match (a, b) with + | TInt, TInt | TBool, TBool | TVoid, TVoid -> true + | TStruct x, TStruct y -> String.equal x y + | TArray x, TArray y -> equal_typ x y + | _ -> false + +type token = + | TIntKw + | TBoolKw + | TVoidKw + | TStructKw + | TIf + | TElse + | TFor + | TEach + | TForEach + | TIn + | TReturn + | TTrue + | TFalse + | TIdent of string + | TIntLit of int + | TLParen + | TRParen + | TLBrace + | TRBrace + | TLBracket + | TRBracket + | TSemicolon + | TComma + | TDot + | TAssign + | TPlus + | TMinus + | TStar + | TSlash + | TPercent + | TAndAnd + | TOrOr + | TBang + | TEqEq + | TNe + | TLt + | TLe + | TGt + | TGe + | TEOF + +let is_space = function ' ' | '\t' | '\r' | '\n' -> true | _ -> false + +let is_digit c = c >= '0' && c <= '9' + +let is_ident_start c = + (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c = '_' + +let is_ident_char c = is_ident_start c || is_digit c + +let keyword_or_ident s = + match s with + | "int" -> TIntKw + | "bool" -> TBoolKw + | "void" -> TVoidKw + | "struct" -> TStructKw + | "if" -> TIf + | "else" -> TElse + | "for" -> TFor + | "each" -> TEach + | "foreach" -> TForEach + | "in" -> TIn + | "return" -> TReturn + | "true" -> TTrue + | "false" -> TFalse + | _ -> TIdent s + +let lex (src : string) : token list = + let n = String.length src in + let rec skip_line_comment i = + if i >= n then i + else if src.[i] = '\n' then i + 1 + else skip_line_comment (i + 1) + in + let rec skip_block_comment i = + if i + 1 >= n then raise (Parse_error "unterminated block comment") + else if src.[i] = '*' && src.[i + 1] = '/' then i + 2 + else skip_block_comment (i + 1) + in + let rec read_number i j = + if j < n && is_digit src.[j] then read_number i (j + 1) + else + let s = String.sub src i (j - i) in + (TIntLit (int_of_string s), j) + in + let rec read_ident i j = + if j < n && is_ident_char src.[j] then read_ident i (j + 1) + else + let s = String.sub src i (j - i) in + (keyword_or_ident s, j) + in + let rec loop i acc = + if i >= n then List.rev (TEOF :: acc) + else if is_space src.[i] then loop (i + 1) acc + else + match src.[i] with + | '/' when i + 1 < n && src.[i + 1] = '/' -> loop (skip_line_comment (i + 2)) acc + | '/' when i + 1 < n && src.[i + 1] = '*' -> loop (skip_block_comment (i + 2)) acc + | '(' -> loop (i + 1) (TLParen :: acc) + | ')' -> loop (i + 1) (TRParen :: acc) + | '{' -> loop (i + 1) (TLBrace :: acc) + | '}' -> loop (i + 1) (TRBrace :: acc) + | '[' -> loop (i + 1) (TLBracket :: acc) + | ']' -> loop (i + 1) (TRBracket :: acc) + | ';' -> loop (i + 1) (TSemicolon :: acc) + | ',' -> loop (i + 1) (TComma :: acc) + | '.' -> loop (i + 1) (TDot :: acc) + | '+' -> loop (i + 1) (TPlus :: acc) + | '-' -> loop (i + 1) (TMinus :: acc) + | '*' -> loop (i + 1) (TStar :: acc) + | '%' -> loop (i + 1) (TPercent :: acc) + | '/' -> loop (i + 1) (TSlash :: acc) + | '!' when i + 1 < n && src.[i + 1] = '=' -> loop (i + 2) (TNe :: acc) + | '!' -> loop (i + 1) (TBang :: acc) + | '=' when i + 1 < n && src.[i + 1] = '=' -> loop (i + 2) (TEqEq :: acc) + | '=' -> loop (i + 1) (TAssign :: acc) + | '&' when i + 1 < n && src.[i + 1] = '&' -> loop (i + 2) (TAndAnd :: acc) + | '|' when i + 1 < n && src.[i + 1] = '|' -> loop (i + 2) (TOrOr :: acc) + | '<' when i + 1 < n && src.[i + 1] = '=' -> loop (i + 2) (TLe :: acc) + | '<' -> loop (i + 1) (TLt :: acc) + | '>' when i + 1 < n && src.[i + 1] = '=' -> loop (i + 2) (TGe :: acc) + | '>' -> loop (i + 1) (TGt :: acc) + | c when is_digit c -> + let tok, j = read_number i (i + 1) in + loop j (tok :: acc) + | c when is_ident_start c -> + let tok, j = read_ident i (i + 1) in + loop j (tok :: acc) + | c -> + let msg = Printf.sprintf "unexpected character: %c" c in + raise (Parse_error msg) + in + loop 0 [] + +type parser_state = { + toks : token array; + mutable i : int; +} + +let mk_state toks = { toks = Array.of_list toks; i = 0 } + +let peek st = if st.i < Array.length st.toks then st.toks.(st.i) else TEOF + +let consume st = + let t = peek st in + st.i <- st.i + 1; + t + +let expect st tok = + match (tok, consume st) with + | TLParen, TLParen + | TRParen, TRParen + | TLBrace, TLBrace + | TRBrace, TRBrace + | TLBracket, TLBracket + | TRBracket, TRBracket + | TSemicolon, TSemicolon + | TComma, TComma + | TDot, TDot + | TAssign, TAssign + | TPlus, TPlus + | TMinus, TMinus + | TStar, TStar + | TSlash, TSlash + | TPercent, TPercent + | TAndAnd, TAndAnd + | TOrOr, TOrOr + | TBang, TBang + | TEqEq, TEqEq + | TNe, TNe + | TLt, TLt + | TLe, TLe + | TGt, TGt + | TGe, TGe + | TIf, TIf + | TElse, TElse + | TForEach, TForEach + | TFor, TFor + | TEach, TEach + | TIn, TIn + | TReturn, TReturn + | TIntKw, TIntKw + | TBoolKw, TBoolKw + | TVoidKw, TVoidKw + | TStructKw, TStructKw + | TEOF, TEOF -> () + | _ -> raise (Parse_error "unexpected token") + +let expect_ident st = + match consume st with + | TIdent s -> s + | _ -> raise (Parse_error "expected identifier") + +let starts_type = function TIntKw | TBoolKw | TVoidKw | TStructKw -> true | _ -> false + +let rec parse_type st = + let base = + match consume st with + | TIntKw -> TInt + | TBoolKw -> TBool + | TVoidKw -> TVoid + | TStructKw -> TStruct (expect_ident st) + | _ -> raise (Parse_error "expected type") + in + let rec arrays t = + match peek st with + | TLBracket -> + expect st TLBracket; + expect st TRBracket; + arrays (TArray t) + | _ -> t + in + arrays base + +let rec parse_program st = + let rec loop acc = + match peek st with + | TEOF -> List.rev acc + | _ -> loop (parse_top st :: acc) + in + loop [] + +and parse_top st = + match peek st with + | TStructKw -> + expect st TStructKw; + let sname = expect_ident st in + (match peek st with + | TLBrace -> + expect st TLBrace; + let rec fields acc = + match peek st with + | TRBrace -> List.rev acc + | _ -> + let t = parse_type st in + let n = expect_ident st in + expect st TSemicolon; + fields ((t, n) :: acc) + in + let fs = fields [] in + expect st TRBrace; + expect st TSemicolon; + TopStruct { sname; fields = fs } + | _ -> + let ty = TStruct sname in + parse_top_after_type st ty) + | _ -> + let ty = parse_type st in + parse_top_after_type st ty + +and parse_top_after_type st ty = + let name = expect_ident st in + match peek st with + | TLParen -> + expect st TLParen; + let params = parse_params st in + expect st TRParen; + let body = parse_stmt_as_block st in + TopFunc { name; params; ret = ty; body } + | _ -> + let init = + match peek st with + | TAssign -> + expect st TAssign; + Some (parse_expr st) + | _ -> None + in + expect st TSemicolon; + TopGlobalVar (ty, name, init) + +and parse_params st = + match peek st with + | TRParen -> [] + | _ -> + let rec loop acc = + let t = parse_type st in + let n = expect_ident st in + match peek st with + | TComma -> + expect st TComma; + loop ((t, n) :: acc) + | _ -> List.rev ((t, n) :: acc) + in + loop [] + +and parse_stmt_as_block st = + match peek st with + | TLBrace -> + expect st TLBrace; + let rec loop acc = + match peek st with + | TRBrace -> + expect st TRBrace; + List.rev acc + | _ -> loop (parse_stmt st :: acc) + in + loop [] + | _ -> [ parse_stmt st ] + +and parse_stmt st = + match peek st with + | TLBrace -> Block (parse_stmt_as_block st) + | TIf -> + expect st TIf; + expect st TLParen; + let cond = parse_expr st in + expect st TRParen; + let then_body = parse_stmt_as_block st in + let else_body = + match peek st with + | TElse -> + expect st TElse; + parse_stmt_as_block st + | _ -> [] + in + If (cond, then_body, else_body) + | TForEach | TFor -> + (match peek st with + | TForEach -> expect st TForEach + | TFor -> + expect st TFor; + expect st TEach + | _ -> ()); + expect st TLParen; + let it_t = parse_type st in + let it_name = expect_ident st in + expect st TIn; + let iterable = parse_expr st in + expect st TRParen; + let body = parse_stmt_as_block st in + ForEach (it_t, it_name, iterable, body) + | TReturn -> + expect st TReturn; + let v = + match peek st with + | TSemicolon -> None + | _ -> Some (parse_expr st) + in + expect st TSemicolon; + Return v + | t when starts_type t -> + let ty = parse_type st in + let n = expect_ident st in + let init = + match peek st with + | TAssign -> + expect st TAssign; + Some (parse_expr st) + | _ -> None + in + expect st TSemicolon; + VarDecl (ty, n, init) + | _ -> + let e = parse_expr st in + expect st TSemicolon; + Expr e + +and parse_expr st = parse_assignment st + +and parse_assignment st = + let lhs = parse_or st in + match peek st with + | TAssign -> + expect st TAssign; + let rhs = parse_assignment st in + Assign (lhs, rhs) + | _ -> lhs + +and parse_or st = + let rec loop left = + match peek st with + | TOrOr -> + expect st TOrOr; + loop (Binop (Or, left, parse_and st)) + | _ -> left + in + loop (parse_and st) + +and parse_and st = + let rec loop left = + match peek st with + | TAndAnd -> + expect st TAndAnd; + loop (Binop (And, left, parse_equality st)) + | _ -> left + in + loop (parse_equality st) + +and parse_equality st = + let rec loop left = + match peek st with + | TEqEq -> + expect st TEqEq; + loop (Binop (Eq, left, parse_rel st)) + | TNe -> + expect st TNe; + loop (Binop (Ne, left, parse_rel st)) + | _ -> left + in + loop (parse_rel st) + +and parse_rel st = + let rec loop left = + match peek st with + | TLt -> + expect st TLt; + loop (Binop (Lt, left, parse_add st)) + | TLe -> + expect st TLe; + loop (Binop (Le, left, parse_add st)) + | TGt -> + expect st TGt; + loop (Binop (Gt, left, parse_add st)) + | TGe -> + expect st TGe; + loop (Binop (Ge, left, parse_add st)) + | _ -> left + in + loop (parse_add st) + +and parse_add st = + let rec loop left = + match peek st with + | TPlus -> + expect st TPlus; + loop (Binop (Add, left, parse_mul st)) + | TMinus -> + expect st TMinus; + loop (Binop (Sub, left, parse_mul st)) + | _ -> left + in + loop (parse_mul st) + +and parse_mul st = + let rec loop left = + match peek st with + | TStar -> + expect st TStar; + loop (Binop (Mul, left, parse_unary st)) + | TSlash -> + expect st TSlash; + loop (Binop (Div, left, parse_unary st)) + | TPercent -> + expect st TPercent; + loop (Binop (Mod, left, parse_unary st)) + | _ -> left + in + loop (parse_unary st) + +and parse_unary st = + match peek st with + | TMinus -> + expect st TMinus; + Unop (Neg, parse_unary st) + | TBang -> + expect st TBang; + Unop (Not, parse_unary st) + | _ -> parse_postfix st + +and parse_postfix st = + let rec loop e = + match peek st with + | TLParen -> + expect st TLParen; + let args = parse_args st in + expect st TRParen; + loop (Call (e, args)) + | TLBracket -> + expect st TLBracket; + let idx = parse_expr st in + expect st TRBracket; + loop (ArrayGet (e, idx)) + | TDot -> + expect st TDot; + let fld = expect_ident st in + loop (StructGet (e, fld)) + | _ -> e + in + loop (parse_primary st) + +and parse_args st = + match peek st with + | TRParen -> [] + | _ -> + let rec loop acc = + let e = parse_expr st in + match peek st with + | TComma -> + expect st TComma; + loop (e :: acc) + | _ -> List.rev (e :: acc) + in + loop [] + +and parse_primary st = + match consume st with + | TIntLit n -> IntLit n + | TTrue -> BoolLit true + | TFalse -> BoolLit false + | TIdent s -> Var s + | TLParen -> + let e = parse_expr st in + expect st TRParen; + e + | _ -> raise (Parse_error "expected expression") + +let parse_string src = + try + let st = mk_state (lex src) in + Ok (parse_program st) + with Parse_error msg -> Error msg + +type func_sig = { + fparams : typ list; + fret : typ; +} + +type tc_ctx = { + structs : (typ StringMap.t) StringMap.t; + funcs : func_sig StringMap.t; + globals : typ StringMap.t; +} + +let fail_type msg = raise (Type_error msg) + +let expect_type got want = + if not (equal_typ got want) then + fail_type (Printf.sprintf "type mismatch: got %s, expected %s" (string_of_typ got) (string_of_typ want)) + +let rec validate_type (structs : (typ StringMap.t) StringMap.t) (allow_void : bool) = function + | TVoid when allow_void -> () + | TVoid -> fail_type "void is not a valid variable type" + | TStruct n -> + if not (StringMap.mem n structs) then fail_type ("unknown struct type: " ^ n) + | TArray t -> + if equal_typ t TVoid then fail_type "array element type cannot be void"; + validate_type structs false t + | TInt | TBool -> () + +let collect_ctx (prog : program) : tc_ctx = + let rec collect tops structs funcs globals = + match tops with + | [] -> { structs; funcs; globals } + | TopStruct s :: tl -> + if StringMap.mem s.sname structs then fail_type ("duplicate struct: " ^ s.sname); + let fields = + List.fold_left + (fun acc (t, n) -> + if StringMap.mem n acc then fail_type ("duplicate field " ^ n ^ " in struct " ^ s.sname); + StringMap.add n t acc) + StringMap.empty s.fields + in + collect tl (StringMap.add s.sname fields structs) funcs globals + | TopFunc f :: tl -> + if StringMap.mem f.name funcs then fail_type ("duplicate function: " ^ f.name); + let sig_ = { fparams = List.map fst f.params; fret = f.ret } in + collect tl structs (StringMap.add f.name sig_ funcs) globals + | TopGlobalVar (t, n, _) :: tl -> + if StringMap.mem n globals then fail_type ("duplicate global variable: " ^ n); + collect tl structs funcs (StringMap.add n t globals) + in + collect prog StringMap.empty StringMap.empty StringMap.empty + +let lookup_var env x = + match StringMap.find_opt x env with + | Some t -> t + | None -> fail_type ("unknown variable: " ^ x) + +let lookup_struct_field ctx sname fname = + match StringMap.find_opt sname ctx.structs with + | None -> fail_type ("unknown struct: " ^ sname) + | Some fields -> + (match StringMap.find_opt fname fields with + | None -> fail_type ("unknown field " ^ fname ^ " on struct " ^ sname) + | Some t -> t) + +let rec infer_expr ctx env = function + | IntLit _ -> TInt + | BoolLit _ -> TBool + | Var x -> lookup_var env x + | Unop (Neg, e) -> + expect_type (infer_expr ctx env e) TInt; + TInt + | Unop (Not, e) -> + expect_type (infer_expr ctx env e) TBool; + TBool + | Binop (op, a, b) -> + let ta = infer_expr ctx env a in + let tb = infer_expr ctx env b in + (match op with + | Add | Sub | Mul | Div | Mod -> + expect_type ta TInt; + expect_type tb TInt; + TInt + | And | Or -> + expect_type ta TBool; + expect_type tb TBool; + TBool + | Lt | Le | Gt | Ge -> + expect_type ta TInt; + expect_type tb TInt; + TBool + | Eq | Ne -> + if not (equal_typ ta tb) then fail_type "equality operands must have same type"; + TBool) + | Assign (lhs, rhs) -> + (match lhs with Var _ | ArrayGet _ | StructGet _ -> () | _ -> fail_type "left side of assignment is not assignable"); + let tl = infer_expr ctx env lhs in + let tr = infer_expr ctx env rhs in + expect_type tr tl; + tl + | ArrayGet (arr, idx) -> + expect_type (infer_expr ctx env idx) TInt; + (match infer_expr ctx env arr with + | TArray t -> t + | t -> fail_type ("indexing requires array, got " ^ string_of_typ t)) + | StructGet (obj, fld) -> + (match infer_expr ctx env obj with + | TStruct sname -> lookup_struct_field ctx sname fld + | t -> fail_type ("field access requires struct, got " ^ string_of_typ t)) + | Call (callee, args) -> + let fname = + match callee with + | Var n -> n + | _ -> fail_type "only direct function calls are supported" + in + let sig_ = + match StringMap.find_opt fname ctx.funcs with + | Some s -> s + | None -> fail_type ("unknown function: " ^ fname) + in + if List.length args <> List.length sig_.fparams then + fail_type + (Printf.sprintf "function %s expects %d arguments, got %d" fname (List.length sig_.fparams) + (List.length args)); + List.iter2 (fun arg pty -> expect_type (infer_expr ctx env arg) pty) args sig_.fparams; + sig_.fret + +let rec check_stmt ctx ret env = function + | VarDecl (t, n, init) -> + validate_type ctx.structs false t; + (match init with None -> () | Some e -> expect_type (infer_expr ctx env e) t); + StringMap.add n t env + | Expr e -> + ignore (infer_expr ctx env e); + env + | If (cond, tbranch, ebranch) -> + expect_type (infer_expr ctx env cond) TBool; + ignore (check_block ctx ret env tbranch); + ignore (check_block ctx ret env ebranch); + env + | ForEach (it_t, it_name, iterable, body) -> + validate_type ctx.structs false it_t; + (match infer_expr ctx env iterable with + | TArray elem_t -> expect_type elem_t it_t + | t -> fail_type ("foreach expects array iterable, got " ^ string_of_typ t)); + let env' = StringMap.add it_name it_t env in + ignore (check_block ctx ret env' body); + env + | Return None -> + expect_type TVoid ret; + env + | Return (Some e) -> + expect_type (infer_expr ctx env e) ret; + env + | Block stmts -> + ignore (check_block ctx ret env stmts); + env + +and check_block ctx ret env stmts = List.fold_left (check_stmt ctx ret) env stmts + +let rec has_return_stmt = function + | Return _ -> true + | If (_, t, e) -> List.exists has_return_stmt t || List.exists has_return_stmt e + | ForEach (_, _, _, body) | Block body -> List.exists has_return_stmt body + | VarDecl _ | Expr _ -> false + +let check_program (prog : program) = + let ctx = collect_ctx prog in + StringMap.iter (fun _ t -> validate_type ctx.structs false t) ctx.globals; + StringMap.iter + (fun _ sig_ -> + List.iter (validate_type ctx.structs false) sig_.fparams; + validate_type ctx.structs true sig_.fret) + ctx.funcs; + List.iter + (function + | TopStruct s -> + List.iter (fun (t, _) -> validate_type ctx.structs false t) s.fields + | TopGlobalVar (t, _, init) -> + validate_type ctx.structs false t; + let env = ctx.globals in + (match init with None -> () | Some e -> expect_type (infer_expr ctx env e) t) + | TopFunc f -> + let env_with_globals = ctx.globals in + let env = + List.fold_left + (fun acc (t, n) -> + validate_type ctx.structs false t; + if StringMap.mem n acc then fail_type ("duplicate parameter name: " ^ n); + StringMap.add n t acc) + env_with_globals f.params + in + ignore (check_block ctx f.ret env f.body); + if (not (equal_typ f.ret TVoid)) && not (List.exists has_return_stmt f.body) then + fail_type ("non-void function " ^ f.name ^ " must return a value")) + prog + +let type_check (prog : program) = + try + check_program prog; + Ok () + with Type_error msg -> Error msg + +let parse_and_type_check src = + match parse_string src with + | Error e -> Error ("Parse error: " ^ e) + | Ok prog -> + (match type_check prog with + | Error e -> Error ("Type error: " ^ e) + | Ok () -> Ok prog) diff --git a/lib/spooky.mli b/lib/spooky.mli new file mode 100644 index 0000000..a62ed34 --- /dev/null +++ b/lib/spooky.mli @@ -0,0 +1,66 @@ +type typ = + | TInt + | TBool + | TVoid + | TStruct of string + | TArray of typ + +type binop = + | Add + | Sub + | Mul + | Div + | Mod + | And + | Or + | Eq + | Ne + | Lt + | Le + | Gt + | Ge + +type unop = Neg | Not + +type expr = + | IntLit of int + | BoolLit of bool + | Var of string + | Binop of binop * expr * expr + | Unop of unop * expr + | Assign of expr * expr + | Call of expr * expr list + | ArrayGet of expr * expr + | StructGet of expr * string + +type stmt = + | VarDecl of typ * string * expr option + | Expr of expr + | If of expr * stmt list * stmt list + | ForEach of typ * string * expr * stmt list + | Return of expr option + | Block of stmt list + +type func = { + name : string; + params : (typ * string) list; + ret : typ; + body : stmt list; +} + +type struct_def = { + sname : string; + fields : (typ * string) list; +} + +type top = + | TopStruct of struct_def + | TopFunc of func + | TopGlobalVar of typ * string * expr option + +type program = top list + +val string_of_typ : typ -> string +val parse_string : string -> (program, string) result +val type_check : program -> (unit, string) result +val parse_and_type_check : string -> (program, string) result diff --git a/test/test_spooky.ml b/test/test_spooky.ml index e69de29..37c3639 100644 --- a/test/test_spooky.ml +++ b/test/test_spooky.ml @@ -0,0 +1,52 @@ +let valid_program = + {| +struct Item { + int value; +}; + +int fold(int[] xs) { + int total = 0; + foreach (int x in xs) { + total = total + x; + } + return total; +} + +int main() { + int[] xs; + struct Item it; + int y = 2 + 3 * 4; + it.value = y; + if (y >= 0) { + y = fold(xs); + } else { + y = 0; + } + return y; +} +|} + +let invalid_program = + {| +int main() { + bool flag = true; + int x = 1; + x = flag; + return x; +} +|} + +let test_valid_program () = + match Spooky.parse_and_type_check valid_program with + | Ok _ -> () + | Error msg -> failwith ("expected valid program, got: " ^ msg) + +let test_invalid_program () = + match Spooky.parse_and_type_check invalid_program with + | Ok _ -> failwith "expected type error, but got success" + | Error _ -> () + +let () = + test_valid_program (); + test_invalid_program (); + print_endline "All parser/type-check tests passed."