klam 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.rspec +0 -0
  4. data/.travis.yml +8 -0
  5. data/Gemfile +1 -0
  6. data/LICENSE.txt +19 -0
  7. data/PROGRESS.asciidoc +105 -0
  8. data/README.asciidoc +11 -0
  9. data/Rakefile +5 -0
  10. data/klam.gemspec +28 -0
  11. data/lib/klam.rb +16 -0
  12. data/lib/klam/compilation_stages.rb +4 -0
  13. data/lib/klam/compilation_stages/convert_freezes_to_lambdas.rb +17 -0
  14. data/lib/klam/compilation_stages/convert_lexical_variables.rb +79 -0
  15. data/lib/klam/compilation_stages/convert_partial_applications_to_lambdas.rb +35 -0
  16. data/lib/klam/compilation_stages/convert_self_tail_calls_to_loops.rb +147 -0
  17. data/lib/klam/compilation_stages/curry_abstraction_applications.rb +33 -0
  18. data/lib/klam/compilation_stages/emit_ruby.rb +232 -0
  19. data/lib/klam/compilation_stages/kl_to_internal_representation.rb +21 -0
  20. data/lib/klam/compilation_stages/make_abstractions_monadic.rb +26 -0
  21. data/lib/klam/compilation_stages/make_abstractions_variadic.rb +23 -0
  22. data/lib/klam/compilation_stages/simplify_boolean_operations.rb +74 -0
  23. data/lib/klam/compilation_stages/strip_type_declarations.rb +24 -0
  24. data/lib/klam/compiler.rb +63 -0
  25. data/lib/klam/cons.rb +18 -0
  26. data/lib/klam/converters.rb +4 -0
  27. data/lib/klam/converters/.list.rb.swp +0 -0
  28. data/lib/klam/converters/list.rb +29 -0
  29. data/lib/klam/environment.rb +61 -0
  30. data/lib/klam/error.rb +4 -0
  31. data/lib/klam/lexer.rb +185 -0
  32. data/lib/klam/primitives.rb +4 -0
  33. data/lib/klam/primitives/arithmetic.rb +49 -0
  34. data/lib/klam/primitives/assignments.rb +13 -0
  35. data/lib/klam/primitives/boolean_operations.rb +22 -0
  36. data/lib/klam/primitives/error_handling.rb +19 -0
  37. data/lib/klam/primitives/generic_functions.rb +19 -0
  38. data/lib/klam/primitives/lists.rb +23 -0
  39. data/lib/klam/primitives/streams.rb +32 -0
  40. data/lib/klam/primitives/strings.rb +58 -0
  41. data/lib/klam/primitives/symbols.rb +16 -0
  42. data/lib/klam/primitives/time.rb +19 -0
  43. data/lib/klam/primitives/vectors.rb +26 -0
  44. data/lib/klam/reader.rb +46 -0
  45. data/lib/klam/template.rb +38 -0
  46. data/lib/klam/variable.rb +21 -0
  47. data/lib/klam/variable_generator.rb +12 -0
  48. data/lib/klam/version.rb +3 -0
  49. data/spec/functional/application_spec.rb +94 -0
  50. data/spec/functional/atoms_spec.rb +56 -0
  51. data/spec/functional/extensions/do_spec.rb +22 -0
  52. data/spec/functional/primitives/assignments_spec.rb +38 -0
  53. data/spec/functional/primitives/boolean_operations_spec.rb +133 -0
  54. data/spec/functional/primitives/error_handling_spec.rb +22 -0
  55. data/spec/functional/primitives/generic_functions_spec.rb +82 -0
  56. data/spec/functional/tail_call_optimization_spec.rb +71 -0
  57. data/spec/spec_helper.rb +27 -0
  58. data/spec/unit/klam/compilation_stages/convert_lexical_variables_spec.rb +58 -0
  59. data/spec/unit/klam/compilation_stages/convert_self_tail_calls_to_loops_spec.rb +33 -0
  60. data/spec/unit/klam/compilation_stages/curry_abstraction_applications_spec.rb +19 -0
  61. data/spec/unit/klam/compilation_stages/make_abstractions_variadic_spec.rb +12 -0
  62. data/spec/unit/klam/converters/list_spec.rb +57 -0
  63. data/spec/unit/klam/lexer_spec.rb +149 -0
  64. data/spec/unit/klam/primitives/arithmetic_spec.rb +153 -0
  65. data/spec/unit/klam/primitives/boolean_operations_spec.rb +39 -0
  66. data/spec/unit/klam/primitives/error_handling_spec.rb +19 -0
  67. data/spec/unit/klam/primitives/lists_spec.rb +49 -0
  68. data/spec/unit/klam/primitives/strings_spec.rb +53 -0
  69. data/spec/unit/klam/primitives/symbols_spec.rb +19 -0
  70. data/spec/unit/klam/primitives/time_spec.rb +16 -0
  71. data/spec/unit/klam/primitives/vectors_spec.rb +55 -0
  72. data/spec/unit/klam/reader_spec.rb +47 -0
  73. data/spec/unit/klam/template_spec.rb +28 -0
  74. data/spec/unit/klam/variable_spec.rb +22 -0
  75. data/spec/unit/klam/version_spec.rb +7 -0
  76. 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