campa 0.1.0

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 +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +37 -0
  5. data/.travis.yml +8 -0
  6. data/CHANGELOG.md +5 -0
  7. data/Gemfile +15 -0
  8. data/Gemfile.lock +82 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +39 -0
  11. data/Rakefile +23 -0
  12. data/bin/console +15 -0
  13. data/bin/setup +8 -0
  14. data/campa.gemspec +34 -0
  15. data/campa/core.cmp +59 -0
  16. data/campa/example.cmp +2 -0
  17. data/campa/test.cmp +2 -0
  18. data/exe/campa +7 -0
  19. data/lib/campa.rb +18 -0
  20. data/lib/campa/cli.rb +66 -0
  21. data/lib/campa/context.rb +42 -0
  22. data/lib/campa/core/load.rb +25 -0
  23. data/lib/campa/core/print.rb +20 -0
  24. data/lib/campa/core/print_ln.rb +17 -0
  25. data/lib/campa/core/test.rb +52 -0
  26. data/lib/campa/core/test_report.rb +59 -0
  27. data/lib/campa/error/arity.rb +11 -0
  28. data/lib/campa/error/illegal_argument.rb +9 -0
  29. data/lib/campa/error/invalid_number.rb +9 -0
  30. data/lib/campa/error/missing_delimiter.rb +9 -0
  31. data/lib/campa/error/not_a_function.rb +9 -0
  32. data/lib/campa/error/not_found.rb +9 -0
  33. data/lib/campa/error/parameters.rb +11 -0
  34. data/lib/campa/error/reserved.rb +11 -0
  35. data/lib/campa/error/resolution.rb +9 -0
  36. data/lib/campa/evaler.rb +106 -0
  37. data/lib/campa/execution_error.rb +3 -0
  38. data/lib/campa/lambda.rb +45 -0
  39. data/lib/campa/language.rb +33 -0
  40. data/lib/campa/lisp/atom.rb +14 -0
  41. data/lib/campa/lisp/cadr.rb +41 -0
  42. data/lib/campa/lisp/car.rb +22 -0
  43. data/lib/campa/lisp/cdr.rb +22 -0
  44. data/lib/campa/lisp/cond.rb +50 -0
  45. data/lib/campa/lisp/cons.rb +23 -0
  46. data/lib/campa/lisp/core.rb +35 -0
  47. data/lib/campa/lisp/defun.rb +36 -0
  48. data/lib/campa/lisp/eq.rb +9 -0
  49. data/lib/campa/lisp/label.rb +29 -0
  50. data/lib/campa/lisp/lambda_fn.rb +33 -0
  51. data/lib/campa/lisp/list_fn.rb +9 -0
  52. data/lib/campa/lisp/quote.rb +13 -0
  53. data/lib/campa/list.rb +83 -0
  54. data/lib/campa/node.rb +17 -0
  55. data/lib/campa/printer.rb +70 -0
  56. data/lib/campa/reader.rb +198 -0
  57. data/lib/campa/repl.rb +75 -0
  58. data/lib/campa/symbol.rb +23 -0
  59. data/lib/campa/version.rb +5 -0
  60. metadata +119 -0
@@ -0,0 +1,33 @@
1
+ module Campa
2
+ class Language < Lisp::Core
3
+ def initialize
4
+ super
5
+ load_core_funs
6
+ load_core_files
7
+ end
8
+
9
+ private
10
+
11
+ FUNS = {
12
+ "tests-run": Core::Test.new,
13
+ "tests-report": Core::TestReport.new,
14
+
15
+ "print": Core::Print.new,
16
+ "println": Core::PrintLn.new,
17
+ }.freeze
18
+
19
+ FILES = [
20
+ "../campa/core.cmp",
21
+ "../campa/test.cmp",
22
+ ].freeze
23
+
24
+ def load_core_funs
25
+ FUNS.each { |label, fun| self[Symbol.new(label.to_s)] = fun }
26
+ end
27
+
28
+ def load_core_files
29
+ loader = Campa::Core::Load.new
30
+ FILES.each { |f| loader.call(Campa.root.join(f), env: self) }
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,14 @@
1
+ module Campa
2
+ module Lisp
3
+ class Atom
4
+ def call(expression)
5
+ case expression
6
+ when Symbol, Numeric, TrueClass, FalseClass, String
7
+ true
8
+ when List
9
+ expression == List::EMPTY
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,41 @@
1
+ module Campa
2
+ module Lisp
3
+ class Cadr
4
+ def initialize
5
+ @printer = Printer.new
6
+ @car = Car.new
7
+ @cdr = Cdr.new
8
+ end
9
+
10
+ def macro?
11
+ true
12
+ end
13
+
14
+ def call(operation, list)
15
+ return nil if list.nil? || list == List::EMPTY
16
+ raise illegal_argument(list) if !list.is_a?(List)
17
+
18
+ cut_list(
19
+ list,
20
+ operation
21
+ .label[1..-2]
22
+ .reverse
23
+ .split("")
24
+ )
25
+ end
26
+
27
+ private
28
+
29
+ def illegal_argument(list)
30
+ Error::IllegalArgument.new(@printer.call(list), "list")
31
+ end
32
+
33
+ def cut_list(list, invocation_sequence)
34
+ invocation_sequence.reduce(list) do |l, oper|
35
+ to_call = oper == "a" ? @car : @cdr
36
+ to_call.call(l)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,22 @@
1
+ module Campa
2
+ module Lisp
3
+ class Car
4
+ def initialize
5
+ @printer = Printer.new
6
+ end
7
+
8
+ def call(list)
9
+ return nil if list.nil? || list == List::EMPTY
10
+ raise illegal_argument(list) if !list.is_a?(List)
11
+
12
+ list.head
13
+ end
14
+
15
+ private
16
+
17
+ def illegal_argument(list)
18
+ Error::IllegalArgument.new(@printer.call(list), "list")
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ module Campa
2
+ module Lisp
3
+ class Cdr
4
+ def initialize
5
+ @printer = Printer.new
6
+ end
7
+
8
+ def call(list)
9
+ return nil if list.nil? || list == List::EMPTY
10
+ raise illegal_argument(list) if !list.is_a?(List)
11
+
12
+ list.tail
13
+ end
14
+
15
+ private
16
+
17
+ def illegal_argument(list)
18
+ Error::IllegalArgument.new(@printer.call(list), "list")
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,50 @@
1
+ module Campa
2
+ module Lisp
3
+ class Cond
4
+ FALSEY = [false, nil].freeze
5
+
6
+ def initialize
7
+ @printer = Printer.new
8
+ @evaler = Evaler.new
9
+ end
10
+
11
+ def macro?
12
+ true
13
+ end
14
+
15
+ def call(*conditions, env:)
16
+ found = conditions.find do |cond|
17
+ raise illegal_argument(cond) if !cond.is_a?(List)
18
+
19
+ !FALSEY.include? evaler.call(cond.head, env)
20
+ end
21
+
22
+ eval_result(found, env)
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :evaler, :printer
28
+
29
+ def illegal_argument(thing)
30
+ Error::IllegalArgument.new(printer.call(thing), "list")
31
+ end
32
+
33
+ def eval_result(found, env)
34
+ return if found.nil?
35
+
36
+ # For when condition is a "truethy" value
37
+ #
38
+ # (cond
39
+ # ((eq 1 2) 'no)
40
+ # (true 'yes)
41
+ # )
42
+ # => 'yes
43
+ to_eval = found.tail == List::EMPTY ? [found.head] : found.tail.to_a
44
+ to_eval.reduce(nil) do |_, expr|
45
+ evaler.call(expr, env)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,23 @@
1
+ module Campa
2
+ module Lisp
3
+ class Cons
4
+ def initialize
5
+ @printer = Printer.new
6
+ end
7
+
8
+ def call(new_head, list)
9
+ raise illegal_argument(list) if !list.is_a?(List)
10
+
11
+ list.push(new_head)
12
+ end
13
+
14
+ private
15
+
16
+ attr_reader :printer
17
+
18
+ def illegal_argument(list)
19
+ Error::IllegalArgument.new(printer.call(list), "list")
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,35 @@
1
+ module Campa
2
+ module Lisp
3
+ class Core < Context
4
+ CORE_FUNCS_MAP = {
5
+ "quote" => Quote,
6
+ "atom" => Atom,
7
+ "eq" => Eq,
8
+ "car" => Car,
9
+ "cdr" => Cdr,
10
+ "cons" => Cons,
11
+ "cond" => Cond,
12
+ "lambda" => LambdaFn,
13
+ "label" => Label,
14
+ "defun" => Defun,
15
+
16
+ "_cadr" => Cadr,
17
+ "list" => ListFn,
18
+
19
+ "load" => Campa::Core::Load,
20
+ }.freeze
21
+
22
+ def initialize
23
+ super Hash[
24
+ CORE_FUNCS_MAP.map { |label, handler| [sym(label), handler.new] }
25
+ ]
26
+ end
27
+
28
+ private
29
+
30
+ def sym(label)
31
+ Campa::Symbol.new(label)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,36 @@
1
+ module Campa
2
+ module Lisp
3
+ class Defun
4
+ def initialize
5
+ @printer = Printer.new
6
+ @label_fn = Label.new
7
+ end
8
+
9
+ def macro?
10
+ true
11
+ end
12
+
13
+ def call(label, params, *body, env:)
14
+ raise label_error(label) if !label.is_a?(Symbol)
15
+
16
+ label_fn.call(
17
+ label,
18
+ invoke_lambda(params, body),
19
+ env: env
20
+ )
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :printer, :label_fn
26
+
27
+ def label_error(given)
28
+ Error::IllegalArgument.new(printer.call(given), "symbol")
29
+ end
30
+
31
+ def invoke_lambda(params, body)
32
+ List.new(SYMBOL_LAMBDA, params, *body)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,9 @@
1
+ module Campa
2
+ module Lisp
3
+ class Eq
4
+ def call(expr_a, expr_b)
5
+ expr_a == expr_b
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,29 @@
1
+ module Campa
2
+ module Lisp
3
+ class Label
4
+ def initialize
5
+ @evaler = Evaler.new
6
+ @printer = Printer.new
7
+ end
8
+
9
+ def macro?
10
+ true
11
+ end
12
+
13
+ def call(label, expression, env:)
14
+ result = evaler.call(expression, env)
15
+ raise Error::Reserved, printer.call(label) if reserved?(label, result)
16
+
17
+ env[label] = result
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :evaler, :printer
23
+
24
+ def reserved?(symbol, result)
25
+ symbol.label.match?(CR_REGEX) && result.is_a?(Lambda)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,33 @@
1
+ module Campa
2
+ module Lisp
3
+ class LambdaFn
4
+ def initialize
5
+ @printer = Printer.new
6
+ end
7
+
8
+ def macro?
9
+ true
10
+ end
11
+
12
+ def call(params, *body, env:)
13
+ raise parameters_error(printer.call(params)) if !params.respond_to?(:find)
14
+
15
+ validate_params(params)
16
+ Lambda.new(params, body, env)
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :printer
22
+
23
+ def parameters_error(param, expected = "list of symbols")
24
+ Error::Parameters.new(param, expected)
25
+ end
26
+
27
+ def validate_params(params)
28
+ invalid_param = params.find { |el| !el.is_a?(Symbol) }
29
+ raise parameters_error(invalid_param, "symbol") if !invalid_param.nil?
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,9 @@
1
+ module Campa
2
+ module Lisp
3
+ class ListFn
4
+ def call(*items)
5
+ List.new(*items)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ module Campa
2
+ module Lisp
3
+ class Quote
4
+ def macro?
5
+ true
6
+ end
7
+
8
+ def call(expression)
9
+ expression
10
+ end
11
+ end
12
+ end
13
+ end
data/lib/campa/list.rb ADDED
@@ -0,0 +1,83 @@
1
+ module Campa
2
+ class List
3
+ include Enumerable
4
+
5
+ EMPTY = new
6
+
7
+ def initialize(*elements)
8
+ @first = nil
9
+ @head = nil
10
+ @tail = EMPTY
11
+
12
+ with elements
13
+ end
14
+
15
+ def push(element)
16
+ self.class.new.tap do |l|
17
+ l.first = Node.new(value: element, next_node: first)
18
+ end
19
+ end
20
+
21
+ def head
22
+ return nil if self == EMPTY
23
+
24
+ first.value
25
+ end
26
+
27
+ def tail
28
+ return EMPTY if first.nil? || first.next_node.nil?
29
+
30
+ self.class.new.tap { |l| l.first = @first.next_node }
31
+ end
32
+
33
+ def each(&block)
34
+ return if self == EMPTY
35
+
36
+ block.call(head)
37
+ tail.each(&block) if tail != EMPTY
38
+ end
39
+
40
+ def ==(other)
41
+ return false if !other.is_a?(List)
42
+
43
+ node = first
44
+ other_node = other.first
45
+
46
+ loop do
47
+ # If both node and other_node are nil
48
+ # we managed to walk the whole list.
49
+ # Since there was no early return (with false)
50
+ # this means all nodes are equal.
51
+ return node.nil? if other_node.nil?
52
+ return false if node != other_node
53
+
54
+ node = node.next_node
55
+ other_node = other_node.next_node
56
+ end
57
+ end
58
+
59
+ def inspect
60
+ @printer ||= Printer.new
61
+ @printer.call(self)
62
+ end
63
+
64
+ protected
65
+
66
+ attr_accessor :first
67
+
68
+ private
69
+
70
+ def with(elements)
71
+ return if elements.empty?
72
+
73
+ elements.reduce(nil) do |previous, current|
74
+ new_node = Node.new(value: current)
75
+ if previous.nil?
76
+ self.first = new_node
77
+ else
78
+ previous.next_node = new_node
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end