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,15 @@
1
+ module Heist
2
+ class Runtime
3
+ class Macro
4
+
5
+ class Expansion
6
+ attr_reader :expression
7
+ def initialize(expression)
8
+ @expression = expression
9
+ end
10
+ end
11
+
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,77 @@
1
+ module Heist
2
+ class Runtime
3
+ class Macro
4
+
5
+ class Matches
6
+ def initialize
7
+ @data = {}
8
+ @depth = 0
9
+ @names = []
10
+ end
11
+
12
+ def depth=(depth)
13
+ mark!(depth) if depth < @depth
14
+ @names[depth] = [] if depth >= @depth
15
+ @depth = depth
16
+ end
17
+
18
+ def put(name, expression)
19
+ name = name.to_s
20
+ @names[@depth] << name
21
+ @data[name] ||= Splice.new(name, @depth)
22
+ @data[name] << expression unless expression.nil?
23
+ end
24
+
25
+ def inspecting(depth)
26
+ @inspecting = true
27
+ self.depth = depth
28
+ end
29
+
30
+ def get(name)
31
+ @inspecting ? @names[@depth] << name.to_s :
32
+ @data[name.to_s].read
33
+ end
34
+
35
+ def defined?(name)
36
+ @data.has_key?(name.to_s)
37
+ end
38
+
39
+ def expand!
40
+ @inspecting = false
41
+ size.times { yield and iterate! }
42
+ end
43
+
44
+ private
45
+
46
+ def mark!(depth)
47
+ d = @depth
48
+ while @names[d]
49
+ @names[d].uniq.each { |name| @data[name].mark!(depth) }
50
+ d += 1
51
+ end
52
+ end
53
+
54
+ def size
55
+ names = @names[@depth].uniq
56
+ splices = @data.select { |k,v| names.include?(k.to_s) }
57
+ sizes = splices.map { |pair| pair.last.size(@depth) }.uniq
58
+
59
+ return 0 if sizes.empty?
60
+ return sizes.first if sizes.size == 1
61
+
62
+ expressions = splices.map { |pair| '"' + pair.last.to_s(@depth) + '"' } * ', '
63
+ raise MacroTemplateMismatch.new(
64
+ "Macro could not be expanded: expressions #{expressions} are of different sizes")
65
+ end
66
+
67
+ def iterate!
68
+ @data.each do |name, splice|
69
+ splice.shift!(@depth) if @names[@depth].include?(name)
70
+ end
71
+ end
72
+ end
73
+
74
+ end
75
+ end
76
+ end
77
+
@@ -0,0 +1,56 @@
1
+ module Heist
2
+ class Runtime
3
+ class Macro
4
+
5
+ class Splice
6
+ attr_reader :name, :depth
7
+
8
+ def initialize(name, depth)
9
+ @name, @depth = name, depth
10
+ @data = []
11
+ (0...@depth).inject(@data) { |list, d| list << []; list.last }
12
+ @indexes = (0..@depth).map { 0 }
13
+ @stack = []
14
+ end
15
+
16
+ def <<(value)
17
+ @stack.pop.call() while not @stack.empty?
18
+ tail(@depth) << value
19
+ end
20
+
21
+ def mark!(depth)
22
+ @stack << lambda { tail(depth) << [] }
23
+ end
24
+
25
+ def size(depth)
26
+ current(depth).size
27
+ end
28
+
29
+ def read
30
+ current(@depth)[@indexes[@depth]]
31
+ end
32
+
33
+ def shift!(depth)
34
+ @indexes[depth] += 1
35
+ @indexes[depth] = 0 if @indexes[depth] >= current(depth).size
36
+ end
37
+
38
+ def to_s(depth = 0)
39
+ "#{@name}#{' ...' * (@depth - depth)}"
40
+ end
41
+
42
+ private
43
+
44
+ def tail(depth)
45
+ (0...depth).inject(@data) { |list, d| list.last }
46
+ end
47
+
48
+ def current(depth)
49
+ @indexes[0...depth].inject(@data) { |list, i| list[i] }
50
+ end
51
+ end
52
+
53
+ end
54
+ end
55
+ end
56
+
@@ -0,0 +1,23 @@
1
+ module Heist
2
+ class Runtime
3
+
4
+ module Expression
5
+ attr_reader :parent, :index
6
+
7
+ def exists_at!(parent, index)
8
+ @parent, @index = parent, index
9
+ end
10
+
11
+ def replace(expression)
12
+ return unless @parent
13
+ @parent[@index] = expression
14
+ end
15
+
16
+ def eval(scope)
17
+ scope.runtime.stack << Frame.new(self, scope)
18
+ end
19
+ end
20
+
21
+ end
22
+ end
23
+
@@ -0,0 +1,20 @@
1
+ module Heist
2
+ class Runtime
3
+
4
+ class Identifier
5
+ include Expression
6
+
7
+ extend Forwardable
8
+ def_delegators(:@metadata, :[], :[]=)
9
+ def_delegators(:@name, :to_s)
10
+ alias :inspect :to_s
11
+
12
+ def initialize(name)
13
+ @name = name.to_s
14
+ @metadata = {}
15
+ end
16
+ end
17
+
18
+ end
19
+ end
20
+
@@ -0,0 +1,36 @@
1
+ module Heist
2
+ class Runtime
3
+
4
+ class List < Array
5
+ include Expression
6
+
7
+ def self.from(array)
8
+ list = new
9
+ array.each { |item| list << item }
10
+ list
11
+ end
12
+
13
+ def <<(element)
14
+ element.exists_at!(self, self.size) if Expression === element
15
+ super
16
+ end
17
+
18
+ def []=(index, value)
19
+ super
20
+ value.exists_at!(self, index) if Expression === value
21
+ end
22
+
23
+ def rest
24
+ self[1..-1]
25
+ end
26
+
27
+ def to_s
28
+ '(' + collect { |cell| cell.to_s } * ' ' + ')'
29
+ end
30
+
31
+ alias :inspect :to_s
32
+ end
33
+
34
+ end
35
+ end
36
+
@@ -0,0 +1,118 @@
1
+ module Heist
2
+ class Runtime
3
+
4
+ class Frame
5
+ attr_reader :expression, :scope
6
+
7
+ def initialize(expression, scope)
8
+ reset!(expression)
9
+ @scope = scope
10
+ end
11
+
12
+ def complete?
13
+ @complete
14
+ end
15
+
16
+ def process!
17
+ case @expression
18
+
19
+ when List then
20
+ if Syntax === @values.first or @values.size == @expression.size
21
+ @complete = true
22
+ merge!
23
+ raise SyntaxError.new("Invalid expression: #{@expression}") unless Function === @data.first
24
+ result = @data.first.call(@scope, @data[1..-1])
25
+ return result unless Macro::Expansion === result
26
+ return reset!(result.expression, true)
27
+ end
28
+
29
+ stack = @scope.runtime.stack
30
+ stack << Frame.new(@expression[@values.size], @scope)
31
+
32
+ when Identifier then
33
+ @complete = true
34
+ @scope[@expression]
35
+
36
+ else
37
+ @complete = true
38
+ Heist.evaluate(@expression, @scope)
39
+ end
40
+ end
41
+
42
+ def dup
43
+ copy, values = super, @values.dup
44
+ copy.instance_eval { @values = values }
45
+ copy
46
+ end
47
+
48
+ def fill!(subexpr, value)
49
+ subexpr ||= @expression[@values.size]
50
+ @values << value
51
+ @subexprs << subexpr
52
+ end
53
+
54
+ def replaces(expression)
55
+ @target = expression
56
+ end
57
+
58
+ def target
59
+ @target || @expression
60
+ end
61
+
62
+ private
63
+
64
+ def merge!
65
+ return @data = @values unless Syntax === @values.first
66
+ @data = @expression.dup
67
+ @expression.each_with_index do |expr, i|
68
+ index = @subexprs.index(expr)
69
+ @data[i] = @values[index] if index
70
+ end
71
+ end
72
+
73
+ def reset!(expression, replace = false)
74
+ @expression.replace(expression) if replace
75
+ @expression = expression
76
+ @values = []
77
+ @subexprs = []
78
+ @complete = false
79
+ end
80
+ end
81
+
82
+ class Body < Frame
83
+ def initialize(expressions, scope)
84
+ @expression = expressions
85
+ @scope = scope
86
+ @values = []
87
+ @index = 0
88
+ end
89
+
90
+ def complete?
91
+ @index == @expression.size
92
+ end
93
+
94
+ def process!
95
+ expression = @expression[@index]
96
+ @index += 1 # increment before evaluating the expression
97
+ # so that when a continuation is saved we
98
+ # resume from the following statement
99
+
100
+ return Frame.new(expression, @scope) if @index == @expression.size
101
+
102
+ stack = @scope.runtime.stack
103
+ stack << Frame.new(expression, @scope)
104
+ stack.value
105
+ end
106
+
107
+ def fill!(value, subexpr = nil)
108
+ @values << value
109
+ end
110
+
111
+ def to_s
112
+ @expression.map { |e| e.to_s } * ' '
113
+ end
114
+ end
115
+
116
+ end
117
+ end
118
+
@@ -0,0 +1,61 @@
1
+ require 'forwardable'
2
+
3
+ module Heist
4
+ class Runtime
5
+
6
+ %w[ data/expression data/identifier data/list
7
+ callable/function callable/macro callable/continuation
8
+ scope binding frame
9
+ stack stackless
10
+
11
+ ].each do |file|
12
+ require RUNTIME_PATH + file
13
+ end
14
+
15
+ extend Forwardable
16
+ def_delegators(:@top_level, :[], :eval, :define, :syntax, :call)
17
+
18
+ attr_reader :order
19
+ attr_accessor :stack, :top_level
20
+
21
+ def initialize(options = {})
22
+ @lazy = !!options[:lazy]
23
+ @continuations = !!options[:continuations]
24
+ @hygienic = !options[:unhygienic]
25
+
26
+ @top_level = Scope.new(self)
27
+ @stack = create_stack
28
+
29
+ syntax_type = (lazy? or not @hygienic) ? 'rb' : 'scm'
30
+
31
+ run("#{ BUILTIN_PATH }primitives.rb")
32
+ run("#{ BUILTIN_PATH }syntax.#{syntax_type}")
33
+ run("#{ BUILTIN_PATH }library.scm")
34
+
35
+ @start_time = Time.now.to_f
36
+ end
37
+
38
+ def run(path)
39
+ return instance_eval(File.read(path)) if File.extname(path) == '.rb'
40
+ @top_level.run(path)
41
+ end
42
+
43
+ def elapsed_time
44
+ (Time.now.to_f - @start_time) * 1000000
45
+ end
46
+
47
+ def lazy?; @lazy; end
48
+
49
+ def hygienic?; @hygienic; end
50
+
51
+ def stackless?
52
+ lazy? or not @continuations
53
+ end
54
+
55
+ def create_stack
56
+ stackless? ? Stackless.new : Stack.new
57
+ end
58
+
59
+ end
60
+ end
61
+
@@ -0,0 +1,121 @@
1
+ module Heist
2
+ class Runtime
3
+
4
+ class Scope
5
+ attr_reader :runtime
6
+
7
+ def initialize(parent = {})
8
+ @symbols = {}
9
+ is_runtime = (Runtime === parent)
10
+ @parent = is_runtime ? {} : parent
11
+ @runtime = is_runtime ? parent : parent.runtime
12
+ end
13
+
14
+ def [](name)
15
+ name = to_name(name)
16
+ bound = @symbols.has_key?(to_name(name))
17
+
18
+ raise UndefinedVariable.new(
19
+ "Variable '#{name}' is not defined") unless bound or Scope === @parent
20
+
21
+ value = bound ? @symbols[name] : @parent[name]
22
+ value = value.extract if Binding === value
23
+ value
24
+ end
25
+
26
+ def []=(name, value)
27
+ @symbols[to_name(name)] = value
28
+ value.name = name if Function === value
29
+ value
30
+ end
31
+
32
+ def defined?(name)
33
+ @symbols.has_key?(to_name(name)) or
34
+ (Scope === @parent and @parent.defined?(name))
35
+ end
36
+
37
+ def set!(name, value)
38
+ name = to_name(name)
39
+ bound = @symbols.has_key?(name)
40
+
41
+ raise UndefinedVariable.new(
42
+ "Cannot set undefined variable '#{name}'") unless bound or Scope === @parent
43
+
44
+ return @parent.set!(name, value) unless bound
45
+ self[name] = value
46
+ end
47
+
48
+ def define(name, *args, &block)
49
+ self[name] = Function.new(self, *args, &block)
50
+ end
51
+
52
+ def syntax(name, holes = [], &block)
53
+ self[name] = Syntax.new(self, holes,&block)
54
+ end
55
+
56
+ def grep(pattern)
57
+ base = (Scope === @parent) ? @parent.grep(pattern) : []
58
+ @symbols.each do |key, value|
59
+ base << key if key =~ pattern
60
+ end
61
+ base.uniq
62
+ end
63
+
64
+ # TODO: this isn't great, figure out a way for functions
65
+ # to transparently handle inter-primitive calls so Ruby can
66
+ # call Scheme code as well as other Ruby code
67
+ def call(name, *params)
68
+ self[name].body.call(*params)
69
+ end
70
+
71
+ def run(path)
72
+ path = path + FILE_EXT unless File.file?(path)
73
+ source = Heist.parse(File.read(path))
74
+ scope = FileScope.new(self, path)
75
+ source.eval(scope)
76
+ end
77
+
78
+ def eval(source)
79
+ source = Heist.parse(source) if String === source
80
+ source.eval(self)
81
+ end
82
+
83
+ def load(path)
84
+ dir = load_path.find do |dir|
85
+ File.file?("#{dir}/#{path}") or File.file?("#{dir}/#{path}#{FILE_EXT}")
86
+ end
87
+ return false unless dir
88
+ runtime.run("#{dir}/#{path}")
89
+ true
90
+ end
91
+
92
+ def current_file
93
+ @path || @parent.current_file rescue nil
94
+ end
95
+
96
+ private
97
+
98
+ def to_name(name)
99
+ name.to_s.downcase
100
+ end
101
+
102
+ def load_path
103
+ paths, file = [], current_file
104
+ paths << File.dirname(file) if file
105
+ paths + LOAD_PATH
106
+ end
107
+ end
108
+
109
+ class FileScope < Scope
110
+ extend Forwardable
111
+ def_delegators(:@parent, :[]=)
112
+
113
+ def initialize(parent, path)
114
+ super(parent)
115
+ @path = File.directory?(path) ? path + '/repl.scm' : path
116
+ end
117
+ end
118
+
119
+ end
120
+ end
121
+