scope analysis: created another type to statically eliminate one (im)possible error case
ci/woodpecker/push/debian Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/nix Pipeline was successful
ci/woodpecker/push/fedora Pipeline was successful
ci/woodpecker/cron/publish Pipeline failed
ci/woodpecker/cron/debian Pipeline was successful
ci/woodpecker/cron/nix Pipeline was successful
ci/woodpecker/cron/fedora Pipeline was successful

This commit is contained in:
2026-04-05 23:14:29 +03:00
parent 39ed14b33d
commit 757df161d1
2 changed files with 29 additions and 25 deletions
+25 -21
View File
@@ -8,28 +8,35 @@ let traverse = Util.traverse
(* literals are not modified. *) (* literals are not modified. *)
type literal = Core_ast.literal type literal = Core_ast.literal
(* I made this a separate type, because this behaviour is common to both symbol
accesses, and to set! operations on symbols.
They can both either refer to a local, or refer to a global, and making a
separate type for this lets us statically eliminate a couple potential
runtime errors
*)
type variable =
| Local of int
| Global of int
(* Note: (* Note:
all symbol accesses are replaced with either a local or global access. all symbol accesses are either referring to a local binding or a global one,
Local accesses a symbol in the local scope. and this is distinguished through the variable type above.
Global accesses a symbol in the global scope.
Lambda expressions are stripped of the symbol name of their single parameter. Lambda expressions are stripped of the symbol name of their single parameter.
This name is not needed at runtime, as all symbol accesses will be resolved This name is not needed at runtime, as all symbol accesses will be resolved
into an index into either the local scope linked list or the global symbol table. into an index into either the local scope linked list or the global symbol table.
Set is also split into its global and local versions, just like Var. Set is also split into its global and local versions, using the above variable type.
The rest aren't modified at all. The rest aren't modified at all.
*) *)
type expression = type expression =
| Literal of literal | Literal of literal
| Local of int | Var of variable
| Global of int
| Apply of expression * expression | Apply of expression * expression
| Lambda of expression | Lambda of expression
| If of expression * expression * expression | If of expression * expression * expression
| SetLocal of int * expression | Set of variable * expression
| SetGlobal of int * expression
| Begin of expression list | Begin of expression list
@@ -64,23 +71,23 @@ let resolve_global tbl sym =
| Some x -> Ok (Global x) | Some x -> Ok (Global x)
| None -> Error ("symbol " ^ sym ^ " is not defined!") | None -> Error ("symbol " ^ sym ^ " is not defined!")
let resolve_lexical tbl env sym = (* First we try to resolve it to a local symbol, then look it up in the
global table if we can't find it in the local environment
*)
let resolve_symbol tbl env sym =
let rec aux counter = function let rec aux counter = function
| [] -> resolve_global tbl sym | [] -> resolve_global tbl sym
| x :: _ when String.equal x sym -> Ok (Local counter) | x :: _ when String.equal x sym -> Ok (Local counter)
| _ :: rest -> aux (counter + 1) rest | _ :: rest -> aux (counter + 1) rest
in aux 0 env in aux 0 env
let resolve_symbol tbl env sym = let resolve_var tbl env sym =
resolve_lexical tbl env sym let* sym = resolve_symbol tbl env sym in
Ok (Var sym)
let resolve_set tbl env sym expr = let resolve_set tbl env sym expr =
let* sym = resolve_symbol tbl env sym in let* sym = resolve_symbol tbl env sym in
match sym with Ok (Set (sym, expr))
| Local i -> Ok (SetLocal (i, expr))
| Global i -> Ok (SetGlobal (i, expr))
| _ -> Error "resolve_set: symbol resolution returned something invalid."
(* We need to do some more sophisticated analysis to detect cases where (* We need to do some more sophisticated analysis to detect cases where
a symbol is accessed before it is defined. a symbol is accessed before it is defined.
If a symbol is accessed in a lambda body, that is fine, since that computation If a symbol is accessed in a lambda body, that is fine, since that computation
@@ -102,12 +109,9 @@ let resolve_set tbl env sym expr =
*) *)
let convert program = let convert program =
let global_tbl = extract_globals program in let global_tbl = extract_globals program in
let id_counter = (ref (-1)) in
let id () =
id_counter := !id_counter + 1; !id_counter in
let rec analyze tbl current = function let rec analyze tbl current = function
| Core_ast.Literal s -> Ok (Literal s) | Core_ast.Literal s -> Ok (Literal s)
| Var sym -> resolve_symbol tbl current sym | Var sym -> resolve_var tbl current sym
| Set (sym, expr) -> | Set (sym, expr) ->
let* inner = analyze tbl current expr in let* inner = analyze tbl current expr in
resolve_set tbl current sym inner resolve_set tbl current sym inner
@@ -131,7 +135,7 @@ let convert program =
| [] -> [] | [] -> []
| (Core_ast.Expr e) :: rest -> (analyze tbl [] e) :: (aux tbl rest) | (Core_ast.Expr e) :: rest -> (analyze tbl [] e) :: (aux tbl rest)
| (Define (s, e)) :: rest -> | (Define (s, e)) :: rest ->
let tbl = SymbolTable.add s (id ()) tbl in let tbl = SymbolTable.add s (SymbolTable.find s global_tbl) tbl in
(analyze tbl [] (Set (s, e))) :: (aux tbl rest) (analyze tbl [] (Set (s, e))) :: (aux tbl rest)
in in
let* program = traverse (fun x -> x) (aux SymbolTable.empty program) in let* program = traverse (fun x -> x) (aux SymbolTable.empty program) in
+4 -4
View File
@@ -26,11 +26,11 @@ let rec interpret_literal = function
let rec interpret_one expr env globals = let rec interpret_one expr env globals =
match expr with match expr with
| Compiler.Scope_analysis.Literal l -> interpret_literal l | Compiler.Scope_analysis.Literal l -> interpret_literal l
| Local i -> | Var (Local i) ->
(match (List.nth_opt env i) with (match (List.nth_opt env i) with
| None -> Error "Error while accessing local variable!" | None -> Error "Error while accessing local variable!"
| Some x -> Ok !x) | Some x -> Ok !x)
| Global i -> | Var (Global i) ->
Ok (Array.get globals i) Ok (Array.get globals i)
| Apply (f, e) -> | Apply (f, e) ->
let* f = interpret_one f env globals in let* f = interpret_one f env globals in
@@ -47,13 +47,13 @@ let rec interpret_one expr env globals =
(match test with (match test with
| Nil -> interpret_one else_e env globals | Nil -> interpret_one else_e env globals
| _ -> interpret_one then_e env globals) | _ -> interpret_one then_e env globals)
| SetLocal (i, e) -> | Set ((Local i), e) ->
(match (List.nth_opt env i) with (match (List.nth_opt env i) with
| None -> Error "Error while setting local variable!" | None -> Error "Error while setting local variable!"
| Some r -> | Some r ->
let* e = interpret_one e env globals in let* e = interpret_one e env globals in
r := e; Ok e) r := e; Ok e)
| SetGlobal (i, e) -> | Set ((Global i), e) ->
let* e = interpret_one e env globals in let* e = interpret_one e env globals in
Array.set globals i e; Ok e Array.set globals i e; Ok e
| Begin [] -> Ok Nil | Begin [] -> Ok Nil