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