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