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,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
+