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,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'extension: (do X Y)', :type => :functional do
4
+ it 'evaluates X before Y' do
5
+ eval_kl('(set foo "a")')
6
+ eval_kl('(do (set foo (cn (value foo) "b")) (set foo (cn (value foo) "c")))')
7
+ expect_kl('(value foo)').to eq("abc")
8
+ end
9
+
10
+ it 'returns the normal form of Y' do
11
+ expect_kl('(do 37 (+ 40 2))').to eq(42)
12
+ end
13
+
14
+ it 'treats Y as being in tail position' do
15
+ eval_kl('(defun count-down (X) (if (> X 0) (do ignore-me (count-down (- X 1))) true))')
16
+ expect_kl('(count-down 20000)').to be(true)
17
+ end
18
+
19
+ it 'allows nesting calls' do
20
+ expect_kl('(do (+ 1 0) (do (+ 1 1) (+ 1 2)))').to eq(3)
21
+ end
22
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper.rb'
2
+
3
+ describe 'Assignments primitives', :type => :functional do
4
+ describe '(set Name Value)' do
5
+ it 'associates Value with Name' do
6
+ eval_kl('(set foo bar)')
7
+ expect_kl('(value foo)').to eq(:bar)
8
+ end
9
+
10
+ it 'returns Value' do
11
+ expect_kl('(set foo bar)').to eq(:bar)
12
+ end
13
+
14
+ it 'overwrites the previous value when called again with same Name' do
15
+ eval_kl('(set foo bar)')
16
+ expect_kl('(set foo baz)').to eq(:baz)
17
+ end
18
+
19
+ it 'does not interfere with the function namespace' do
20
+ eval_kl('(set value bar)')
21
+ expect_kl('(value value)').to eq(:bar)
22
+ end
23
+ end
24
+
25
+ describe '(value Name)' do
26
+ # Duplicated for documentation purposes
27
+ it 'returns the value associated with Name' do
28
+ eval_kl('(set foo bar)')
29
+ expect_kl('(value foo)').to eq(:bar)
30
+ end
31
+
32
+ it 'raises an error if Name has not previously been set' do
33
+ expect {
34
+ eval_kl('(value an-unset-symobl)')
35
+ }.to raise_error(Klam::Error)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,133 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Boolean operation primitives', :type => :functional do
4
+ describe '(and Expr1 Expr2)' do
5
+ describe 'when Expr1 evaluates to true' do
6
+ it 'returns the result of evaluating Expr2' do
7
+ expect_kl('(and (< 1 2) (> 3 3))').to be(false)
8
+ expect_kl('(and (< 1 2) (>= 3 3))').to be(true)
9
+ end
10
+ end
11
+
12
+ describe 'when Expr1 evaluates to false' do
13
+ it 'returns false' do
14
+ expect_kl('(and (> 1 2) true)').to be(false)
15
+ end
16
+
17
+ it 'does not evaluate Expr2' do
18
+ eval_kl('(set success true)')
19
+ eval_kl('(and (> 1 2) (set success false))')
20
+ expect_kl('(value success)').to be(true)
21
+ end
22
+ end
23
+
24
+ it 'is available for partial application' do
25
+ expect_kl('((and true) true)').to be(true)
26
+ end
27
+ end
28
+
29
+ describe '(cond Test1 Expr1 ... TestN ExprN)' do
30
+ it 'returns the normal form of the Expr for the first true Test' do
31
+ kl = <<-EOKL
32
+ (cond
33
+ ((< 3 3) 1)
34
+ ((= 3 3) 2)
35
+ ((= 1 1) 3))
36
+ EOKL
37
+
38
+ expect_kl(kl).to eq(2)
39
+ end
40
+
41
+ it 'does not evaluate the other Exprs' do
42
+ eval_kl('(set expr1-was-evaluated false)')
43
+ eval_kl('(set expr2-was-evaluated false)')
44
+ eval_kl('(set expr3-was-evaluated false)')
45
+
46
+ kl = <<-EOKL
47
+ (cond
48
+ ((< 3 3) (set expr1-was-evaluated true))
49
+ ((<= 3 3) (set expr2-was-evaluated true))
50
+ ((<= 1 1) (set expr3-was-evaluated true)))
51
+ EOKL
52
+ eval_kl(kl)
53
+
54
+ expect_kl('(value expr1-was-evaluated)').to be(false)
55
+ expect_kl('(value expr2-was-evaluated)').to be(true)
56
+ expect_kl('(value expr3-was-evaluated)').to be(false)
57
+ end
58
+
59
+ it 'does not evaluate the subsequent tests' do
60
+ eval_kl('(set test3-was-evaluated false)')
61
+
62
+ kl = <<-EOKL
63
+ (cond
64
+ ((< 3 3) 1)
65
+ ((<= 3 3) 2)
66
+ ((set test3-was-evaluated true) 3))
67
+ EOKL
68
+ eval_kl(kl)
69
+
70
+ expect_kl('(value test3-was-evaluated)').to be(false)
71
+ end
72
+
73
+ it 'raises an error if none of the tests evaluate to true' do
74
+ expect_kl('(trap-error (cond (false false)) error-to-string)')
75
+ .to eq('cond failure')
76
+ end
77
+ end
78
+
79
+ describe '(if Test TrueExpr FalseExpr)' do
80
+ describe 'when Test evaluates to true' do
81
+ it 'returns the value of TrueExpr' do
82
+ expect_kl('(if (< 1 2) yes no)').to eq(:yes)
83
+ end
84
+
85
+ it 'does not evaluate FalseExpr' do
86
+ eval_kl('(set success true)')
87
+ eval_kl('(if (< 1 2) yes (set success false))')
88
+ expect_kl('(value success)').to be(true)
89
+ end
90
+ end
91
+
92
+ describe 'when Test evaluates to false' do
93
+ it 'returns the value of FalseExpr' do
94
+ expect_kl('(if (> 1 2) yes no)').to eq(:no)
95
+ end
96
+
97
+ it 'does not evaluate TrueExpr' do
98
+ eval_kl('(set success true)')
99
+ eval_kl('(if (> 1 2) (set success false) no)')
100
+ expect_kl('(value success)').to be(true)
101
+ end
102
+ end
103
+
104
+ it 'is available for partial application' do
105
+ expect_kl('(((if false) 1) 37)').to eq(37)
106
+ end
107
+ end
108
+
109
+ describe '(or Expr1 Expr2)' do
110
+ describe 'when Expr1 evaluates to true' do
111
+ it 'returns true' do
112
+ expect_kl('(or (< 1 2) false)').to be(true)
113
+ end
114
+
115
+ it 'does not evaluate Expr2' do
116
+ eval_kl('(set success true)')
117
+ eval_kl('(or (< 1 2) (set success false))')
118
+ expect_kl('(value success)').to be(true)
119
+ end
120
+ end
121
+
122
+ describe 'when Expr1 evaluates to false' do
123
+ it 'returns the result of evaluating Expr2' do
124
+ expect_kl('(or (> 1 2) (> 3 3))').to be(false)
125
+ expect_kl('(or (> 1 2) (>= 3 3))').to be(true)
126
+ end
127
+ end
128
+
129
+ it 'is available for partial application' do
130
+ expect_kl('((or false) false)').to be(false)
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Error handling primitives', :type => :functional do
4
+ describe '(trap-error Expr Handler)' do
5
+ describe 'when evaluating Expr succeeds' do
6
+ it 'returns the normal form of Expr' do
7
+ expect_kl('(trap-error (+ 1 2) error-to-string)').to eq(3)
8
+ end
9
+ end
10
+
11
+ describe 'when evaluating Expr raises an error' do
12
+ it 'applies Handler to the error' do
13
+ expect_kl('(trap-error (simple-error "oops!") error-to-string)')
14
+ .to eq('oops!')
15
+ end
16
+ end
17
+
18
+ it 'may be used as an argument to another function' do
19
+ expect_kl('(+ (trap-error 2 error-to-string) 3)').to eq(5)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Generic function primitives', :type => :functional do
4
+ describe '(defun Name Params Expr)' do
5
+ it 'returns Name' do
6
+ expect_kl('(defun foo (X) X)').to eq(:foo)
7
+ end
8
+
9
+ it 'does not evaluate Expr' do
10
+ eval_kl('(set success true)')
11
+ eval_kl('(defun foo (X) (set success false))')
12
+ expect_kl('(value success)').to be(true)
13
+ end
14
+
15
+ it 'installs Name as a global function' do
16
+ eval_kl('(defun foo (X) X)')
17
+ expect_kl('(foo 37)').to eq(37)
18
+ end
19
+
20
+ it 'allows names that are not allowed with the Ruby define syntax' do
21
+ eval_kl('(defun f-o-o (X) X)')
22
+ expect_kl('(f-o-o 37)').to eq(37)
23
+ end
24
+
25
+ it 'allows parameter names that are not valid Ruby parameter names' do
26
+ eval_kl('(defun foo (A!B?-C) A!B?-C)')
27
+ expect_kl('(foo 37)').to eq(37)
28
+ end
29
+
30
+ it 'allows params to be empty' do
31
+ eval_kl('(defun foo () 37)')
32
+ expect_kl('(foo)').to eq(37)
33
+ end
34
+ end
35
+
36
+ describe '(lambda Var Expr)' do
37
+ it 'returns a function' do
38
+ expect_kl('(lambda X X)').to be_kind_of(Proc)
39
+ end
40
+
41
+ it 'does not evaluate Expr' do
42
+ eval_kl('(set success true)')
43
+ eval_kl('(lambda X (set success false))')
44
+ expect_kl('(value success)').to be(true)
45
+ end
46
+ end
47
+
48
+ describe '(let Var ValueExpr BodyExpr)' do
49
+ it 'evaluates BodyExpr with Var bound to the normal form of ValueExpr' do
50
+ expect_kl('(let X (+ 1 2) (* X 2))').to eq(6)
51
+ end
52
+
53
+ it 'uses the inner-most binding when Var is shadowed' do
54
+ expect_kl('(let X 1 (let X 2 X))').to eq(2)
55
+ end
56
+ end
57
+
58
+ describe '(freeze Expr)' do
59
+ it 'returns a function' do
60
+ expect_kl('(freeze (+ 30 7))').to be_kind_of(Proc)
61
+ end
62
+
63
+ it 'does not evaluate Expr' do
64
+ eval_kl('(set success true)')
65
+ eval_kl('(freeze (set success false))')
66
+ expect_kl('(value success)').to be(true)
67
+ end
68
+
69
+ describe 'when thawed' do
70
+ it 'evaluates the frozen Expr and returns the result' do
71
+ eval_kl('(defun thaw (Thunk) (Thunk))')
72
+ expect_kl('(thaw (freeze (+ 30 7)))').to eq(37)
73
+ end
74
+ end
75
+ end
76
+
77
+ describe '(type Expr Type)' do
78
+ it 'returns the normal form of Expr' do
79
+ expect_kl('(type (+ 1 2) expr)').to eq(3)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Tail call optimization', :type => :functional do
4
+ it 'optimizes self tail calls in an if true clause' do
5
+ eval_kl('(defun count-down (X) (if (> X 0) (count-down (- X 1)) true))')
6
+ expect_kl('(count-down 20000)').to be(true)
7
+ end
8
+
9
+ it 'optimizes self tail calls in an if false clause' do
10
+ eval_kl('(defun count-down (X) (if (<= X 0) true (count-down (- X 1))))')
11
+ expect_kl('(count-down 20000)').to be(true)
12
+ end
13
+
14
+ it 'optimizes self tail calls in a let body' do
15
+ eval_kl('(defun count-down (X) (if (<= X 0) true (let F 1 (count-down (- X 1)))))')
16
+ expect_kl('(count-down 20000)').to be(true)
17
+ end
18
+
19
+ it 'uses the current values of the params when calculating the new ones' do
20
+ eval_kl <<-EOKL
21
+ (defun fact-iter (N Accum)
22
+ (if (= N 1)
23
+ Accum
24
+ (fact-iter (- N 1) (* N Accum))))
25
+ EOKL
26
+ expect_kl('(fact-iter 5 1)').to eq(120)
27
+ end
28
+
29
+ it 'preserves the value of closed-over function parameters' do
30
+ eval_kl('(set foo (absvector 3))')
31
+ eval_kl <<-EOKL
32
+ (defun do-it (N)
33
+ (if (= N 3)
34
+ true
35
+ (let _ (address-> (value foo) N (freeze N))
36
+ (do-it (+ N 1)))))
37
+ EOKL
38
+ eval_kl('(do-it 0)')
39
+ expect_kl('((<-address (value foo) 0))').to eq(0)
40
+ expect_kl('((<-address (value foo) 1))').to eq(1)
41
+ expect_kl('((<-address (value foo) 2))').to eq(2)
42
+ end
43
+
44
+ it 'preserves the value of closed-over local variables' do
45
+ eval_kl('(set foo (absvector 3))')
46
+ eval_kl <<-EOKL
47
+ (defun do-it (N)
48
+ (if (= N 3)
49
+ true
50
+ (let X N
51
+ (let _ (address-> (value foo) N (freeze X))
52
+ (do-it (+ N 1))))))
53
+ EOKL
54
+ eval_kl('(do-it 0)')
55
+ expect_kl('((<-address (value foo) 0))').to eq(0)
56
+ expect_kl('((<-address (value foo) 1))').to eq(1)
57
+ expect_kl('((<-address (value foo) 2))').to eq(2)
58
+ end
59
+
60
+ it 'supports functions with zero arguments' do
61
+ eval_kl('(set counter 0)')
62
+ eval_kl <<-EOKL
63
+ (defun count-down ()
64
+ (if (= 20000 (set counter (+ (value counter) 1)))
65
+ true
66
+ (count-down)))
67
+ EOKL
68
+ expect_kl('(count-down)').to be(true)
69
+ end
70
+
71
+ end
@@ -0,0 +1,27 @@
1
+ require 'stringio'
2
+ require 'klam'
3
+
4
+ module EvalKl
5
+ def read_kl(str)
6
+ stream = StringIO.new(str)
7
+ reader = Klam::Reader.new(stream)
8
+ reader.next
9
+ end
10
+
11
+ def eval_kl(str)
12
+ form = read_kl(str)
13
+ @env.__send__(:"eval-kl", form)
14
+ end
15
+
16
+ def expect_kl(str)
17
+ expect(eval_kl(str))
18
+ end
19
+ end
20
+
21
+ RSpec.configure do |cfg|
22
+ # Support eval_kl and friends in functional specs
23
+ cfg.include EvalKl, :type => :functional
24
+ cfg.before(:each, :type => :functional) do
25
+ @env = Klam::Environment.new
26
+ end
27
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ describe Klam::CompilationStages::ConvertLexicalVariables do
4
+ include Klam::CompilationStages::ConvertLexicalVariables
5
+
6
+ # ConvertLexicalVariables requires this function to be defined
7
+ # by its including class.
8
+ def fresh_variable
9
+ @generator.next
10
+ end
11
+
12
+ def reset_generator
13
+ @generator = Klam::VariableGenerator.new
14
+ end
15
+
16
+ before(:each) do
17
+ reset_generator
18
+ end
19
+
20
+ describe 'with defun' do
21
+ it 'converts formal param symbols to variables' do
22
+ sexp = [:defun, :foo, [:X, :Y], [:+, :X, :Y]]
23
+ converted = convert_lexical_variables(sexp)
24
+
25
+ reset_generator
26
+ v1, v2 = fresh_variable, fresh_variable
27
+ expected = [:defun, :foo, [v1, v2], [:+, v1, v2]]
28
+
29
+ expect(converted).to eq(expected)
30
+ end
31
+ end
32
+
33
+ describe 'with lambda' do
34
+ it 'converts formal param symbols to variables' do
35
+ sexp = [:lambda, [:X, :Y], [:+, :X, :Y]]
36
+ converted = convert_lexical_variables(sexp)
37
+
38
+ reset_generator
39
+ v1, v2 = fresh_variable, fresh_variable
40
+ expected = [:lambda, [v1, v2], [:+, v1, v2]]
41
+
42
+ expect(converted).to eq(expected)
43
+ end
44
+ end
45
+
46
+ describe 'with let' do
47
+ it 'converts var param to a variable' do
48
+ sexp = [:let, :X, [:+, :X, 1], [:+, :X, 2]]
49
+ converted = convert_lexical_variables(sexp)
50
+
51
+ reset_generator
52
+ v1 = fresh_variable
53
+ expected = [:let, v1, [:+, :X, 1], [:+, v1, 2]]
54
+
55
+ expect(converted).to eq(expected)
56
+ end
57
+ end
58
+ end