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
data/lib/klam/cons.rb ADDED
@@ -0,0 +1,18 @@
1
+ module Klam
2
+ class Cons
3
+ attr_reader :hd, :tl
4
+
5
+ def initialize(hd, tl)
6
+ @hd = hd
7
+ @tl = tl
8
+ end
9
+
10
+ def ==(other)
11
+ other.instance_of?(Cons) && other.hd == @hd && other.tl == @tl
12
+ end
13
+
14
+ def hash
15
+ [@hd, @tl].hash
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,4 @@
1
+ this_dir = File.dirname(__FILE__)
2
+ Dir[File.join(this_dir, 'converters', '*.rb')].each do |file|
3
+ require file
4
+ end
Binary file
@@ -0,0 +1,29 @@
1
+ module Klam
2
+ module Converters
3
+ # Utility methods to convert between Ruby Arrays and Kl Lists
4
+ module List
5
+ include Klam::Primitives::Lists
6
+
7
+ def arrayToList(array)
8
+ list = EMPTY_LIST
9
+ (array.length - 1).downto(0) do |index|
10
+ item = array[index]
11
+ item = arrayToList(item) if item.kind_of?(Array)
12
+ list = cons(item, list)
13
+ end
14
+ list
15
+ end
16
+
17
+ def listToArray(list)
18
+ array = []
19
+ while list != EMPTY_LIST
20
+ item = hd(list)
21
+ item = listToArray(item) if cons?(item) || item == EMPTY_LIST
22
+ array << item
23
+ list = tl(list)
24
+ end
25
+ array
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,61 @@
1
+ module Klam
2
+ class Environment < BasicObject
3
+ include ::Klam::Primitives::BooleanOperations
4
+ include ::Klam::Primitives::Symbols
5
+ include ::Klam::Primitives::Strings
6
+ include ::Klam::Primitives::Assignments
7
+ include ::Klam::Primitives::ErrorHandling
8
+ include ::Klam::Primitives::Lists
9
+ include ::Klam::Primitives::GenericFunctions
10
+ include ::Klam::Primitives::Vectors
11
+ include ::Klam::Primitives::Streams
12
+ include ::Klam::Primitives::Time
13
+ include ::Klam::Primitives::Arithmetic
14
+
15
+ def initialize
16
+ # The global assignments namespace. Errors are thrown here in the
17
+ # missing element handler rather than in the value primitive in order
18
+ # to facilitate inlining later.
19
+ @assignments = ::Hash.new do |_, name|
20
+ ::Kernel.raise ::Klam::Error, "The variable #{name} is unbound."
21
+ end
22
+
23
+ @arities = ::Hash.new { |h, k| h[k] = __arity(k) }
24
+ @curried_methods = ::Hash.new { |h, k| h[k] = __method(k).to_proc.curry }
25
+ @loop_cache = {}
26
+
27
+ # Grab a handle to this object's eigenclass for use later when the
28
+ # compiled code needs to reference it. It is used, e.g., when renaming
29
+ # methods.
30
+ @eigenclass = class << self; self; end
31
+
32
+ # The open primitive depends on having *home-directory* assigned.
33
+ set(:"*home-directory*", ::Dir.pwd)
34
+ end
35
+
36
+ def __apply(rator, *rands)
37
+ if rator.kind_of?(::Symbol)
38
+ @curried_methods[rator].call(*rands)
39
+ else
40
+ rator.call(*rands)
41
+ end
42
+ end
43
+
44
+ def __method(sym)
45
+ @eigenclass.instance_method(sym).bind(self)
46
+ end
47
+
48
+ def __arity(sym)
49
+ @eigenclass.instance_method(sym).arity
50
+ rescue ::NameError
51
+ -1
52
+ end
53
+
54
+ class << self
55
+ def rename_method(old_name, new_name)
56
+ alias_method(new_name, old_name)
57
+ remove_method(old_name)
58
+ end
59
+ end
60
+ end
61
+ end
data/lib/klam/error.rb ADDED
@@ -0,0 +1,4 @@
1
+ module Klam
2
+ class Error < StandardError; end
3
+ class SyntaxError < Error; end
4
+ end
data/lib/klam/lexer.rb ADDED
@@ -0,0 +1,185 @@
1
+ require 'singleton'
2
+
3
+ module Klam
4
+ class Lexer
5
+ SYMBOL_CHARS = /[-=*\/+_?$!\@~><&%'#`;:{}a-zA-Z0-9.]/
6
+
7
+ # Syntax tokens
8
+ class OpenParen
9
+ include Singleton
10
+ end
11
+ class CloseParen
12
+ include Singleton
13
+ end
14
+
15
+ def initialize(stream)
16
+ @stream = stream
17
+ @buffer = []
18
+ end
19
+
20
+ def eof?
21
+ @buffer.empty? && @stream.eof?
22
+ end
23
+
24
+ def getc
25
+ if @buffer.empty?
26
+ @stream.getc
27
+ else
28
+ @buffer.pop
29
+ end
30
+ end
31
+
32
+ def ungetc(c)
33
+ @buffer.push(c)
34
+ end
35
+
36
+ def next
37
+ drain_whitespace
38
+ unless eof?
39
+ c = getc
40
+ case c
41
+ when '('
42
+ OpenParen.instance
43
+ when ')'
44
+ CloseParen.instance
45
+ when '"'
46
+ consume_string
47
+ when SYMBOL_CHARS
48
+ ungetc(c)
49
+ consume_number_or_symbol
50
+ else
51
+ raise Klam::SyntaxError, "illegal character: #{c}"
52
+ end
53
+ end
54
+ end
55
+
56
+ private
57
+ def drain_whitespace
58
+ until eof?
59
+ c = getc
60
+ if c =~ /\S/
61
+ ungetc(c)
62
+ break
63
+ end
64
+ end
65
+ end
66
+
67
+ def consume_string
68
+ chars = []
69
+ loop do
70
+ raise Klam::SyntaxError, "unterminated string" if eof?
71
+ c = getc
72
+ break if c == '"'
73
+ chars << c
74
+ end
75
+ chars.join
76
+ end
77
+
78
+ def consume_number
79
+ # Shen allows multiple leading plusses and minuses. The plusses
80
+ # are ignored and an even number of minuses cancel each other.
81
+ # Thus '------+-7' is read as 7.
82
+ #
83
+ # The Shen reader parses "7." as the integer 7 and the symbol '.'
84
+ decimal_seen = false
85
+ negative = false
86
+ past_sign = false
87
+ chars = []
88
+ loop do
89
+ break if eof?
90
+ c = getc
91
+ if c =~ /\d/
92
+ past_sign = true
93
+ chars << c
94
+ elsif c == '.' && !decimal_seen
95
+ past_sign = true
96
+ decimal_seen = true
97
+ chars << c
98
+ elsif c == '+' && !past_sign
99
+ # ignore
100
+ elsif c == '-' && !past_sign
101
+ negative = !negative
102
+ else
103
+ ungetc c
104
+ break
105
+ end
106
+ end
107
+ chars.unshift('-') if negative
108
+ if chars.last == '.'
109
+ # A trailing decimal point is treated as part of the next
110
+ # token. Forget we saw it.
111
+ ungetc(chars.pop)
112
+ decimal_seen = false
113
+ end
114
+ str = chars.join
115
+ decimal_seen ? str.to_f : str.to_i
116
+ end
117
+
118
+ def consume_symbol
119
+ chars = []
120
+ loop do
121
+ break if eof?
122
+ c = getc
123
+ unless c =~ SYMBOL_CHARS
124
+ ungetc c
125
+ break
126
+ end
127
+ chars << c
128
+ end
129
+ str = chars.join
130
+
131
+ case str
132
+ when 'true'
133
+ true
134
+ when 'false'
135
+ false
136
+ else
137
+ str.to_sym
138
+ end
139
+ end
140
+
141
+ def consume_number_or_symbol
142
+ # First drain optional leading signs
143
+ # Then drain optional decimal point
144
+ # If there is another character and it is a digit, then it
145
+ # is a number. Otherwise it is a symbol.
146
+ chars = []
147
+ loop do
148
+ break if eof?
149
+ c = getc
150
+ unless c =~ /[-+]/
151
+ ungetc c
152
+ break
153
+ end
154
+ chars << c
155
+ end
156
+ if eof?
157
+ chars.reverse.each {|x| ungetc x}
158
+ return consume_symbol
159
+ end
160
+
161
+ c = getc
162
+ chars << c
163
+ if c == '.'
164
+ if eof?
165
+ chars.reverse.each {|x| ungetc x}
166
+ return consume_symbol
167
+ end
168
+ c = getc
169
+ chars << c
170
+ chars.reverse.each {|x| ungetc x}
171
+ if c =~ /\d/
172
+ return consume_number
173
+ else
174
+ return consume_symbol
175
+ end
176
+ elsif c =~ /\d/
177
+ chars.reverse.each {|x| ungetc x}
178
+ return consume_number
179
+ else
180
+ chars.reverse.each {|x| ungetc x}
181
+ return consume_symbol
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,4 @@
1
+ this_dir = File.dirname(__FILE__)
2
+ Dir[File.join(this_dir, 'primitives', '*.rb')].each do |file|
3
+ require file
4
+ end
@@ -0,0 +1,49 @@
1
+ module Klam
2
+ module Primitives
3
+ module Arithmetic
4
+ def +(a, b)
5
+ a + b
6
+ end
7
+
8
+ def -(a, b)
9
+ a - b
10
+ end
11
+
12
+ def *(a, b)
13
+ a * b
14
+ end
15
+
16
+ def /(a, b)
17
+ # Kl does not make a distinction between integers and reals. Dividing
18
+ # the integer 3 by the interger 2 must yield 1.5 rather than 1. We'd
19
+ # like to keep things in integers as much as possible, so we coerce a
20
+ # to a float only if integer division is not possible.
21
+ if a.kind_of?(Fixnum) && b.kind_of?(Fixnum) && a.remainder(b) != 0
22
+ a = a.to_f
23
+ end
24
+
25
+ a / b
26
+ end
27
+
28
+ def <(a, b)
29
+ a < b
30
+ end
31
+
32
+ def >(a, b)
33
+ a > b
34
+ end
35
+
36
+ def <=(a, b)
37
+ a <= b
38
+ end
39
+
40
+ def >=(a, b)
41
+ a >= b
42
+ end
43
+
44
+ def number?(a)
45
+ a.kind_of?(Numeric)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,13 @@
1
+ module Klam
2
+ module Primitives
3
+ module Assignments
4
+ def set(name, value)
5
+ @assignments[name] = value
6
+ end
7
+
8
+ def value(name)
9
+ @assignments[name]
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,22 @@
1
+ module Klam
2
+ module Primitives
3
+ # All of the boolean operations are special forms, but if, and, and or
4
+ # are also available as normal functions to facilitate partial application.
5
+ # When partially applied, they are no longer short circuiting.
6
+ module BooleanOperations
7
+ def _if(a, b, c)
8
+ a ? b : c
9
+ end
10
+ alias_method :'if', :_if
11
+ remove_method :_if
12
+
13
+ def and(a, b)
14
+ a && b
15
+ end
16
+
17
+ def or(a, b)
18
+ a || b
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ module Klam
2
+ module Primitives
3
+ module ErrorHandling
4
+ def simple_error(msg)
5
+ ::Kernel.raise ::Klam::Error, msg
6
+ end
7
+ alias_method :"simple-error", :simple_error
8
+ remove_method :simple_error
9
+
10
+ # trap-error is a special form and implemented in the compiler
11
+
12
+ def error_to_string(err)
13
+ err.message
14
+ end
15
+ alias_method :"error-to-string", :error_to_string
16
+ remove_method :error_to_string
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module Klam
2
+ module Primitives
3
+ module GenericFunctions
4
+ def equal(a, b)
5
+ a == b
6
+ end
7
+ alias_method :"=", :equal
8
+ remove_method :equal
9
+
10
+ def eval_kl(form)
11
+ compiler = Klam::Compiler.new(self)
12
+ code = compiler.compile(form)
13
+ instance_eval code
14
+ end
15
+ alias_method :"eval-kl", :eval_kl
16
+ remove_method :eval_kl
17
+ end
18
+ end
19
+ end