campa 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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