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