feat(interpreter): add session driver with module system
This commit is contained in:
1 parent
9ce5ca1651
commit
940305ce85
1 file changed
+217
@@ -0,0 +1,217 @@
|
||||
module Fml.Core.Interpreter
|
||||
|
||||
open Fml.Core.Ast
|
||||
open Fml.Core.Lexer
|
||||
open Fml.Core.Parser
|
||||
open Fml.Core.Types
|
||||
open Fml.Core.Decls
|
||||
open Fml.Core.Match
|
||||
open Fml.Core.Infer
|
||||
open Fml.Core.Value
|
||||
open Fml.Core.Eval
|
||||
open Fml.Core.Prelude
|
||||
open Fml.Core.Bytecode
|
||||
|
||||
type Backend =
|
||||
| Interp
|
||||
| Vm
|
||||
|
||||
type PhraseResult =
|
||||
{ Name: string
|
||||
TypeText: string
|
||||
Value: Value
|
||||
ValueText: string }
|
||||
|
||||
let parsePhrases (source: string) : Phrase list = source |> tokenize |> parse
|
||||
|
||||
let rec private patternVars (pat: Pattern) : Set<string> =
|
||||
match pat with
|
||||
| PVar x -> Set.singleton x
|
||||
| PWildcard | PInt _ | PBool _ | PString _ | PUnit | PNil -> Set.empty
|
||||
| PTuple ps -> ps |> List.map patternVars |> Set.unionMany
|
||||
| PCons(h, t) -> Set.union (patternVars h) (patternVars t)
|
||||
| PConstruct(_, ps) -> ps |> List.map patternVars |> Set.unionMany
|
||||
| PRecord fields -> fields |> List.map (snd >> patternVars) |> Set.unionMany
|
||||
|
||||
// Rewrites free variables named in mapping to their qualified form, respecting
|
||||
// binders so shadowed names are left alone. Used to give module-internal
|
||||
// references their qualified global keys.
|
||||
let rec renameFreeVars (mapping: Map<string, string>) (bound: Set<string>) (e: Expr) : Expr =
|
||||
let recur b = renameFreeVars mapping b
|
||||
match e with
|
||||
| IntLit _ | BoolLit _ | StringLit _ | UnitLit | Nil -> e
|
||||
| Var x ->
|
||||
if Set.contains x bound then e
|
||||
else
|
||||
match Map.tryFind x mapping with
|
||||
| Some qualified -> Var qualified
|
||||
| None -> e
|
||||
| Lambda(p, body) -> Lambda(p, recur (Set.add p bound) body)
|
||||
| App(fn, arg) -> App(recur bound fn, recur bound arg)
|
||||
| Let(false, name, rhs, body) -> Let(false, name, recur bound rhs, recur (Set.add name bound) body)
|
||||
| Let(true, name, rhs, body) ->
|
||||
let inner = Set.add name bound
|
||||
Let(true, name, recur inner rhs, recur inner body)
|
||||
| LetGroup(isRec, bindings, body) ->
|
||||
let names = bindings |> List.map fst |> Set.ofList
|
||||
let rhsBound = if isRec then Set.union bound names else bound
|
||||
let renamed = bindings |> List.map (fun (n, r) -> n, recur rhsBound r)
|
||||
LetGroup(isRec, renamed, recur (Set.union bound names) body)
|
||||
| If(c, t, f) -> If(recur bound c, recur bound t, recur bound f)
|
||||
| BinOp(op, l, r) -> BinOp(op, recur bound l, recur bound r)
|
||||
| Neg inner -> Neg(recur bound inner)
|
||||
| Tuple items -> Tuple(items |> List.map (recur bound))
|
||||
| Cons(h, t) -> Cons(recur bound h, recur bound t)
|
||||
| Construct(name, args) -> Construct(name, args |> List.map (recur bound))
|
||||
| Record fields -> Record(fields |> List.map (fun (n, v) -> n, recur bound v))
|
||||
| Field(record, name) -> Field(recur bound record, name)
|
||||
| Match(scrut, cases) ->
|
||||
let cases' =
|
||||
cases
|
||||
|> List.map (fun (pat, body) -> pat, recur (Set.union bound (patternVars pat)) body)
|
||||
Match(recur bound scrut, cases')
|
||||
|
||||
type Session(backend: Backend) =
|
||||
do resetDecls ()
|
||||
let mutable typeEnv = initialTypeEnv
|
||||
let mutable runtimeEnv: Env = initialValueEnv
|
||||
let functions = ResizeArray<Instr []>()
|
||||
let mutable moduleMembers: Map<string, string list> = Map.empty
|
||||
|
||||
let evalExpr (e: Expr) : Value =
|
||||
match backend with
|
||||
| Interp -> eval runtimeEnv e
|
||||
| Vm -> Vm.compileAndRun functions runtimeEnv e
|
||||
|
||||
let inferTop (e: Expr) : Ty =
|
||||
currentLevel <- 0
|
||||
enterLevel ()
|
||||
let ty = infer typeEnv e
|
||||
leaveLevel ()
|
||||
generalize ty
|
||||
ty
|
||||
|
||||
let processExpr (e: Expr) : PhraseResult =
|
||||
let ty = inferTop e
|
||||
let v = evalExpr e
|
||||
typeEnv <- Map.add "it" ty typeEnv
|
||||
runtimeEnv <- Map.add "it" v runtimeEnv
|
||||
{ Name = "it"
|
||||
TypeText = prettyTy ty
|
||||
Value = v
|
||||
ValueText = prettyValue v }
|
||||
|
||||
let processLet (isRec: bool) (bindings: (string * Expr) list) : PhraseResult list =
|
||||
currentLevel <- 0
|
||||
let newTypeEnv = inferLetGroup typeEnv isRec bindings
|
||||
let values =
|
||||
match backend with
|
||||
| Interp ->
|
||||
let newEnv = evalLetGroup runtimeEnv isRec bindings
|
||||
runtimeEnv <- newEnv
|
||||
bindings |> List.map (fun (n, _) -> n, Map.find n newEnv)
|
||||
| Vm ->
|
||||
let vs = bindings |> List.map (fun (n, rhs) -> n, Vm.compileAndRun functions runtimeEnv rhs)
|
||||
runtimeEnv <- vs |> List.fold (fun e (n, v) -> Map.add n v e) runtimeEnv
|
||||
vs
|
||||
typeEnv <- newTypeEnv
|
||||
bindings
|
||||
|> List.map (fun (n, _) ->
|
||||
let ty = Map.find n newTypeEnv
|
||||
let v = values |> List.find (fun (m, _) -> m = n) |> snd
|
||||
{ Name = n
|
||||
TypeText = prettyTy ty
|
||||
Value = v
|
||||
ValueText = prettyValue v })
|
||||
|
||||
let processOpen (name: string) =
|
||||
match Map.tryFind name moduleMembers with
|
||||
| Some members ->
|
||||
for m in members do
|
||||
let qualified = name + "." + m
|
||||
typeEnv <- Map.add m (Map.find qualified typeEnv) typeEnv
|
||||
runtimeEnv <- Map.add m (Map.find qualified runtimeEnv) runtimeEnv
|
||||
| None -> raise (TypeError(sprintf "unknown module '%s'" name))
|
||||
|
||||
let rec processModule (name: string) (inner: Phrase list) =
|
||||
let memberNames =
|
||||
inner
|
||||
|> List.collect (fun ph ->
|
||||
match ph with
|
||||
| PhraseLet(_, bindings) -> bindings |> List.map fst
|
||||
| _ -> [])
|
||||
let mapping = memberNames |> List.map (fun n -> n, name + "." + n) |> Map.ofList
|
||||
for ph in inner do
|
||||
match ph with
|
||||
| PhraseType decls -> decls |> List.iter registerDecl
|
||||
| PhraseOpen m -> processOpen m
|
||||
| PhraseLet(isRec, bindings) ->
|
||||
let qualified =
|
||||
bindings |> List.map (fun (n, rhs) -> name + "." + n, renameFreeVars mapping Set.empty rhs)
|
||||
processLet isRec qualified |> ignore
|
||||
| PhraseExpr e -> processExpr (renameFreeVars mapping Set.empty e) |> ignore
|
||||
| PhraseModule _ -> raise (ParseError "nested modules are not supported")
|
||||
moduleMembers <- Map.add name memberNames moduleMembers
|
||||
|
||||
let processPhrase (ph: Phrase) : PhraseResult list =
|
||||
match ph with
|
||||
| PhraseExpr e -> [ processExpr e ]
|
||||
| PhraseLet(isRec, bindings) -> processLet isRec bindings
|
||||
| PhraseType decls -> decls |> List.iter registerDecl; []
|
||||
| PhraseOpen name -> processOpen name; []
|
||||
| PhraseModule(name, inner) -> processModule name inner; []
|
||||
|
||||
new() = Session(Interp)
|
||||
|
||||
member _.TypeEnv = typeEnv
|
||||
member _.ValueEnv = runtimeEnv
|
||||
member _.RunPhrase(ph: Phrase) : PhraseResult list = processPhrase ph
|
||||
|
||||
member this.Feed(source: string) : PhraseResult list =
|
||||
source |> parsePhrases |> List.collect this.RunPhrase
|
||||
|
||||
member this.LoadPrelude() : unit = this.Feed preludeSource |> ignore
|
||||
|
||||
let private lastResult (results: PhraseResult list) : PhraseResult =
|
||||
match List.tryLast results with
|
||||
| Some r -> r
|
||||
| None -> raise (RuntimeError "program produced no result")
|
||||
|
||||
let runProgram (source: string) : PhraseResult list =
|
||||
let session = Session(Interp)
|
||||
session.Feed source
|
||||
|
||||
let typeOf (source: string) : string = (lastResult (runProgram source)).TypeText
|
||||
|
||||
let evalString (source: string) : Value = (lastResult (runProgram source)).Value
|
||||
|
||||
let evalToString (source: string) : string = prettyValue (evalString source)
|
||||
|
||||
let tryTypeOf (source: string) : Result<string, string> =
|
||||
try
|
||||
Ok(typeOf source)
|
||||
with
|
||||
| TypeError m -> Error m
|
||||
| MatchError m -> Error m
|
||||
| ParseError m -> Error m
|
||||
| LexError m -> Error m
|
||||
|
||||
// Runs a program on a chosen backend, optionally on top of the fml standard
|
||||
// library, returning the final phrase value.
|
||||
let evalOn (backend: Backend) (loadPrelude: bool) (source: string) : Value =
|
||||
let session = Session(backend)
|
||||
if loadPrelude then session.LoadPrelude()
|
||||
lastResult(session.Feed source).Value
|
||||
|
||||
let evalInterp (source: string) : Value = evalOn Interp false source
|
||||
let evalVm (source: string) : Value = evalOn Vm false source
|
||||
|
||||
let tryEval (source: string) : Result<Value, string> =
|
||||
try
|
||||
Ok(evalInterp source)
|
||||
with
|
||||
| TypeError m -> Error m
|
||||
| MatchError m -> Error m
|
||||
| RuntimeError m -> Error m
|
||||
| ParseError m -> Error m
|
||||
| LexError m -> Error m
|
||||
Reference in new issue
Block a user