klam 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.rspec +0 -0
- data/.travis.yml +8 -0
- data/Gemfile +1 -0
- data/LICENSE.txt +19 -0
- data/PROGRESS.asciidoc +105 -0
- data/README.asciidoc +11 -0
- data/Rakefile +5 -0
- data/klam.gemspec +28 -0
- data/lib/klam.rb +16 -0
- data/lib/klam/compilation_stages.rb +4 -0
- data/lib/klam/compilation_stages/convert_freezes_to_lambdas.rb +17 -0
- data/lib/klam/compilation_stages/convert_lexical_variables.rb +79 -0
- data/lib/klam/compilation_stages/convert_partial_applications_to_lambdas.rb +35 -0
- data/lib/klam/compilation_stages/convert_self_tail_calls_to_loops.rb +147 -0
- data/lib/klam/compilation_stages/curry_abstraction_applications.rb +33 -0
- data/lib/klam/compilation_stages/emit_ruby.rb +232 -0
- data/lib/klam/compilation_stages/kl_to_internal_representation.rb +21 -0
- data/lib/klam/compilation_stages/make_abstractions_monadic.rb +26 -0
- data/lib/klam/compilation_stages/make_abstractions_variadic.rb +23 -0
- data/lib/klam/compilation_stages/simplify_boolean_operations.rb +74 -0
- data/lib/klam/compilation_stages/strip_type_declarations.rb +24 -0
- data/lib/klam/compiler.rb +63 -0
- data/lib/klam/cons.rb +18 -0
- data/lib/klam/converters.rb +4 -0
- data/lib/klam/converters/.list.rb.swp +0 -0
- data/lib/klam/converters/list.rb +29 -0
- data/lib/klam/environment.rb +61 -0
- data/lib/klam/error.rb +4 -0
- data/lib/klam/lexer.rb +185 -0
- data/lib/klam/primitives.rb +4 -0
- data/lib/klam/primitives/arithmetic.rb +49 -0
- data/lib/klam/primitives/assignments.rb +13 -0
- data/lib/klam/primitives/boolean_operations.rb +22 -0
- data/lib/klam/primitives/error_handling.rb +19 -0
- data/lib/klam/primitives/generic_functions.rb +19 -0
- data/lib/klam/primitives/lists.rb +23 -0
- data/lib/klam/primitives/streams.rb +32 -0
- data/lib/klam/primitives/strings.rb +58 -0
- data/lib/klam/primitives/symbols.rb +16 -0
- data/lib/klam/primitives/time.rb +19 -0
- data/lib/klam/primitives/vectors.rb +26 -0
- data/lib/klam/reader.rb +46 -0
- data/lib/klam/template.rb +38 -0
- data/lib/klam/variable.rb +21 -0
- data/lib/klam/variable_generator.rb +12 -0
- data/lib/klam/version.rb +3 -0
- data/spec/functional/application_spec.rb +94 -0
- data/spec/functional/atoms_spec.rb +56 -0
- data/spec/functional/extensions/do_spec.rb +22 -0
- data/spec/functional/primitives/assignments_spec.rb +38 -0
- data/spec/functional/primitives/boolean_operations_spec.rb +133 -0
- data/spec/functional/primitives/error_handling_spec.rb +22 -0
- data/spec/functional/primitives/generic_functions_spec.rb +82 -0
- data/spec/functional/tail_call_optimization_spec.rb +71 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/unit/klam/compilation_stages/convert_lexical_variables_spec.rb +58 -0
- data/spec/unit/klam/compilation_stages/convert_self_tail_calls_to_loops_spec.rb +33 -0
- data/spec/unit/klam/compilation_stages/curry_abstraction_applications_spec.rb +19 -0
- data/spec/unit/klam/compilation_stages/make_abstractions_variadic_spec.rb +12 -0
- data/spec/unit/klam/converters/list_spec.rb +57 -0
- data/spec/unit/klam/lexer_spec.rb +149 -0
- data/spec/unit/klam/primitives/arithmetic_spec.rb +153 -0
- data/spec/unit/klam/primitives/boolean_operations_spec.rb +39 -0
- data/spec/unit/klam/primitives/error_handling_spec.rb +19 -0
- data/spec/unit/klam/primitives/lists_spec.rb +49 -0
- data/spec/unit/klam/primitives/strings_spec.rb +53 -0
- data/spec/unit/klam/primitives/symbols_spec.rb +19 -0
- data/spec/unit/klam/primitives/time_spec.rb +16 -0
- data/spec/unit/klam/primitives/vectors_spec.rb +55 -0
- data/spec/unit/klam/reader_spec.rb +47 -0
- data/spec/unit/klam/template_spec.rb +28 -0
- data/spec/unit/klam/variable_spec.rb +22 -0
- data/spec/unit/klam/version_spec.rb +7 -0
- metadata +225 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
module Klam
|
2
|
+
module CompilationStages
|
3
|
+
# Curry Abstraction Applications
|
4
|
+
#
|
5
|
+
# This stage converts all applications of abstractions (i.e., any
|
6
|
+
# application that does not have a symbol in rator position) to
|
7
|
+
# a curried form in which only one argument is applied at a time.
|
8
|
+
# For example:
|
9
|
+
#
|
10
|
+
# ((foo) 1 2 3)
|
11
|
+
#
|
12
|
+
# becomes:
|
13
|
+
#
|
14
|
+
# ((((foo) 1) 2) 3)
|
15
|
+
module CurryAbstractionApplications
|
16
|
+
def curry_abstraction_applications(sexp)
|
17
|
+
if sexp.instance_of?(Array)
|
18
|
+
if !sexp[0].instance_of?(Symbol) && sexp.length > 2
|
19
|
+
[curry_abstraction_applications(sexp[0..-2]),
|
20
|
+
curry_abstraction_applications(sexp[-1])]
|
21
|
+
elsif sexp[0] == :defun
|
22
|
+
sexp[0,3] +
|
23
|
+
sexp[3..-1].map { |form| curry_abstraction_applications(form) }
|
24
|
+
else
|
25
|
+
sexp.map { |form| curry_abstraction_applications(form) }
|
26
|
+
end
|
27
|
+
else
|
28
|
+
sexp
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
module Klam
|
2
|
+
module CompilationStages
|
3
|
+
# Emit Ruby
|
4
|
+
#
|
5
|
+
# This is the final stage in the compilation pipeline. It is responsible
|
6
|
+
# for converting the compiler's internal representation to a string
|
7
|
+
# containing Ruby code that is suitable for execution via instance_eval in
|
8
|
+
# the context of a Klam::Environment object.
|
9
|
+
#
|
10
|
+
# By the time compilation reaches this stage, all of the heavy lifting
|
11
|
+
# should be complete. Emitting Ruby is simply a matter of transliterating
|
12
|
+
# the simplified s-expression into Ruby.
|
13
|
+
module EmitRuby
|
14
|
+
include Klam::Template
|
15
|
+
|
16
|
+
PRIMITIVE_TEMPLATES = {
|
17
|
+
:+ => [2, '($1 + $2)'],
|
18
|
+
:- => [2, '($1 - $2)'],
|
19
|
+
:* => [2, '($1 * $2)'],
|
20
|
+
:< => [2, '($1 < $2)'],
|
21
|
+
:> => [2, '($1 > $2)'],
|
22
|
+
:<= => [2, '($1 <= $2)'],
|
23
|
+
:>= => [2, '($1 >= $2)'],
|
24
|
+
:"=" => [2, '($1 == $2)'],
|
25
|
+
:absvector => [1, '::Array.new($1)'],
|
26
|
+
:absvector? => [1, '$1.instance_of?(::Array)'],
|
27
|
+
:"<-address" => [2, '$1[$2]'],
|
28
|
+
:cons => [2, '::Klam::Cons.new($1, $2)'],
|
29
|
+
:cons? => [1, '$1.instance_of?(::Klam::Cons)'],
|
30
|
+
:hd => [1, '$1.hd'],
|
31
|
+
:set => [2, '(@assignments[$1] = $2)'],
|
32
|
+
:tl => [1, '$1.tl'],
|
33
|
+
:value => [1, '@assignments[$1]']
|
34
|
+
}
|
35
|
+
|
36
|
+
def emit_ruby(sexp)
|
37
|
+
case sexp
|
38
|
+
when Symbol
|
39
|
+
emit_symbol(sexp)
|
40
|
+
when String
|
41
|
+
emit_string(sexp)
|
42
|
+
when Klam::Variable, Numeric, true, false
|
43
|
+
sexp.to_s
|
44
|
+
when Array
|
45
|
+
if sexp.empty?
|
46
|
+
Klam::Primitives::Lists::EMPTY_LIST.inspect
|
47
|
+
else
|
48
|
+
emit_compound_form(sexp)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def emit_compound_form(form)
|
56
|
+
# Handle special forms and fall back to normal function application
|
57
|
+
case form[0]
|
58
|
+
when :defun
|
59
|
+
emit_defun(form)
|
60
|
+
when :if
|
61
|
+
emit_if(form)
|
62
|
+
when :lambda
|
63
|
+
emit_lambda(form)
|
64
|
+
when :let
|
65
|
+
emit_let(form)
|
66
|
+
when :"trap-error"
|
67
|
+
emit_trap_error(form)
|
68
|
+
when :do
|
69
|
+
emit_do(form)
|
70
|
+
when :"[FIX-VARS]"
|
71
|
+
emit_fix_vars(form)
|
72
|
+
when :"[LOOP]"
|
73
|
+
emit_loop(form)
|
74
|
+
when :"[RECUR]"
|
75
|
+
emit_recur(form)
|
76
|
+
else
|
77
|
+
if full_primitive_form?(form)
|
78
|
+
emit_primitive(form)
|
79
|
+
else
|
80
|
+
emit_application(form)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def full_primitive_form?(form)
|
86
|
+
num_args, template = PRIMITIVE_TEMPLATES[form[0]]
|
87
|
+
num_args && (num_args == form.size - 1)
|
88
|
+
end
|
89
|
+
|
90
|
+
def emit_primitive(form)
|
91
|
+
_, template = PRIMITIVE_TEMPLATES[form[0]]
|
92
|
+
rands_rb = form[1..-1].map { |rand| emit_ruby(rand) }
|
93
|
+
render_string(template, *rands_rb)
|
94
|
+
end
|
95
|
+
|
96
|
+
def emit_application(form)
|
97
|
+
rator = form[0]
|
98
|
+
rands = form[1..-1]
|
99
|
+
rator_rb = emit_ruby(rator)
|
100
|
+
rands_rb = rands.map { |rand| emit_ruby(rand) }
|
101
|
+
|
102
|
+
if rator.kind_of?(Symbol)
|
103
|
+
# Application of a function defined in the environment. At this point
|
104
|
+
# partial application and currying has been taken care of, so a
|
105
|
+
# simple send suffices.
|
106
|
+
render_string('__send__($1)', [rator_rb] + rands_rb)
|
107
|
+
else
|
108
|
+
render_string('__apply($1)', [rator_rb] + rands_rb)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def emit_defun(form)
|
113
|
+
_, name, params, body = form
|
114
|
+
name_rb = emit_ruby(name)
|
115
|
+
params_rb = params.map { |param| emit_ruby(param) }
|
116
|
+
body_rb = emit_ruby(body)
|
117
|
+
|
118
|
+
# Some valid Kl function names (e.g. foo-bar) are not valid when used
|
119
|
+
# with Ruby's def syntax. They will work with define_method, but the
|
120
|
+
# resulting methods are slower than if they had been defined via def.
|
121
|
+
# To maximize performance, methods are defined with def and then
|
122
|
+
# renamed to their intended name afterwards.
|
123
|
+
mangled_name = '__klam_fn_' + name.to_s.gsub(/[^a-zA-Z0-9]/, '_')
|
124
|
+
render_string(<<-EOT, name_rb, params_rb, body_rb, mangled_name, params.size)
|
125
|
+
def $4($2)
|
126
|
+
$3
|
127
|
+
end
|
128
|
+
@eigenclass.rename_method(:$4, $1)
|
129
|
+
@arities[$1] = $5
|
130
|
+
@curried_methods.delete($1)
|
131
|
+
@loop_cache.delete($1)
|
132
|
+
$1
|
133
|
+
EOT
|
134
|
+
end
|
135
|
+
|
136
|
+
def emit_do(form)
|
137
|
+
rands = form[1,2]
|
138
|
+
rands_rb = rands.map { |rand| emit_ruby(rand) }
|
139
|
+
'(' + rands_rb.join(';') + ')'
|
140
|
+
end
|
141
|
+
|
142
|
+
def emit_fix_vars(form)
|
143
|
+
_, params, expr = form
|
144
|
+
params_rb = params.map { |param| emit_ruby(param) }
|
145
|
+
expr_rb = emit_ruby(expr)
|
146
|
+
|
147
|
+
render_string('(::Kernel.lambda { |$1| $2 }).call($1)',
|
148
|
+
params_rb, expr_rb)
|
149
|
+
end
|
150
|
+
|
151
|
+
def emit_if(form)
|
152
|
+
args = form[1..3].map { |sexp| emit_ruby(sexp) }
|
153
|
+
render_string('($1 ? $2 : $3)', *args)
|
154
|
+
end
|
155
|
+
|
156
|
+
def emit_lambda(form)
|
157
|
+
_, params, body = form
|
158
|
+
params_rb = params.map { |param| emit_ruby(param) }
|
159
|
+
body_rb = emit_ruby(body)
|
160
|
+
|
161
|
+
render_string('(::Kernel.lambda { |$1| $2 })', params_rb, body_rb)
|
162
|
+
end
|
163
|
+
|
164
|
+
def emit_let(form)
|
165
|
+
_, var, value_expr, body_expr = form
|
166
|
+
|
167
|
+
var_rb = emit_ruby(var)
|
168
|
+
value_expr_rb = emit_ruby(value_expr)
|
169
|
+
body_expr_rb = emit_ruby(body_expr)
|
170
|
+
|
171
|
+
render_string('($1 = $2; $3)', var_rb, value_expr_rb, body_expr_rb)
|
172
|
+
end
|
173
|
+
|
174
|
+
def emit_loop(form)
|
175
|
+
name_rb = emit_ruby(form[1])
|
176
|
+
params_rb = form[2].map {|v| emit_ruby(v)}
|
177
|
+
expr_rb = emit_ruby(form[3])
|
178
|
+
render_string('(@loop_cache[$1] ||= ::Kernel.lambda { |$2| $3 }).call($2)', name_rb,
|
179
|
+
params_rb, expr_rb)
|
180
|
+
end
|
181
|
+
|
182
|
+
def emit_recur(form)
|
183
|
+
_, params, new_value_exprs = form
|
184
|
+
if params.size > 0
|
185
|
+
params_rb = params.map { |param| emit_ruby(param) }
|
186
|
+
new_value_exprs_rb = new_value_exprs.map { |expr| emit_ruby(expr) }
|
187
|
+
|
188
|
+
render_string('(($1 = $2); redo)', params_rb, new_value_exprs_rb)
|
189
|
+
else
|
190
|
+
'redo'
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def emit_string(str)
|
195
|
+
"'" + escape_string(str) + "'"
|
196
|
+
end
|
197
|
+
|
198
|
+
def emit_symbol(sym)
|
199
|
+
':"' + sym.to_s + '"'
|
200
|
+
end
|
201
|
+
|
202
|
+
def emit_trap_error(form)
|
203
|
+
_, expr, handler = form
|
204
|
+
err_var = fresh_variable
|
205
|
+
|
206
|
+
expr_rb = emit_ruby(expr)
|
207
|
+
apply_handler_rb = emit_application([handler, err_var])
|
208
|
+
err_var_rb = emit_ruby(err_var)
|
209
|
+
|
210
|
+
render_string('(begin; $2; rescue => $1; $3; end)', err_var_rb,
|
211
|
+
expr_rb, apply_handler_rb)
|
212
|
+
end
|
213
|
+
|
214
|
+
# Escape single quotes and backslashes
|
215
|
+
def escape_string(str)
|
216
|
+
new_str = ""
|
217
|
+
str.each_char do |c|
|
218
|
+
if c == "'"
|
219
|
+
new_str << "\\"
|
220
|
+
new_str << "'"
|
221
|
+
elsif c == '\\'
|
222
|
+
new_str << '\\'
|
223
|
+
new_str << '\\'
|
224
|
+
else
|
225
|
+
new_str << c
|
226
|
+
end
|
227
|
+
end
|
228
|
+
new_str
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Klam
|
2
|
+
module CompilationStages
|
3
|
+
# Kl to Internal Represenation
|
4
|
+
#
|
5
|
+
# To simplify coding and improve performance, the compiler uses arrays
|
6
|
+
# rather than Kl lists to represent nested s-expressions. This stage
|
7
|
+
# performs the conversion.
|
8
|
+
module KlToInternalRepresentation
|
9
|
+
include Klam::Primitives::Lists
|
10
|
+
include Klam::Converters::List
|
11
|
+
|
12
|
+
def kl_to_internal_representation(kl)
|
13
|
+
if cons?(kl) || kl == Klam::Primitives::Lists::EMPTY_LIST
|
14
|
+
listToArray(kl)
|
15
|
+
else
|
16
|
+
kl
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Klam
|
2
|
+
module CompilationStages
|
3
|
+
# Make Abstractions Monadic
|
4
|
+
#
|
5
|
+
# By the time the Ruby code is emitted, we want abstractions to take
|
6
|
+
# no more than one parameter. This simplifies code generation and
|
7
|
+
# avoids the need for using Proc#curry, which is slow.
|
8
|
+
#
|
9
|
+
# This stage converts (lambda [X Y] form) to (lambda [X] (lambda [Y] form))
|
10
|
+
module MakeAbstractionsMonadic
|
11
|
+
def make_abstractions_monadic(sexp)
|
12
|
+
if sexp.instance_of?(Array)
|
13
|
+
if sexp[0] == :lambda && sexp[1].length > 1
|
14
|
+
params = sexp[1]
|
15
|
+
[:lambda, [params[0]],
|
16
|
+
make_abstractions_monadic([:lambda, params[1..-1], sexp[2]])]
|
17
|
+
else
|
18
|
+
sexp.map { |form| make_abstractions_monadic(form) }
|
19
|
+
end
|
20
|
+
else
|
21
|
+
sexp
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Klam
|
2
|
+
module CompilationStages
|
3
|
+
# Make Abstractions Variadic
|
4
|
+
#
|
5
|
+
# Kl's lambda special form only accepts a single parameter for the
|
6
|
+
# abstraction. It is useful internally, however, to allow abstractions to
|
7
|
+
# have zero or more parameters.
|
8
|
+
module MakeAbstractionsVariadic
|
9
|
+
def make_abstractions_variadic(sexp)
|
10
|
+
if sexp.instance_of?(Array)
|
11
|
+
if sexp[0] == :lambda
|
12
|
+
rator, param, form = sexp
|
13
|
+
[rator, [param], make_abstractions_variadic(form)]
|
14
|
+
else
|
15
|
+
sexp.map { |form| make_abstractions_variadic(form) }
|
16
|
+
end
|
17
|
+
else
|
18
|
+
sexp
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Klam
|
2
|
+
module CompilationStages
|
3
|
+
# Simplify Boolean Operations
|
4
|
+
#
|
5
|
+
# Kl defines four special forms for boolean operations: if, or, and, and
|
6
|
+
# cond. Having if or cond alone is sufficient for implementing the complete
|
7
|
+
# set. This compilation stages recasts or, and, and cond in terms of if.
|
8
|
+
module SimplifyBooleanOperations
|
9
|
+
def simplify_boolean_operations(sexp)
|
10
|
+
if sexp.instance_of?(Array)
|
11
|
+
case sexp[0]
|
12
|
+
when :and
|
13
|
+
simplify_and(sexp)
|
14
|
+
when :cond
|
15
|
+
simplify_cond(sexp)
|
16
|
+
when :or
|
17
|
+
simplify_or(sexp)
|
18
|
+
else
|
19
|
+
sexp.map { |form| simplify_boolean_operations(form) }
|
20
|
+
end
|
21
|
+
else
|
22
|
+
sexp
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def simplify_and(sexp)
|
29
|
+
# Only convert the fully-applied case
|
30
|
+
if sexp.length == 3
|
31
|
+
_, expr1, expr2 = sexp
|
32
|
+
expr1 = simplify_boolean_operations(expr1)
|
33
|
+
expr2 = simplify_boolean_operations(expr2)
|
34
|
+
|
35
|
+
[:if, expr1, expr2, false]
|
36
|
+
else
|
37
|
+
sexp.map { |form| simplify_boolean_operations(form) }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def simplify_cond(sexp)
|
42
|
+
# Cond expressions are of the form:
|
43
|
+
# (cond (Test1 Expr1) ... (TestN ExprN))
|
44
|
+
clauses = sexp[1..-1]
|
45
|
+
simplify_cond_clauses(clauses)
|
46
|
+
end
|
47
|
+
|
48
|
+
def simplify_cond_clauses(clauses)
|
49
|
+
if clauses.empty?
|
50
|
+
# An error is raised if none of the clauses match.
|
51
|
+
[:"simple-error", 'cond failure']
|
52
|
+
else
|
53
|
+
test, expr = clauses[0]
|
54
|
+
test = simplify_boolean_operations(test)
|
55
|
+
expr = simplify_boolean_operations(expr)
|
56
|
+
[:if, test, expr, simplify_cond_clauses(clauses[1..-1])]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def simplify_or(sexp)
|
61
|
+
# Only convert the fully-applied case
|
62
|
+
if sexp.length == 3
|
63
|
+
_, expr1, expr2 = sexp
|
64
|
+
expr1 = simplify_boolean_operations(expr1)
|
65
|
+
expr2 = simplify_boolean_operations(expr2)
|
66
|
+
|
67
|
+
[:if, expr1, true, expr2]
|
68
|
+
else
|
69
|
+
sexp.map { |form| simplify_boolean_operations(form) }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Klam
|
2
|
+
module CompilationStages
|
3
|
+
# Strip Type Declarations
|
4
|
+
#
|
5
|
+
# The Kl type primitive is provided to pass type hints from the Shen
|
6
|
+
# compiler to the Kl compiler. Klam does not make use of them, so they are
|
7
|
+
# removed in this stage to simplify the s-expression and avoid having to
|
8
|
+
# take the type primitive into account when optimizing self tail calls.
|
9
|
+
module StripTypeDeclarations
|
10
|
+
def strip_type_declarations(sexp)
|
11
|
+
if sexp.instance_of?(Array)
|
12
|
+
if sexp[0] == :type
|
13
|
+
_, form, _ = sexp
|
14
|
+
strip_type_declarations(form)
|
15
|
+
else
|
16
|
+
sexp.map { |form| strip_type_declarations(form) }
|
17
|
+
end
|
18
|
+
else
|
19
|
+
sexp
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Klam
|
2
|
+
class Compiler
|
3
|
+
# Compiliation is implmented as a series of stages, each defined in
|
4
|
+
# its own module. The first stage converts Kl s-expressions to the
|
5
|
+
# the compiler's internal represenation. The final stage converts
|
6
|
+
# the internal representation of a string containing Ruby code
|
7
|
+
# suitable for use with instance_eval in the context of a
|
8
|
+
# Klam::Environment instance.
|
9
|
+
|
10
|
+
include Klam::CompilationStages::KlToInternalRepresentation
|
11
|
+
include Klam::CompilationStages::StripTypeDeclarations
|
12
|
+
include Klam::CompilationStages::MakeAbstractionsVariadic
|
13
|
+
include Klam::CompilationStages::ConvertLexicalVariables
|
14
|
+
include Klam::CompilationStages::ConvertFreezesToLambdas
|
15
|
+
include Klam::CompilationStages::SimplifyBooleanOperations
|
16
|
+
include Klam::CompilationStages::ConvertPartialApplicationsToLambdas
|
17
|
+
include Klam::CompilationStages::CurryAbstractionApplications
|
18
|
+
include Klam::CompilationStages::MakeAbstractionsMonadic
|
19
|
+
include Klam::CompilationStages::ConvertSelfTailCallsToLoops
|
20
|
+
include Klam::CompilationStages::EmitRuby
|
21
|
+
|
22
|
+
def initialize(environment)
|
23
|
+
@environment = environment
|
24
|
+
@generator = Klam::VariableGenerator.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def compile(kl)
|
28
|
+
stages = [
|
29
|
+
:kl_to_internal_representation,
|
30
|
+
:strip_type_declarations,
|
31
|
+
:make_abstractions_variadic,
|
32
|
+
:convert_lexical_variables,
|
33
|
+
:convert_freezes_to_lambdas,
|
34
|
+
:simplify_boolean_operations,
|
35
|
+
:convert_partial_applications_to_lambdas,
|
36
|
+
:curry_abstraction_applications,
|
37
|
+
:make_abstractions_monadic,
|
38
|
+
:convert_self_tail_calls_to_loops,
|
39
|
+
:emit_ruby
|
40
|
+
]
|
41
|
+
apply_stages(stages, kl)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def apply_stages(stages, kl)
|
47
|
+
stages.reduce(kl) do |exp, stage|
|
48
|
+
send(stage, exp)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def arity(sym)
|
53
|
+
@environment.__arity(sym)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns a new Klam::Variable object that is unique within this
|
57
|
+
# instance of the compiler. Variables are never used in a global
|
58
|
+
# context in Kl, so this is sufficient to avoid collisions.
|
59
|
+
def fresh_variable
|
60
|
+
@generator.next
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|