heist 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|