skeem 0.2.14 → 0.2.18

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +451 -195
  3. data/.travis.yml +27 -0
  4. data/CHANGELOG.md +35 -1
  5. data/Gemfile +2 -0
  6. data/README.md +125 -56
  7. data/Rakefile +2 -0
  8. data/appveyor.yml +3 -4
  9. data/bin/cubic.skm +4 -0
  10. data/bin/hello-world.skm +1 -0
  11. data/bin/skeem +72 -0
  12. data/lib/skeem/datum_dsl.rb +40 -30
  13. data/lib/skeem/element_visitor.rb +5 -2
  14. data/lib/skeem/grammar.rb +88 -26
  15. data/lib/skeem/interpreter.rb +9 -7
  16. data/lib/skeem/parser.rb +6 -4
  17. data/lib/skeem/primitive/primitive_builder.rb +148 -122
  18. data/lib/skeem/primitive/primitive_procedure.rb +23 -25
  19. data/lib/skeem/runtime.rb +17 -15
  20. data/lib/skeem/s_expr_builder.rb +49 -117
  21. data/lib/skeem/s_expr_nodes.rb +147 -132
  22. data/lib/skeem/skeem_exception.rb +1 -0
  23. data/lib/skeem/skm_binding.rb +9 -11
  24. data/lib/skeem/skm_compound_datum.rb +9 -6
  25. data/lib/skeem/skm_element.rb +15 -13
  26. data/lib/skeem/skm_empty_list.rb +6 -4
  27. data/lib/skeem/skm_exception.rb +9 -0
  28. data/lib/skeem/skm_expression.rb +3 -1
  29. data/lib/skeem/skm_frame.rb +3 -2
  30. data/lib/skeem/skm_pair.rb +26 -18
  31. data/lib/skeem/skm_procedure_exec.rb +11 -6
  32. data/lib/skeem/skm_simple_datum.rb +23 -20
  33. data/lib/skeem/skm_unary_expression.rb +34 -37
  34. data/lib/skeem/standard/base.skm +4 -0
  35. data/lib/skeem/tokenizer.rb +38 -28
  36. data/lib/skeem/version.rb +3 -1
  37. data/lib/skeem.rb +2 -0
  38. data/skeem.gemspec +9 -6
  39. data/spec/skeem/add4.skm +4 -0
  40. data/spec/skeem/datum_dsl_spec.rb +13 -12
  41. data/spec/skeem/element_visitor_spec.rb +14 -10
  42. data/spec/skeem/interpreter_spec.rb +84 -44
  43. data/spec/skeem/lambda_spec.rb +13 -11
  44. data/spec/skeem/parser_spec.rb +23 -19
  45. data/spec/skeem/primitive/primitive_builder_spec.rb +65 -48
  46. data/spec/skeem/primitive/primitive_procedure_spec.rb +14 -12
  47. data/spec/skeem/runtime_spec.rb +20 -18
  48. data/spec/skeem/s_expr_nodes_spec.rb +8 -6
  49. data/spec/skeem/skm_compound_datum_spec.rb +12 -10
  50. data/spec/skeem/skm_element_spec.rb +7 -5
  51. data/spec/skeem/skm_empty_list_spec.rb +7 -5
  52. data/spec/skeem/skm_frame_spec.rb +5 -4
  53. data/spec/skeem/skm_pair_spec.rb +9 -8
  54. data/spec/skeem/skm_procedure_exec_spec.rb +2 -0
  55. data/spec/skeem/skm_simple_datum_spec.rb +24 -22
  56. data/spec/skeem/skm_unary_expression_spec.rb +11 -9
  57. data/spec/skeem/tokenizer_spec.rb +54 -43
  58. data/spec/skeem_spec.rb +2 -0
  59. data/spec/spec_helper.rb +15 -10
  60. metadata +18 -10
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../spec_helper'
2
4
  require_relative '../../lib/skeem/datum_dsl'
3
5
 
@@ -7,12 +9,14 @@ require_relative '../../lib/skeem/element_visitor'
7
9
  module Skeem
8
10
  describe SkmElementVisitor do
9
11
  include DatumDSL
10
- let(:simple_datum) { integer 42 }
11
- let(:listener) do
12
- fake = double('fake-subscriber')
13
- fake.define_singleton_method(:accept_all) {}
14
- fake
15
- end
12
+ let(:simple_datum) { integer 42 }
13
+ let(:listener) do
14
+ fake = double('fake-subscriber')
15
+ fake.define_singleton_method(:accept_all) do
16
+ # Dummy block
17
+ end
18
+ fake
19
+ end
16
20
 
17
21
  # Default instantiation rule
18
22
  subject { SkmElementVisitor.new(simple_datum) }
@@ -122,7 +126,7 @@ module Skeem
122
126
  end
123
127
 
124
128
  it 'should allow the visit of a nested compound datum' do
125
- nested_list = list ['uno', 'twei', 'three']
129
+ nested_list = list %w[uno twei three']
126
130
  vec = vector ['#false', 3, nested_list, 'foo']
127
131
  instance = SkmElementVisitor.new(vec)
128
132
  instance.subscribe(listener)
@@ -132,7 +136,7 @@ module Skeem
132
136
  expect(listener).to receive(:after_simple_datum).with(runtime, vec.members[0]).ordered
133
137
  expect(listener).to receive(:before_simple_datum).with(runtime, vec.members[1]).ordered
134
138
  expect(listener).to receive(:after_simple_datum).with(runtime, vec.members[1]).ordered
135
-
139
+
136
140
  expect(listener).to receive(:before_pair).with(runtime, nested_list).ordered
137
141
  expect(listener).to receive(:before_car).with(runtime, nested_list, nested_list.car).ordered
138
142
  expect(nested_list.car).to eq('uno')
@@ -159,7 +163,7 @@ module Skeem
159
163
  expect(listener).to receive(:after_cdr).with(runtime, nested_list.cdr, nested_list.cdr.cdr).ordered
160
164
  expect(listener).to receive(:after_pair).with(runtime, nested_list.cdr).ordered
161
165
  expect(listener).to receive(:after_cdr).with(runtime, nested_list, nested_list.cdr).ordered
162
- expect(listener).to receive(:after_pair).with(runtime, nested_list).ordered
166
+ expect(listener).to receive(:after_pair).with(runtime, nested_list).ordered
163
167
  expect(listener).to receive(:before_simple_datum).with(runtime, vec.members[3]).ordered
164
168
  expect(listener).to receive(:after_simple_datum).with(runtime, vec.members[3]).ordered
165
169
  expect(listener).to receive(:after_children).with(runtime, vec, vec.members).ordered
@@ -168,4 +172,4 @@ module Skeem
168
172
  end
169
173
  end # context
170
174
  end # describe
171
- end # module
175
+ end # module
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'stringio'
2
4
  require_relative '../spec_helper' # Use the RSpec framework
3
5
  require_relative '../../lib/skeem/datum_dsl'
@@ -14,7 +16,7 @@ module Skeem
14
16
  end
15
17
 
16
18
  it 'could be initialized with a block argument' do
17
- expect { Interpreter.new { |interp| } }.not_to raise_error
19
+ expect { Interpreter.new(&:runtime) }.not_to raise_error
18
20
  end
19
21
 
20
22
  it 'should have a parser' do
@@ -65,6 +67,7 @@ module Skeem
65
67
  end
66
68
  end
67
69
 
70
+ # rubocop: disable Style/ExponentialNotation
68
71
  it 'should evaluate isolated real numbers' do
69
72
  samples = [
70
73
  ['0.0', 0.0],
@@ -78,6 +81,7 @@ module Skeem
78
81
  expect(result).to eq(predicted)
79
82
  end
80
83
  end
84
+ # rubocop: enable Style/ExponentialNotation
81
85
 
82
86
  it 'should evaluate isolated strings' do
83
87
  samples = [
@@ -90,6 +94,7 @@ module Skeem
90
94
  end
91
95
 
92
96
  it 'should evaluate vector of constants' do
97
+ require 'benchmark'
93
98
  source = '#(2018 10 20 "Sat")'
94
99
  result = subject.run(source)
95
100
  expect(result).to be_kind_of(SkmVector)
@@ -108,7 +113,7 @@ module Skeem
108
113
 
109
114
  context 'Built-in primitives' do
110
115
  it 'should implement variable definition' do
111
- result = subject.run('(define x 28)')
116
+ subject.run('(define x 28)')
112
117
  expect(subject.fetch('x')).to eq(28)
113
118
  end
114
119
 
@@ -125,7 +130,7 @@ SKEEM
125
130
  end
126
131
 
127
132
  it 'should implement the simple conditional form' do
128
- checks = [
133
+ checks = [
129
134
  ['(if (> 3 2) "yes")', 'yes'],
130
135
  ['(if (> 2 3) "yes")', SkmUndefined.instance]
131
136
  ]
@@ -157,7 +162,7 @@ SKEEM
157
162
  ((< x 0) -1)
158
163
  )))
159
164
  SKEEM
160
- result = subject.run(source)
165
+ subject.run(source)
161
166
  checks = [
162
167
  ['(signum 3)', 1],
163
168
  ['(signum 0)', 0],
@@ -175,7 +180,7 @@ SKEEM
175
180
  ((< x 0) => -1)
176
181
  )))
177
182
  SKEEM
178
- result = subject.run(source)
183
+ subject.run(source)
179
184
  checks = [
180
185
  ['(signum 3)', 1],
181
186
  ['(signum 0)', 0],
@@ -193,7 +198,7 @@ SKEEM
193
198
  (else -1)
194
199
  )))
195
200
  SKEEM
196
- result = subject.run(source)
201
+ subject.run(source)
197
202
  checks = [
198
203
  ['(signum 3)', 1],
199
204
  ['(signum 0)', 0],
@@ -211,7 +216,7 @@ SKEEM
211
216
  end
212
217
 
213
218
  it 'should implement the quotation of constant literals' do
214
- checks = [
219
+ checks = [
215
220
  ['(quote a)', 'a'],
216
221
  ['(quote 145932)', 145932],
217
222
  ['(quote "abc")', 'abc'],
@@ -367,8 +372,6 @@ SKEEM
367
372
  result = subject.run(source)
368
373
  expect(result.last.value).to eq(20)
369
374
  end
370
-
371
-
372
375
  end # context
373
376
 
374
377
  context 'Binding constructs:' do
@@ -406,7 +409,7 @@ SKEEM
406
409
  end
407
410
 
408
411
  it 'should support the nesting of a lambda in a let expression' do
409
- source =<<-SKEEM
412
+ source = <<-SKEEM
410
413
  (define make-counter
411
414
  (lambda ()
412
415
  (let ((count 0))
@@ -477,7 +480,7 @@ SKEEM
477
480
 
478
481
  context 'Quasiquotation:' do
479
482
  it 'should implement the quasiquotation of constant literals' do
480
- checks = [
483
+ checks = [
481
484
  ['(quasiquote a)', 'a'],
482
485
  ['(quasiquote 145932)', 145932],
483
486
  ['(quasiquote "abc")', 'abc'],
@@ -518,7 +521,7 @@ SKEEM
518
521
  expect(result.members[index]).to eq(value)
519
522
  end
520
523
 
521
- source = "`#()"
524
+ source = '`#()'
522
525
  result = subject.run(source)
523
526
  expect(result).to be_kind_of(SkmVector)
524
527
  expect(result).to be_empty
@@ -555,7 +558,7 @@ SKEEM
555
558
  result = result.cdr
556
559
  end
557
560
 
558
- source = "`()"
561
+ source = '`()'
559
562
  result = subject.run(source)
560
563
  expect(result).to be_kind_of(SkmEmptyList)
561
564
  expect(result).to be_null
@@ -576,7 +579,7 @@ SKEEM
576
579
  result = result.cdr
577
580
  end
578
581
 
579
- source = "`()"
582
+ source = '`()'
580
583
  result = subject.run(source)
581
584
  expect(result).to be_kind_of(SkmEmptyList)
582
585
  expect(result).to be_null
@@ -801,42 +804,62 @@ SKEEM
801
804
  compare_to_predicted(checks)
802
805
  end
803
806
 
804
- it 'should implement the truncate-quotient procedure' do
805
- checks = [
806
- ['(truncate-quotient 5 2)', 2],
807
- ['(truncate-quotient -5 2)', -2],
808
- ['(truncate-quotient 5 -2)', -2],
809
- ['(truncate-quotient -5 -2)', 2],
810
- ['(quotient 5 2)', 2],
811
- ['(quotient -5 2)', -2],
812
- ['(quotient 5 -2)', -2],
813
- ['(quotient -5 -2)', 2]
814
- ]
815
- compare_to_predicted(checks)
816
- end
807
+ it 'should implement the truncate-quotient procedure' do
808
+ checks = [
809
+ ['(truncate-quotient 5 2)', 2],
810
+ ['(truncate-quotient -5 2)', -2],
811
+ ['(truncate-quotient 5 -2)', -2],
812
+ ['(truncate-quotient -5 -2)', 2],
813
+ ['(quotient 5 2)', 2],
814
+ ['(quotient -5 2)', -2],
815
+ ['(quotient 5 -2)', -2],
816
+ ['(quotient -5 -2)', 2]
817
+ ]
818
+ compare_to_predicted(checks)
819
+ end
817
820
 
818
- it 'should implement the truncate-remainder procedure' do
819
- checks = [
820
- ['(truncate-remainder 5 2)', 1],
821
- ['(truncate-remainder -5 2)', -1],
822
- ['(truncate-remainder 5 -2)', 1],
823
- ['(truncate-remainder -5 -2)', -1],
824
- ['(remainder 5 2)', 1],
825
- ['(remainder -5 2)', -1],
826
- ['(remainder 5 -2)', 1],
827
- ['(remainder -5 -2)', -1]
828
- ]
829
- compare_to_predicted(checks)
830
- end
821
+ it 'should implement the truncate-remainder procedure' do
822
+ checks = [
823
+ ['(truncate-remainder 5 2)', 1],
824
+ ['(truncate-remainder -5 2)', -1],
825
+ ['(truncate-remainder 5 -2)', 1],
826
+ ['(truncate-remainder -5 -2)', -1],
827
+ ['(remainder 5 2)', 1],
828
+ ['(remainder -5 2)', -1],
829
+ ['(remainder 5 -2)', 1],
830
+ ['(remainder -5 -2)', -1]
831
+ ]
832
+ compare_to_predicted(checks)
833
+ end
831
834
 
832
835
  it 'should implement the test-equal procedure' do
833
836
  checks = [
834
- ["(test-equal (cons 1 2) (cons 1 2))", true]
837
+ ['(test-equal (cons 1 2) (cons 1 2))', true]
835
838
  ]
836
839
  compare_to_predicted(checks)
837
840
  end
838
841
  end # context
839
842
 
843
+ context 'Input/output:' do
844
+ it 'should implement the include expression' do
845
+ initial_dir = Dir.pwd
846
+ filedir = File.dirname(__FILE__)
847
+ Dir.chdir(filedir)
848
+ source = '(include "add4.skm")' # Path is assumed to be relative to pwd
849
+ result = subject.run(source)
850
+ expect(result.last).to eq(10)
851
+ Dir.chdir(initial_dir)
852
+ end
853
+
854
+ it 'should implement the newline procedure' do
855
+ default_stdout = $stdout
856
+ $stdout = StringIO.new
857
+ subject.run('(newline) (newline) (newline)')
858
+ expect($stdout.string).to match(/\n\n\n$/)
859
+ $stdout = default_stdout
860
+ end
861
+ end # context
862
+
840
863
  context 'Second-order functions' do
841
864
  it 'should implement lambda that calls second-order function' do
842
865
  source = <<-SKEEM
@@ -885,7 +908,7 @@ SKEEM
885
908
  SKEEM
886
909
  result = subject.run(source)
887
910
  expect(result).to eq([0, 1, 2, 3, 4])
888
-
911
+
889
912
  source = <<-SKEEM
890
913
  (let ((x '(1 3 5 7 9)))
891
914
  (do (
@@ -894,8 +917,25 @@ SKEEM
894
917
  ((null? x) sum))) ; => 25
895
918
  SKEEM
896
919
  result = subject.run(source)
897
- expect(result).to eq(25)
920
+ expect(result).to eq(25)
898
921
  end
899
922
  end # context
923
+
924
+ context 'Macro processing:' do
925
+ # it 'should parse macro expressions' do
926
+ # source = <<-SKEEM
927
+ # (define-syntax while
928
+ # (syntax-rules ()
929
+ # ((while condition body ...)
930
+ # (let loop ()
931
+ # (if condition
932
+ # (begin
933
+ # body ...
934
+ # (loop))
935
+ # #f)))))
936
+ # SKEEM
937
+ # ptree = subject.parse(source)
938
+ # end
939
+ end
900
940
  end # describe
901
- end # module
941
+ end # module
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../spec_helper' # Use the RSpec framework
2
4
 
3
5
  # Load the class under test
@@ -29,7 +31,7 @@ SKEEM
29
31
 
30
32
  context 'Defining compound procedures:' do
31
33
  it 'should accept the definition of simple procedure with arity 1' do
32
- source = definition_set + "\n" + 'square'
34
+ source = "#{definition_set}\nsquare"
33
35
  result = subject.run(source)
34
36
 
35
37
  square = result.last
@@ -39,7 +41,7 @@ SKEEM
39
41
  end
40
42
 
41
43
  it 'should accept the definition of simple procedure with arity 2' do
42
- source = definition_set + "\n" + 'sum-of-squares'
44
+ source = "#{definition_set}\nsum-of-squares"
43
45
  result = subject.run(source)
44
46
 
45
47
  square = result.last
@@ -63,25 +65,25 @@ SKEEM
63
65
  end
64
66
 
65
67
  it 'should support the call to a simple procedure with arity 2' do
66
- source = definition_set + "\n" + '(sum-of-squares 3 4)'
68
+ source = "#{definition_set}\n(sum-of-squares 3 4)"
67
69
  result = subject.run(source)
68
70
 
69
71
  expect(result.last).to eq(25)
70
72
  end
71
73
 
72
74
  it 'should support the call to a nested lambda procedure' do
73
- source = definition_set + "\n" + '(f 5)'
75
+ source = "#{definition_set}\n(f 5)"
74
76
  result = subject.run(source)
75
77
 
76
78
  expect(result.last).to eq(136)
77
79
  end
78
-
80
+
79
81
  it 'should accept calls to anonymous procedures' do
80
82
  source = '((lambda (x) (+ x x)) 4)'
81
83
  result = subject.run(source)
82
84
  expect(result).to eq(8)
83
85
  end
84
-
86
+
85
87
  it 'should accept unary second-order lambdas' do
86
88
  source = <<-SKEEM
87
89
  (define add-with
@@ -94,10 +96,10 @@ SKEEM
94
96
  expect(result).to eq(7)
95
97
  end
96
98
  end # context
97
-
99
+
98
100
  context 'More advanced features:' do
99
101
  subject { Interpreter.new }
100
-
102
+
101
103
  it 'should implement binary second-order functions' do
102
104
  source = <<-SKEEM
103
105
  (define compose
@@ -105,10 +107,10 @@ SKEEM
105
107
  (lambda (x)
106
108
  (f (g x)))))
107
109
  SKEEM
108
- result = subject.run(source)
110
+ subject.run(source)
109
111
  result = subject.run('((compose list square) 5)')
110
112
  expect(result.last).to eq(25)
111
- end
113
+ end
112
114
  end # context
113
115
  end # describe
114
- end # module
116
+ end # module
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../spec_helper' # Use the RSpec framework
2
4
  require_relative '../../lib/skeem/tokenizer' # Load the class under test
3
5
 
@@ -5,7 +7,7 @@ module Skeem
5
7
  describe Parser do
6
8
  context 'Initialization:' do
7
9
  it 'should be initialized without argument' do
8
- expect { Parser.new() }.not_to raise_error
10
+ expect { Parser.new }.not_to raise_error
9
11
  end
10
12
 
11
13
  it 'should have its parse engine initialized' do
@@ -16,11 +18,11 @@ module Skeem
16
18
  context 'Parsing literals:' do
17
19
  it 'should parse isolated booleans' do
18
20
  samples = [
19
- ['#f', false],
21
+ ['#f', false]
20
22
  # ['#false', false],
21
23
  # ['#t', true],
22
24
  # ['#true', true]
23
- ]
25
+ ]
24
26
  samples.each do |source, predicted|
25
27
  ptree = subject.parse(source)
26
28
  expect(ptree.root).to be_kind_of(SkmBoolean)
@@ -30,12 +32,12 @@ module Skeem
30
32
 
31
33
  it 'should parse isolated integers' do
32
34
  samples = [
33
- ['0', 0],
34
- ['3', 3],
35
- ['-3', -3],
36
- ['+12345', 12345],
37
- ['-12345', -12345]
38
- ]
35
+ ['0', 0],
36
+ ['3', 3],
37
+ ['-3', -3],
38
+ ['+12345', 12345],
39
+ ['-12345', -12345]
40
+ ]
39
41
  samples.each do |source, predicted|
40
42
  ptree = subject.parse(source)
41
43
  expect(ptree.root).to be_kind_of(SkmInteger)
@@ -43,25 +45,27 @@ module Skeem
43
45
  end
44
46
  end
45
47
 
48
+ # rubocop: disable Style/ExponentialNotation
46
49
  it 'should parse isolated real numbers' do
47
50
  samples = [
48
- ['0.0', 0.0],
49
- ['3.14', 3.14],
50
- ['-3.14', -3.14],
51
- ['+123e+45', 123e+45],
52
- ['-123e-45', -123e-45]
53
- ]
51
+ ['0.0', 0.0],
52
+ ['3.14', 3.14],
53
+ ['-3.14', -3.14],
54
+ ['+123e+45', 123e+45],
55
+ ['-123e-45', -123e-45]
56
+ ]
54
57
  samples.each do |source, predicted|
55
58
  ptree = subject.parse(source)
56
59
  expect(ptree.root).to be_kind_of(SkmReal)
57
60
  expect(ptree.root.value).to eq(predicted)
58
61
  end
59
62
  end
63
+ # rubocop: enable Style/ExponentialNotation
60
64
 
61
65
  it 'should parse isolated strings' do
62
66
  samples = [
63
- ['"Hello world!"', 'Hello world!']
64
- ]
67
+ ['"Hello world!"', 'Hello world!']
68
+ ]
65
69
  samples.each do |source, predicted|
66
70
  ptree = subject.parse(source)
67
71
  expect(ptree.root).to be_kind_of(SkmString)
@@ -71,8 +75,8 @@ module Skeem
71
75
 
72
76
  it 'should parse isolated identifiers' do
73
77
  samples = [
74
- ['the-word-recursion-has-many-meanings', 'the-word-recursion-has-many-meanings']
75
- ]
78
+ %w[the-word-recursion-has-many-meanings the-word-recursion-has-many-meanings]
79
+ ]
76
80
  samples.each do |source, predicted|
77
81
  ptree = subject.parse(source)
78
82
  expect(ptree.root).to be_kind_of(SkmVariableReference)