heist 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +21 -0
- data/Manifest.txt +53 -0
- data/README.txt +274 -0
- data/Rakefile +12 -0
- data/bin/heist +16 -0
- data/lib/bin_spec.rb +25 -0
- data/lib/builtin/library.scm +95 -0
- data/lib/builtin/primitives.rb +306 -0
- data/lib/builtin/syntax.rb +166 -0
- data/lib/builtin/syntax.scm +155 -0
- data/lib/heist.rb +47 -0
- data/lib/parser/nodes.rb +105 -0
- data/lib/parser/scheme.rb +1081 -0
- data/lib/parser/scheme.tt +80 -0
- data/lib/repl.rb +112 -0
- data/lib/runtime/binding.rb +31 -0
- data/lib/runtime/callable/continuation.rb +24 -0
- data/lib/runtime/callable/function.rb +55 -0
- data/lib/runtime/callable/macro.rb +170 -0
- data/lib/runtime/callable/macro/expansion.rb +15 -0
- data/lib/runtime/callable/macro/matches.rb +77 -0
- data/lib/runtime/callable/macro/splice.rb +56 -0
- data/lib/runtime/data/expression.rb +23 -0
- data/lib/runtime/data/identifier.rb +20 -0
- data/lib/runtime/data/list.rb +36 -0
- data/lib/runtime/frame.rb +118 -0
- data/lib/runtime/runtime.rb +61 -0
- data/lib/runtime/scope.rb +121 -0
- data/lib/runtime/stack.rb +60 -0
- data/lib/runtime/stackless.rb +49 -0
- data/lib/stdlib/benchmark.scm +12 -0
- data/lib/stdlib/birdhouse.scm +82 -0
- data/test/arithmetic.scm +57 -0
- data/test/benchmarks.scm +27 -0
- data/test/booleans.scm +6 -0
- data/test/closures.scm +16 -0
- data/test/conditionals.scm +55 -0
- data/test/continuations.scm +144 -0
- data/test/define_functions.scm +27 -0
- data/test/define_values.scm +28 -0
- data/test/delay.scm +8 -0
- data/test/file_loading.scm +9 -0
- data/test/hygienic.scm +39 -0
- data/test/let.scm +42 -0
- data/test/lib.scm +2 -0
- data/test/macro-helpers.scm +19 -0
- data/test/macros.scm +343 -0
- data/test/numbers.scm +19 -0
- data/test/plt-macros.txt +40 -0
- data/test/test_heist.rb +84 -0
- data/test/unhygienic.scm +11 -0
- data/test/vars.scm +2 -0
- metadata +138 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
module Heist
|
2
|
+
grammar Scheme
|
3
|
+
rule program
|
4
|
+
cell* <Program>
|
5
|
+
end
|
6
|
+
|
7
|
+
rule cell
|
8
|
+
ignore quote? ignore (list / atom) ignore <Cell>
|
9
|
+
end
|
10
|
+
|
11
|
+
rule quote
|
12
|
+
"'" / "`" / ",@" / ","
|
13
|
+
end
|
14
|
+
|
15
|
+
rule list
|
16
|
+
("(" cell* ")" / "[" cell* "]") <List>
|
17
|
+
end
|
18
|
+
|
19
|
+
rule atom
|
20
|
+
datum / identifier
|
21
|
+
end
|
22
|
+
|
23
|
+
rule datum
|
24
|
+
(boolean / number / string) !(!delimiter .) <Datum>
|
25
|
+
end
|
26
|
+
|
27
|
+
rule boolean
|
28
|
+
("#t" / "#f") <Boolean>
|
29
|
+
end
|
30
|
+
|
31
|
+
rule number
|
32
|
+
complex / real / rational / integer
|
33
|
+
end
|
34
|
+
|
35
|
+
rule complex
|
36
|
+
real "+" real "i" <Complex>
|
37
|
+
end
|
38
|
+
|
39
|
+
rule real
|
40
|
+
integer ("." digit+) <Real>
|
41
|
+
end
|
42
|
+
|
43
|
+
rule rational
|
44
|
+
numerator:integer "/" denominator:integer <Rational>
|
45
|
+
end
|
46
|
+
|
47
|
+
rule integer
|
48
|
+
"-"? ("0" / [1-9] digit*) <Integer>
|
49
|
+
end
|
50
|
+
|
51
|
+
rule string
|
52
|
+
'"' ('\\"' / [^"])* '"' <String>
|
53
|
+
end
|
54
|
+
|
55
|
+
rule identifier
|
56
|
+
(!delimiter .)+ <Identifier>
|
57
|
+
end
|
58
|
+
|
59
|
+
rule digit
|
60
|
+
[0-9]
|
61
|
+
end
|
62
|
+
|
63
|
+
rule delimiter
|
64
|
+
[\(\)\[\]] / space
|
65
|
+
end
|
66
|
+
|
67
|
+
rule space
|
68
|
+
[\s\n\r\t]
|
69
|
+
end
|
70
|
+
|
71
|
+
rule ignore
|
72
|
+
space* comment?
|
73
|
+
end
|
74
|
+
|
75
|
+
rule comment
|
76
|
+
";" (![\n\r] .)* ignore
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
data/lib/repl.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'readline'
|
2
|
+
|
3
|
+
module Heist
|
4
|
+
class REPL
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
@runtime = Runtime.new(options)
|
8
|
+
@scope = Runtime::FileScope.new(@runtime.top_level, File.expand_path('.'))
|
9
|
+
reset!
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
Heist.info(@runtime)
|
14
|
+
|
15
|
+
Readline.completion_append_character = nil
|
16
|
+
Readline.completion_proc = lambda do |prefix|
|
17
|
+
return nil if prefix == ''
|
18
|
+
matches = @runtime.top_level.grep(%r[^#{prefix}]i).
|
19
|
+
sort_by { |r| r.length }
|
20
|
+
return nil unless word = matches.first
|
21
|
+
while word.length > prefix.length
|
22
|
+
break if matches.all? { |m| m =~ %r[^#{word}]i }
|
23
|
+
word = word.gsub(/.$/, '')
|
24
|
+
end
|
25
|
+
return nil if word == prefix
|
26
|
+
word + (matches.size == 1 ? ' ' : '')
|
27
|
+
end
|
28
|
+
|
29
|
+
loop do
|
30
|
+
input = Readline.readline(prompt)
|
31
|
+
exit if input.nil?
|
32
|
+
|
33
|
+
push(input)
|
34
|
+
tree = Heist.parse(@buffer * ' ')
|
35
|
+
next if tree.nil?
|
36
|
+
|
37
|
+
reset!
|
38
|
+
begin
|
39
|
+
result = @scope.eval(tree)
|
40
|
+
puts "; => #{ result }\n\n" unless result.nil?
|
41
|
+
rescue Exception => ex
|
42
|
+
return if SystemExit === ex
|
43
|
+
puts "; [error] #{ ex.message }\n\n"
|
44
|
+
puts "; backtrace: " + ex.backtrace.join("\n; ") +
|
45
|
+
"\n\n" unless Heist::RuntimeError === ex
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
INDENT = 2
|
53
|
+
SPECIAL = %w[define lambda let let* letrec
|
54
|
+
define-syntax syntax-rules
|
55
|
+
let-syntax letrec-syntax]
|
56
|
+
|
57
|
+
def reset!
|
58
|
+
@buffer = []
|
59
|
+
@open = []
|
60
|
+
@indent = 0
|
61
|
+
end
|
62
|
+
|
63
|
+
def push(line)
|
64
|
+
old_depth = depth
|
65
|
+
@buffer << line
|
66
|
+
Readline::HISTORY.push(line)
|
67
|
+
new_depth = depth
|
68
|
+
|
69
|
+
return if new_depth == old_depth
|
70
|
+
return @open.slice!(new_depth..-1) if new_depth < old_depth
|
71
|
+
|
72
|
+
code = line.dup
|
73
|
+
while code == code.gsub!(/\([^\(\)\[\]]*\)|\[[^\(\)\[\]]*\]/, ''); end
|
74
|
+
calls = code.scan(/[\(\[](?:[^\(\)\[\]\s]*\s*)/).flatten
|
75
|
+
|
76
|
+
calls.pop while calls.size > 1 and
|
77
|
+
SPECIAL.include?(calls.last[1..-1].strip) and
|
78
|
+
SPECIAL.include?(calls[-2][1..-1].strip)
|
79
|
+
|
80
|
+
offsets = calls.inject([]) do |list, call|
|
81
|
+
index = list.empty? ? 0 : list.last.last - @indent
|
82
|
+
index = @indent + (line.index(call, index) || 0)
|
83
|
+
rindex = index + call.length
|
84
|
+
eol = (rindex == line.length + @indent)
|
85
|
+
list << [call[1..-1], eol, index, rindex]
|
86
|
+
list
|
87
|
+
end
|
88
|
+
|
89
|
+
@open = @open + offsets
|
90
|
+
end
|
91
|
+
|
92
|
+
def indent
|
93
|
+
return 0 if @buffer.empty? or @open.empty?
|
94
|
+
open = @open.last
|
95
|
+
@indent = (SPECIAL.include?(open[0].strip) or open[1]) ?
|
96
|
+
open[2] + INDENT :
|
97
|
+
open[3]
|
98
|
+
end
|
99
|
+
|
100
|
+
def prompt
|
101
|
+
" " * indent
|
102
|
+
end
|
103
|
+
|
104
|
+
def depth
|
105
|
+
source = @buffer * ' '
|
106
|
+
[/[\(\[]/, /[\)\]]/].map { |p| source.scan(p).size }.
|
107
|
+
inject { |a,b| a - b }
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Heist
|
2
|
+
class Runtime
|
3
|
+
|
4
|
+
class Binding
|
5
|
+
include Expression
|
6
|
+
|
7
|
+
attr_reader :expression, :scope
|
8
|
+
|
9
|
+
def initialize(expression, scope, memoized = true)
|
10
|
+
@expression = expression
|
11
|
+
@scope = scope
|
12
|
+
@memoized = !!memoized
|
13
|
+
end
|
14
|
+
|
15
|
+
def extract
|
16
|
+
return @value if defined?(@value) and @memoized
|
17
|
+
@value = Heist.evaluate(@expression, @scope)
|
18
|
+
end
|
19
|
+
|
20
|
+
def eval(scope)
|
21
|
+
extract
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
@expression.to_s
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Heist
|
2
|
+
class Runtime
|
3
|
+
|
4
|
+
class Continuation < Function
|
5
|
+
def initialize(stack)
|
6
|
+
@stack = stack.copy(false)
|
7
|
+
@target = stack.last.target
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(scope, cells)
|
11
|
+
filler = Heist.evaluate(cells.first, scope)
|
12
|
+
stack = @stack.copy
|
13
|
+
stack.fill!(@target, filler)
|
14
|
+
stack
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
"#<continuation>"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Heist
|
2
|
+
class Runtime
|
3
|
+
|
4
|
+
class Function
|
5
|
+
attr_reader :body, :name
|
6
|
+
|
7
|
+
def initialize(scope, formals = [], body = nil, &block)
|
8
|
+
@scope = scope
|
9
|
+
@body = body ? List.from(body) : block
|
10
|
+
@formals = formals.map { |id| id.to_s }
|
11
|
+
@lazy = scope.runtime.lazy?
|
12
|
+
@eager = !scope.runtime.stackless?
|
13
|
+
end
|
14
|
+
|
15
|
+
def name=(name)
|
16
|
+
@name ||= name.to_s
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(scope, cells)
|
20
|
+
params, closure = [], Scope.new(@scope)
|
21
|
+
cells.each_with_index do |arg, i|
|
22
|
+
params[i] = closure[@formals[i]] = lazy? ?
|
23
|
+
Binding.new(arg, scope) :
|
24
|
+
(@eager ? arg : Heist.evaluate(arg, scope))
|
25
|
+
end
|
26
|
+
return @body.call(*params) if primitive?
|
27
|
+
Body.new(@body, closure)
|
28
|
+
end
|
29
|
+
|
30
|
+
def primitive?
|
31
|
+
Proc === @body
|
32
|
+
end
|
33
|
+
|
34
|
+
def lazy?
|
35
|
+
@lazy and not primitive?
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_s
|
39
|
+
"#<procedure:#{ @name }>"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Syntax < Function
|
44
|
+
def call(scope, cells)
|
45
|
+
@body.call(scope, *cells)
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
"#<syntax:#{ @name }>"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
@@ -0,0 +1,170 @@
|
|
1
|
+
# Quotations taken from the R5RS spec
|
2
|
+
# http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-7.html
|
3
|
+
module Heist
|
4
|
+
class Runtime
|
5
|
+
|
6
|
+
class Macro < Syntax
|
7
|
+
ELLIPSIS = '...'
|
8
|
+
|
9
|
+
%w[expansion splice matches].each do |klass|
|
10
|
+
require RUNTIME_PATH + 'callable/macro/' + klass
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(scope, *args)
|
14
|
+
super
|
15
|
+
@hygienic = scope.runtime.hygienic?
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(scope, cells)
|
19
|
+
@calling_scope = scope
|
20
|
+
rule, matches = *rule_for(cells)
|
21
|
+
|
22
|
+
return Expansion.new(expand_template(rule.last, matches)) if rule
|
23
|
+
|
24
|
+
# TODO include macro name in error message,
|
25
|
+
# and be more specific about which pattern failed
|
26
|
+
input = cells.map { |c| c.to_s } * ' '
|
27
|
+
raise SyntaxError.new(
|
28
|
+
"Bad syntax: no macro expansion found for (#{@name} #{input})")
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
"#<macro:#{ @name }>"
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def rule_for(cells)
|
38
|
+
@body.each do |rule|
|
39
|
+
matches = rule_matches(rule.first.rest, cells)
|
40
|
+
return [rule, matches] if matches
|
41
|
+
end
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
# More formally, an input form F matches a pattern P if and only if:
|
46
|
+
#
|
47
|
+
# * P is a non-literal identifier; or
|
48
|
+
# * P is a literal identifier and F is an identifier with the
|
49
|
+
# same binding; or
|
50
|
+
# * P is a list (P1 ... Pn) and F is a list of n forms that match
|
51
|
+
# P1 through Pn, respectively; or
|
52
|
+
# * P is an improper list (P1 P2 ... Pn . Pn+1) and F is a list
|
53
|
+
# or improper list of n or more forms that match P1 through Pn,
|
54
|
+
# respectively, and whose nth 'cdr' matches Pn+1; or
|
55
|
+
# * P is of the form (P1 ... Pn Pn+1 <ellipsis>) where <ellipsis>
|
56
|
+
# is the identifier '...' and F is a proper list of at least n forms,
|
57
|
+
# the first n of which match P1 through Pn, respectively, and
|
58
|
+
# each remaining element of F matches Pn+1; or
|
59
|
+
# * P is a vector of the form #(P1 ... Pn) and F is a vector of n
|
60
|
+
# forms that match P1 through Pn; or
|
61
|
+
# * P is of the form #(P1 ... Pn Pn+1 <ellipsis>) where <ellipsis>
|
62
|
+
# is the identifier '...' and F is a vector of n or more forms the
|
63
|
+
# first n of which match P1 through Pn, respectively, and each
|
64
|
+
# remaining element of F matches Pn+1; or
|
65
|
+
# * P is a datum and F is equal to P in the sense of the 'equal?'
|
66
|
+
# procedure.
|
67
|
+
#
|
68
|
+
# It is an error to use a macro keyword, within the scope of its
|
69
|
+
# binding, in an expression that does not match any of the patterns.
|
70
|
+
#
|
71
|
+
def rule_matches(pattern, input, matches = Matches.new, depth = 0)
|
72
|
+
case pattern
|
73
|
+
|
74
|
+
when List then
|
75
|
+
return nil unless List === input
|
76
|
+
idx = 0
|
77
|
+
pattern.each_with_index do |token, i|
|
78
|
+
followed_by_ellipsis = (pattern[i+1].to_s == ELLIPSIS)
|
79
|
+
dx = followed_by_ellipsis ? 1 : 0
|
80
|
+
|
81
|
+
matches.depth = depth + dx
|
82
|
+
next if token.to_s == ELLIPSIS
|
83
|
+
|
84
|
+
consume = lambda { rule_matches(token, input[idx], matches, depth + dx) }
|
85
|
+
return nil unless value = consume[] or followed_by_ellipsis
|
86
|
+
next unless value
|
87
|
+
idx += 1
|
88
|
+
|
89
|
+
idx += 1 while idx < input.size and
|
90
|
+
followed_by_ellipsis and
|
91
|
+
consume[]
|
92
|
+
end
|
93
|
+
return nil unless idx == input.size
|
94
|
+
|
95
|
+
when Identifier then
|
96
|
+
return (pattern.to_s == input.to_s) if @formals.include?(pattern.to_s)
|
97
|
+
matches.put(pattern, input)
|
98
|
+
return nil if input.nil?
|
99
|
+
|
100
|
+
else
|
101
|
+
return pattern == input ? true : nil
|
102
|
+
end
|
103
|
+
matches
|
104
|
+
end
|
105
|
+
|
106
|
+
# When a macro use is transcribed according to the template of the
|
107
|
+
# matching <syntax rule>, pattern variables that occur in the template
|
108
|
+
# are replaced by the subforms they match in the input. Pattern variables
|
109
|
+
# that occur in subpatterns followed by one or more instances of the
|
110
|
+
# identifier '...' are allowed only in subtemplates that are followed
|
111
|
+
# by as many instances of '...'. They are replaced in the output by all
|
112
|
+
# of the subforms they match in the input, distributed as indicated. It
|
113
|
+
# is an error if the output cannot be built up as specified.
|
114
|
+
#
|
115
|
+
# Identifiers that appear in the template but are not pattern variables
|
116
|
+
# or the identifier '...' are inserted into the output as literal
|
117
|
+
# identifiers. If a literal identifier is inserted as a free identifier
|
118
|
+
# then it refers to the binding of that identifier within whose scope
|
119
|
+
# the instance of 'syntax-rules' appears. If a literal identifier is
|
120
|
+
# inserted as a bound identifier then it is in effect renamed to prevent
|
121
|
+
# inadvertent captures of free identifiers.
|
122
|
+
#
|
123
|
+
def expand_template(template, matches, depth = 0, inspection = false)
|
124
|
+
case template
|
125
|
+
|
126
|
+
when List then
|
127
|
+
result = List.new
|
128
|
+
template.each_with_index do |cell, i|
|
129
|
+
followed_by_ellipsis = (template[i+1].to_s == ELLIPSIS)
|
130
|
+
dx = followed_by_ellipsis ? 1 : 0
|
131
|
+
|
132
|
+
matches.inspecting(depth + 1) if followed_by_ellipsis and
|
133
|
+
not inspection
|
134
|
+
|
135
|
+
if cell.to_s == ELLIPSIS and not inspection
|
136
|
+
repeater = template[i-1]
|
137
|
+
matches.expand! { result << expand_template(repeater, matches, depth + 1) }
|
138
|
+
matches.depth = depth
|
139
|
+
else
|
140
|
+
inspect = inspection || (followed_by_ellipsis && depth + 1)
|
141
|
+
value = expand_template(cell, matches, depth + dx, inspect)
|
142
|
+
result << value unless inspect
|
143
|
+
end
|
144
|
+
end
|
145
|
+
result
|
146
|
+
|
147
|
+
when Identifier then
|
148
|
+
return matches.get(template) if matches.defined?(template)
|
149
|
+
return Identifier.new(template) unless @hygienic
|
150
|
+
|
151
|
+
@scope.defined?(template) ?
|
152
|
+
Binding.new(template, @scope, false) :
|
153
|
+
rename(template)
|
154
|
+
|
155
|
+
else
|
156
|
+
template
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def rename(id)
|
161
|
+
return id unless @calling_scope.defined?(id)
|
162
|
+
i = 1
|
163
|
+
i += 1 while @calling_scope.defined?("#{id}#{i}")
|
164
|
+
Identifier.new("#{id}#{i}")
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|