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.
Files changed (53) hide show
  1. data/History.txt +21 -0
  2. data/Manifest.txt +53 -0
  3. data/README.txt +274 -0
  4. data/Rakefile +12 -0
  5. data/bin/heist +16 -0
  6. data/lib/bin_spec.rb +25 -0
  7. data/lib/builtin/library.scm +95 -0
  8. data/lib/builtin/primitives.rb +306 -0
  9. data/lib/builtin/syntax.rb +166 -0
  10. data/lib/builtin/syntax.scm +155 -0
  11. data/lib/heist.rb +47 -0
  12. data/lib/parser/nodes.rb +105 -0
  13. data/lib/parser/scheme.rb +1081 -0
  14. data/lib/parser/scheme.tt +80 -0
  15. data/lib/repl.rb +112 -0
  16. data/lib/runtime/binding.rb +31 -0
  17. data/lib/runtime/callable/continuation.rb +24 -0
  18. data/lib/runtime/callable/function.rb +55 -0
  19. data/lib/runtime/callable/macro.rb +170 -0
  20. data/lib/runtime/callable/macro/expansion.rb +15 -0
  21. data/lib/runtime/callable/macro/matches.rb +77 -0
  22. data/lib/runtime/callable/macro/splice.rb +56 -0
  23. data/lib/runtime/data/expression.rb +23 -0
  24. data/lib/runtime/data/identifier.rb +20 -0
  25. data/lib/runtime/data/list.rb +36 -0
  26. data/lib/runtime/frame.rb +118 -0
  27. data/lib/runtime/runtime.rb +61 -0
  28. data/lib/runtime/scope.rb +121 -0
  29. data/lib/runtime/stack.rb +60 -0
  30. data/lib/runtime/stackless.rb +49 -0
  31. data/lib/stdlib/benchmark.scm +12 -0
  32. data/lib/stdlib/birdhouse.scm +82 -0
  33. data/test/arithmetic.scm +57 -0
  34. data/test/benchmarks.scm +27 -0
  35. data/test/booleans.scm +6 -0
  36. data/test/closures.scm +16 -0
  37. data/test/conditionals.scm +55 -0
  38. data/test/continuations.scm +144 -0
  39. data/test/define_functions.scm +27 -0
  40. data/test/define_values.scm +28 -0
  41. data/test/delay.scm +8 -0
  42. data/test/file_loading.scm +9 -0
  43. data/test/hygienic.scm +39 -0
  44. data/test/let.scm +42 -0
  45. data/test/lib.scm +2 -0
  46. data/test/macro-helpers.scm +19 -0
  47. data/test/macros.scm +343 -0
  48. data/test/numbers.scm +19 -0
  49. data/test/plt-macros.txt +40 -0
  50. data/test/test_heist.rb +84 -0
  51. data/test/unhygienic.scm +11 -0
  52. data/test/vars.scm +2 -0
  53. 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
+
@@ -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
+