heist 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+