-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathBind.fs
More file actions
192 lines (169 loc) · 7.32 KB
/
Bind.fs
File metadata and controls
192 lines (169 loc) · 7.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
module Mutton.Binder
open Mutton.Illuminate
open Mutton.Utils
open System.Collections.Generic
/// A named storage location with a unique numeric suffix for scope tracking.
/// The integer disambiguates variables with the same base name in different scopes.
[<StructuredFormatDisplay("{DisplayText}")>]
type Storage =
| S of string * int
member private this.DisplayText =
let (S (name, suffix)) = this
sprintf "%s.%d" name suffix
override this.ToString() = this.DisplayText
/// A simple s-expression datum. This is the runtime representation of
/// quoted syntax — compiler metadata (scope IDs, CST pointers) is stripped
/// away, leaving only the structural content.
type Datum =
| DNum of int
| DSym of string
| DList of Datum list
/// Bound Expression Node
///
/// Each variant represents a different kind of bound node available in the
/// tree. Bound nodes represent the _semantic_ structure of the program rather
/// than the syntactic one. Any symbols in the program are resolved to their
/// appropriate storage locations.
type Bound =
| Var of Storage
| Def of Storage * Bound
| App of Bound * Bound list
| Fun of Storage * Bound
| Stx of Stx
| Quot of Datum
/// Errors that can occur during binding. These are simple string messages with
/// attached source ranges. In a more complex implementation, these would likely
/// be richer structures with error codes, suggestions, etc.
type BinderError = string * Firethorn.TextRange
/// A simple binding context. This is used to resolve identifiers to their
/// bound names. In a more complex language, this would need to be extended to
/// support multiple namespaces, modules, etc.
type private BindingContext =
{
Parent: BindingContext option
Bindings: Dictionary<Ident, Storage>
}
module private BinderCtx =
/// Create an empty binding context
let empty = { Parent = None; Bindings = Dictionary<Ident, Storage>() }
/// Extend a binding context with a new name mapping
let extend ident bound ctx =
// Use indexer assignment to avoid exceptions on duplicate keys.
// This allows redefinition in the same scope without crashing.
ctx.Bindings.[ident] <- bound
/// Create a new child context with the given parent
let withParent parent =
{ Parent = Some parent; Bindings = Dictionary<Ident, Storage>() }
/// Resolve a name in the given context, searching parent contexts if
/// necessary. Returns the resolved name or the original name if not found.
let rec resolve ident ctx =
match ctx.Bindings.TryGetValue ident with
| true, bound -> bound
| false, _ ->
match ctx.Parent with
| Some parent -> resolve ident parent
| None -> S(ident.Name, 0)
// ── Binding ────────────────────────────────────────────────────────────────
let mutable private varSuffix = ref 0
/// Generate a fresh storage location for the given variable name
let private freshStorage (name: string) : Storage =
let suffix = System.Threading.Interlocked.Increment(varSuffix)
S(name, suffix)
/// Strip an illuminated syntax node down to a plain datum, discarding all
/// compiler metadata (scope IDs, CST node references).
let rec private strip (stx: Stx) : Datum =
match stx with
| StxLiteral l ->
match l.Value with
| Some n -> DNum n
| None -> DSym "#err"
| StxIdent(id, _) -> DSym id.Name
| StxForm(items, _) -> DList(List.map strip items)
| StxClosure(stx, _) -> strip stx
/// Bind a single expression
let rec private bindOne (ctx: BindingContext) (stxEnv: StxEnv) =
function
| StxLiteral l ->
match l.Value with
| Some n -> Quot(DNum n) |> Some |> Ok
| None -> ($"Invalid literal %A{l}", l.Syntax.Range) |> Error
| StxIdent(id, _) ->
Var(BinderCtx.resolve id ctx) |> Some |> Ok
| StxForm(items, f) ->
match items with
| [] -> Error ($"No applicant in application form.", f.Syntax.Range)
| StxIdent(id, sym) :: args ->
match Expand.resolve id stxEnv with
| Illuminate.Lam -> bindLambda ctx stxEnv f args
| Illuminate.Def -> bindDefinition ctx stxEnv f args
| Illuminate.Quot -> bindQuotation f args
| Illuminate.Stx -> bindSyntaxQuotation f args
| _ -> bindSimpleApp ctx stxEnv f (StxIdent(id, sym)) args
| applicant :: args ->
bindSimpleApp ctx stxEnv f applicant args
| StxClosure(stx, env) -> bindOne ctx env stx
and private bindSimpleApp ctx stxEnv f applicant args =
bindOne ctx stxEnv applicant
|> Result.bind (fun called ->
args
|> List.map (bindOne ctx stxEnv)
|> accumulateResults
|> Result.bind (fun boundArgs ->
match called with
| Some called ->
App(called, (List.choose id boundArgs)) |> Some |> Ok
| None -> Error ($"Invalid function in application: has no value.", f.Syntax.Range)))
and private bindLambda ctx stxEnv f args =
match args with
| [ StxIdent(id, _); body ] ->
let argStorage = freshStorage id.Name
let bodyCtx =
BinderCtx.withParent ctx
BinderCtx.extend id argStorage bodyCtx
match bindOne bodyCtx stxEnv body with
| Ok(Some boundBody) ->
Fun(argStorage, boundBody) |> Some |> Ok
| Ok None ->
Error ($"Body has no value.", f.Syntax.Range)
| e -> e
| _ -> Error ($"Invalid lambda form %A{args}", f.Syntax.Range)
and private bindDefinition ctx stxEnv f args =
match args with
| [ StxIdent(id, _); body ] ->
match bindOne ctx stxEnv body with
| Ok(Some boundBody) ->
let newStore = freshStorage id.Name
BinderCtx.extend id newStore ctx
Def(newStore, boundBody) |> Some |> Result.Ok
| Ok None ->
Error ($"Invalid `def` body: has no value.", f.Syntax.Range)
| e -> e
| _ -> Error ($"Invalid `def` form %A{args}", f.Syntax.Range)
and private bindQuotation f args =
match args with
| [ form ] -> Quot(strip form) |> Some |> Result.Ok
| _ -> Error ($"Invalid `quot` form: %A{args}", f.Syntax.Range)
and private bindSyntaxQuotation f args =
match args with
| [ form ] -> Stx form |> Some |> Result.Ok
| _ -> Error ($"Invalid `stx` form: %A{args}", f.Syntax.Range)
let rec private bindSequence (ctx: BindingContext) (stxEnv: StxEnv) (exprs: Stx list) : Result<Bound list, string * Firethorn.TextRange> =
match exprs with
| [] -> Ok []
| stx :: rest ->
match Expand.expand stx stxEnv with
| None, newStxEnv ->
// def-syn: no runtime value produced; continue with updated env
bindSequence ctx newStxEnv rest
| Some expanded, newStxEnv ->
bindOne ctx newStxEnv expanded
|> Result.bind (fun bound ->
match bound with
| None -> bindSequence ctx newStxEnv rest
| Some b ->
bindSequence ctx newStxEnv rest
|> Result.map (fun bs -> b :: bs))
/// Main binder entry point. Runs the binder over each input item
let public bind =
let ctx = BinderCtx.empty
bindSequence ctx Map.empty