-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDESIGN
324 lines (245 loc) · 8.19 KB
/
DESIGN
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
=== Introduction
This document is supposed to be an overview of the current overall design,
not to be used for implementation details or specific algorithms.
Feel free to discuss or amend any portion of this document.
=== The vision
To implement as much as possible in scheme; as little as possible in Go.
One of the reasons why (syntax-rules) was invented was to allow this.
Many parts of scheme expand to fundamental forms, such as: and, or,
case, cond, when, unless, etc. We should not implement these in Go.
However, pragmatism can be a good thing, so in a conflict between
generality and speed, speed should win.
=== The lexer
Traditionally, lisp and scheme lexers are customizable, allowing '#'
to be overridden to add syntax to the language, and while this may be
useful at some point, we may want a staged approach where this is
available to the end-user through some kind of preprocessing. I
don't think we should have full readtable support like in CL.
We might do something like replace number forms with procedures like this:
#i... => (exact->inexact ...)
#e... => (inexact->exact ...)
#b... => (string->integer ... 2)
#o... => (string->integer ... 8)
#x... => (string->integer ... 16)
...+... => (make-rectangular ... ...)
...@... => (make-polar ... ...)
.../... => (make/ ... ...)
before it gets to the parser, so as to simplify the grammar...
=== The parser
This would be implemented in "parse.y" and so should take the output
of the lexer and parse it.
=== The program-interpreter
Must have current-environment, and a list of imported libraries,
and might even split the environment into 2 sections, one for
procedures and one for syntax, but I don't think thats a good idea.
Procedures should be evaluated using the same method as (eval),
and could either be implemented in Scheme or the FFI (see below).
=== The library-installer
This might be too early to think about libraries, the first few versions
should have some kind of builtin library for testing purposes.
Both R6RS/R7RS require that programs either
- (import (rnrs)) ; R6RS
- (import (scheme)) ; R7RS
to get the full functionality of the basic language.
The end goal should be a base language with only the following symbols:
- define
- if
- import
- lambda
- quote
- set!
available, until one of the above is imported. I'm not sure if it is
possible, but I'd like to find a way to implement quasiquote, unquote,
and unquote-splicing using some kind of scheme code that's
run on the source tree at some point.
Another thing to consider is that there are hundreds of library syntaxes.
The way that Racket (DrScheme/MzScheme) solves this is it has a reader
syntax "#%module-begin" that is automatically wrapped around a library
before it is compiled/loaded. Any sub-language can write their own
definition of this syntax in order to make their library syntax conform
to Racket's builtin library syntax. Also, since Scheme has only had
libraries since R6RS, it is still evolving:
- (library (my lib) ; R6RS
(export f ...)
(import ...)
(define (f x) ...))
- (define-library (my lib) ; R7RS
(export f ...)
(import ...)
(begin
(define (f x) ...)))
These two library syntaxes could be mapped to a droscheme-specific syntax
or we could pick one and convert the other to it.
Official description:
; R6RS
(library <library name>
(export <export spec> ...)
(import <import spec> ...)
<library body>)
<export spec> =
<identifier>
(rename (<identifier1> <identifier2>) ...) -- I don't think we should support this
<import spec> =
<import set>
(for <import set> <import level> ...) -- I don't think we should support this
<import set> =
<library name> -- must support
<library reference> -- OMG so complicated, no version support
(library <library reference>) -- I don't think we should support this
(only <import set> <identifier> ...)
(except <import set> <identifier> ...)
(prefix <import set> <identifier> ...)
(rename <import set> (<identifier1> <identifier2>) ...)
; R7RS
(define-library <library name>
<library declaration> ...)
<library declaration> =
(export <export spec> ...)
(import <import spec> ...)
(begin <library body>)
(include <filename> ...)
(include-ci <filename> ...)
(cond-expand <cond-expand clause> ...)
<export spec> =
<identifier>
(rename <identifier1> <identifier2>) -- I don't think we should support this
<import spec> =
<import set>
<import set> =
<library name>
(only <import set> <identifier> ...)
(except <import set> <identifier> ...)
(prefix <import set> <identifier>)
(rename <import set> (<identifier1> <identifier2>) ...)
For example:
(import
(prefix
(rename (only (scheme base) = < <= > >=)
(= =?) (< <?) (<= <=?) (> >?) (>= >=?))
fx))
would import the identifiers: fx=? fx<? fx<=? fx>? fx>=?
into the current library being defined.
There should be at least two libraries that have all builtin things,
and the rest of the libraries should match R6RS or R7RS:
(ds base) -- Droscheme derived procedures and syntaxes
(ds base syntax)
(ds builtin) -- core procedures and syntaxes -- NOT automatically imported
(ds builtin syntax) -- no need to import, it's builtin
(ds io)
(ds math)
(scheme base) ; R7RS draft
(scheme case-lambda)
(scheme char normalization)
(scheme char)
(scheme complex)
(scheme division)
(scheme eval)
(scheme file)
(scheme inexact)
(scheme lazy)
(scheme load)
(scheme process-context)
(scheme read)
(scheme repl)
(scheme time)
(scheme write)
(scheme)
(srfi 0)
(srfi 1)
(srfi 9)
(srfi 16)
(srfi 99)
(rnrs base) ; R6RS final
(rnrs arithmetic bitwise)
(rnrs arithmetic fixnums)
(rnrs arithmetic flonums)
(rnrs bytevectors)
(rnrs conditions)
(rnrs control)
(rnrs enums)
(rnrs exceptions)
(rnrs files)
(rnrs hashtables)
(rnrs io ports)
(rnrs io simple)
(rnrs lists)
(rnrs programs)
(rnrs records inspection)
(rnrs records procedural)
(rnrs records syntactic)
(rnrs sorting)
(rnrs syntax-case)
(rnrs unicode)
(rnrs)
(rnrs eval)
(rnrs mutable-pairs)
(rnrs mutable-strings)
(rnrs r5rs)
=== The foreign-function-interface
This will probably be needed to bootstrap anyways, so we might as well
make it well-designed for the end-user. The primary difference here
with other schemes is that this will be an FFI to Go, not C.
Every builtin function has the following function signature:
func (Any) Any
the rationale behind this is that every scheme definition has the option
of giving a 'rest' parameter, either at the beginning, or at the end of the
formal parameter list, which must be a list structure.
.
=== Porting other scheme code
One posibility to get a lot of usability is to port SLIB or something similar
to be able to work in droscheme.
=== Multi-line REPL
There should be two conditions for an incomplete line:
(1) Paren mismatch
(2) Eval() error
the reason for this is that an error can occur with matching parens,
and a paren mismatch may eval without errors (e.g. (write "(")).
=== Error management
There are currently three techniques for error management:
(1) second return
(2) panic/recover
(3) global variables
Of these three, I would prefer sticking with the first two, or even
explicitly preferring them in this order. yyParse() seems to require
global variables, so there may not be a choice there, and any builtin
function will require a panic instead of a second return. Another thing
to consider is that it is possible to convert between (1) and (2).
To convert from (1) to (2):
func f() Any {
...
thing, myerr := g()
if myerr != nil { panic(err) }
...
}
To convert from (2) to (1):
func g() (value Any, err error) {
defer func(){
myerr := recover()
if myerr != nil { err = myerr }
}()
...
}
=== Data Model
Ds type Go type Scm type
----- ------- -------
SBool bool boolean?
SChar rune char?
SNull struct null?
SPair struct pair?
SBinary []byte bytevector?
SString []rune string?
SSymbol string symbol?
SVector []Any vector?
SValues []Any values?
STable map[Any]Any
*Pair
*Env
SPrimSyntax
SCaseSyntax
SRuleSyntax
SPrim struct
SProc struct
SType struct
SBytePort
SCharPort
SFilePort