campa 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6410a4621dc09f84072f8033d4497bbe49d13df0a03e3a5f6107ac27cdaffdc8
4
- data.tar.gz: '0923072a54a030a5747f254a3903efa0b22909b8440b41b7086a70eaf3b40d31'
3
+ metadata.gz: db71a3db30c9fe5be51a2033b53adba713c4a0dd5f7fdcc0b2ae64b471792df5
4
+ data.tar.gz: fe74a65e008ef2575d2daf4f741563305a98b0ae28ac2830883ec0de8feb4d48
5
5
  SHA512:
6
- metadata.gz: 7c45daf7fbaf5a5a6035db61a25f3607281e64ef4ffbce48b3fcf596816a6f08aad6e0bf0f902768be15a4f9e587616ce1fa6f56f5110a760ed621456f021a9d
7
- data.tar.gz: a68e9021520f4efcfb4494f128a4cb67a9d0fa5f446403d26458fccc80971e704383c819b7c56786ea525ab6d51a79b4c9bfabe98c9160e5eb394fbf08ef2eee
6
+ metadata.gz: ef6569c0fa26a2dc886903c4689cd2ae667e0ca6a3110289c9b280a4b88602e12be2d605d88dfd09b46e046f39dc7124d3c6dcb91456eca75992455f72313674
7
+ data.tar.gz: 3974b56df43c387b40a7211d0c65b7ab554d66e5546a0a3e851fbbd6ad19e42d4201ee9f0735928b4b6fe044fd23c1e3b4fdaa75ec24e2ec23f86969cff33487
data/.gitattributes ADDED
@@ -0,0 +1 @@
1
+ *.cmp linguist-language=lisp
data/Gemfile CHANGED
@@ -13,3 +13,5 @@ gem "rubocop-rspec", require: false
13
13
 
14
14
  gem "pry-byebug"
15
15
  gem "simplecov", require: false
16
+
17
+ gem "yard"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- campa (0.1.2)
4
+ campa (0.1.3)
5
5
  zeitwerk (~> 2.4)
6
6
 
7
7
  GEM
@@ -63,6 +63,7 @@ GEM
63
63
  simplecov-html (0.12.3)
64
64
  simplecov_json_formatter (0.1.3)
65
65
  unicode-display_width (2.0.0)
66
+ yard (0.9.26)
66
67
  zeitwerk (2.4.2)
67
68
 
68
69
  PLATFORMS
@@ -77,6 +78,7 @@ DEPENDENCIES
77
78
  rubocop-rake
78
79
  rubocop-rspec
79
80
  simplecov
81
+ yard
80
82
 
81
83
  BUNDLED WITH
82
84
  2.2.20
data/README.md CHANGED
@@ -56,7 +56,7 @@ more accessible.
56
56
  This project implements **LISP** to the point
57
57
  where all the functions exemplified by Graham's article
58
58
  run successfully. Which means one could implement *Campa* on itself
59
- (or they could just [go here](../blob/main/campa/core.cmp)
59
+ (or they could just [go here](../main/campa/core.cmp)
60
60
  and see how it is done already).
61
61
 
62
62
  ## Usage
@@ -144,7 +144,7 @@ to quote an object is also implemented in the runtime.
144
144
  ### Implementation details
145
145
 
146
146
  All those functions are implemented in Ruby
147
- [and they live right here](../blob/4fc244d41dd6d9/lib/campa/lisp).
147
+ [and they live right here](../3b43a21/lib/campa/lisp).
148
148
 
149
149
  ## Beyond the Roots
150
150
 
@@ -158,16 +158,16 @@ and the ones implemented on the runtime (in Ruby).
158
158
 
159
159
  ### Extras in Campa
160
160
 
161
- - [(assert x y)](../blob/4fc244d41dd6d9/campa/test.cmp#L1)
161
+ - [(assert x y)](../3b43a21/campa/test.cmp#L1)
162
162
  Returns true if x and y are *eq*.
163
163
 
164
164
  ### Extras in Ruby (runtime)
165
165
 
166
- - [(load a-file another-file)](../blob/4fc244d41dd6d9/lib/campa/core/load.rb)
166
+ - [(load a-file another-file)](../3b43a21/lib/campa/core/load.rb)
167
167
  Read and evaluate the files given as arguments
168
- - [(print "some" "stuff" 4 '("even" "lists"))](../blob/4fc244d41dd6d9/lib/campa/core/print.rb)
168
+ - [(print "some" "stuff" 4 '("even" "lists"))](../3b43a21/lib/campa/core/print.rb)
169
169
  Print out a reader friendly representation of the given parameter(s).
170
- - [(println stuffz here)](../blob/4fc244d41dd6d9/lib/campa/core/println.rb)
170
+ - [(println stuffz here)](../3b43a21/lib/campa/core/println.rb)
171
171
  Same as print but add a line break (`\n`) after each parameter.
172
172
 
173
173
  #### Tests
@@ -184,7 +184,7 @@ that starts with *test-* or *test_* (case insentive)
184
184
  and return *true* for success
185
185
  or *false* for failure.
186
186
  The core implementation for *Campa* rely on this tool
187
- and you can check out [some test examples here](../blob/4fc244d41dd6d9/test/core_test.cmp).
187
+ and you can check out [some test examples here](../3b43a21/test/core_test.cmp).
188
188
 
189
189
  $ campa test test/core_test.cmp
190
190
 
@@ -194,8 +194,8 @@ and you can check out [some test examples here](../blob/4fc244d41dd6d9/test/core
194
194
  Internally this "framework" is comprised
195
195
  of the two following functions:
196
196
 
197
- - [(tests-run optional-name other-optional-name)](../blob/4fc244d41dd6d9/lib/campa/core/test.rb)
198
- - [(tests-report (tests-run))](../blob/4fc244d41dd6d9/lib/campa/core/test_report.rb)
197
+ - [(tests-run optional-name other-optional-name)](../3b43a21/lib/campa/core/test.rb)
198
+ - [(tests-report (tests-run))](../3b43a21/lib/campa/core/test_report.rb)
199
199
 
200
200
  ##### (tests-run)
201
201
 
@@ -237,7 +237,7 @@ This is an easy way to integrate this tool
237
237
  with CI environments or any type of "progressive" build.
238
238
 
239
239
  An example of how this is used by
240
- *Campa* implementation [can be found here](../blob/4fc244d41dd6d9/Rakefile#L12).
240
+ *Campa* implementation [can be found here](../3b43a21/Rakefile#L12).
241
241
 
242
242
 
243
243
  ## Development
data/Rakefile CHANGED
@@ -20,4 +20,11 @@ rescue SystemExit => e
20
20
  exit(1) if e.status != 0
21
21
  end
22
22
 
23
- task default: %i[spec rubocop campatest]
23
+ require "yard"
24
+ YARD::Rake::YardocTask.new do |t|
25
+ # t.files = ['lib/**/*.rb', OTHER_PATHS] # optional
26
+ # t.options = ['--any', '--extra', '--opts'] # optional
27
+ # t.stats_options = ['--list-undoc'] # optional
28
+ end
29
+
30
+ task default: %i[spec rubocop campatest yard]
data/bin/console CHANGED
@@ -3,13 +3,6 @@
3
3
 
4
4
  require "bundler/setup"
5
5
  require "campa"
6
-
7
- # You can add fixtures and/or initialization code here to make experimenting
8
- # with your gem easier. You can also use a different console, if you like.
9
-
10
- # (If you use this, don't forget to add pry to your Gemfile!)
11
- # require "pry"
12
- # Pry.start
13
-
14
6
  require "irb"
7
+
15
8
  IRB.start(__FILE__)
data/bin/setup CHANGED
@@ -4,5 +4,3 @@ IFS=$'\n\t'
4
4
  set -vx
5
5
 
6
6
  bundle install
7
-
8
- # Do any other automated setup that you need to do here
data/lib/campa.rb CHANGED
@@ -7,12 +7,57 @@ require "zeitwerk"
7
7
  loader = Zeitwerk::Loader.for_gem
8
8
  loader.setup
9
9
 
10
+ # Campa is a tiny LISP implementation.
11
+ #
12
+ # The "benchmark" for this is to cover the specification
13
+ # established by the Paul Graham's article
14
+ # {http://paulgraham.com/rootsoflisp.html The Roots of Lisp}.
15
+ #
16
+ # So the following functions are implemented
17
+ # in the runtime:
18
+ #
19
+ # - (atom something)
20
+ # - (car list)
21
+ # - (cdr list)
22
+ # - (cond (some-condition value) (another-condition another-value))
23
+ # - (cons 'first '(second third))
24
+ # - (defun fun-name (parameters list) 'body)
25
+ # - (eq a-thing another-thing)
26
+ # - (label meaning-of-life 42)
27
+ # - (quote (some stuff))
28
+ #
29
+ # Besides these core functions
30
+ # other two ones,
31
+ # also specified on The Roots of Lisp,
32
+ # are also implemented on tnis LISP:
33
+ #
34
+ # - (cadr list) - and any variation possible (caaar, cadadar...)
35
+ # - (list 'this 'creates 'a 'new 'list)
36
+ #
37
+ # Those are all the functions necessary
38
+ # to implement an eval function
39
+ # able to interprete LISP by itself.
40
+ #
41
+ # And to be sure that this is the case
42
+ # we have this implementation on {file:campa/core.cmp campa/core.cmp}.
10
43
  module Campa
11
- CR_REGEX = /\Ac((ad)|(a|d){2,})r$$/ # caar, cddr, cadr, but not car or cdr
44
+ # caar, cddr, cadr, but not car or cdr
45
+ CR_REGEX = /\Ac((ad)|(a|d){2,})r$$/
46
+
47
+ # symbol to reference the "stdout" in a Campa execution context
12
48
  SYMBOL_OUT = Symbol.new("__out__")
49
+
50
+ # symbol for the lambda function
13
51
  SYMBOL_LAMBDA = Symbol.new("lambda")
52
+
53
+ # symbol for the quote function
14
54
  SYMBOL_QUOTE = Symbol.new("quote")
15
55
 
56
+ # Returns a Pathname pointint to
57
+ # the root of the "gem".
58
+ #
59
+ # Useful for requiring and/or finding files
60
+ # that need to be read by the runtime.
16
61
  def self.root
17
62
  @root ||= Pathname.new File.expand_path(__dir__)
18
63
  end
data/lib/campa/cli.rb CHANGED
@@ -1,13 +1,40 @@
1
1
  module Campa
2
+ # Implements the features available
3
+ # to the command `campa` shipped with this gem.
2
4
  class Cli
3
- def initialize(repl: nil, evaler: nil, context: nil, reader: nil)
5
+ # @param repl [#run] the repl to be run if no option is given to `campa`
6
+ # @param evaler [#eval] lisp/campa evaler to be used
7
+ # by the repl or file evaluation
8
+ # @param context [#push, #[], #[]=] the context provider
9
+ # for the current run.
10
+ # This is where the __out__ binding pointing to $stdout will be created.
11
+ # @param reader [#new] lisp/campa reader for when
12
+ # an existent file is passed to `campa`.
13
+ def initialize(repl: nil, evaler: nil, context: nil, reader: Campa::Reader)
4
14
  @evaler = evaler || default_evaler
5
15
  @context = context || default_context
6
- @reader = reader || default_reader
16
+ @reader = reader
7
17
 
8
18
  @repl = repl || default_repl
9
19
  end
10
20
 
21
+ # Execute some campa stuff
22
+ # depending on the options given in the command line.
23
+ #
24
+ # If no argument is given
25
+ # it will start a {Campa::Repl} session.
26
+ #
27
+ # When first argument is an existent file
28
+ # then it will evaluated using {Campa::Reader} and {Campa::Evaler}.
29
+ #
30
+ # If the CLI argument is anything else,
31
+ # this method tries to match it with {OPTIONS}
32
+ # and find the method to be executed.
33
+ # Current options for the CLI are:
34
+ #
35
+ # <b><i>campa test FILE1, FILE2</i></b>
36
+ # Uses {Campa::Core::Test} and {Campa::Core::TestReport} to evaluate
37
+ # the files given as options as *Campa* test code.
11
38
  def execute(argv = nil, input: $stdin, out: $stdout)
12
39
  return repl.run(input, out) if argv.nil? || argv.empty?
13
40
  return evaluate(argv[0], input, out) if File.file?(argv[0])
@@ -58,9 +85,5 @@ module Campa
58
85
  def default_context
59
86
  @default_context ||= Campa::Language.new
60
87
  end
61
-
62
- def default_reader
63
- @default_reader ||= Campa::Reader
64
- end
65
88
  end
66
89
  end
data/lib/campa/context.rb CHANGED
@@ -1,36 +1,84 @@
1
1
  module Campa
2
+ # Represents a storage for all bindings
3
+ # in any <i>Campa</i> execution.
4
+ #
5
+ # All functions run inside their own context
6
+ # so when they are finished
7
+ # any binding created by it will be gone.
8
+ #
9
+ # The {Repl} also uses it's own {Context} for execution.
2
10
  class Context
11
+ # @!visibility private
3
12
  attr_accessor :fallback
4
13
 
14
+ # @param env [#[], #[]=] a Hash like or another {Context} object
15
+ # to be used as the underlying storage for this {Context}.
5
16
  def initialize(env = {})
6
17
  @env = env
7
18
  end
8
19
 
20
+ # Creates a new binding between a given {Symbol} and an Object.
21
+ #
22
+ # @param symbol [Symbol]
23
+ # (actually, nothing guarantees it will be a symbol right now)
24
+ # to be bound to a value in this {Context}.
25
+ # @param value [Object] associated to a given {Symbol}
9
26
  def []=(symbol, value)
10
27
  env[symbol] = value
11
28
  end
12
29
 
30
+ # Returns the value bound to a {Symbol}
31
+ #
32
+ # @param symbol [Symbol] for which we want to fetch the value
33
+ # in this {Context}
34
+ # @return Object
13
35
  def [](symbol)
14
36
  return env[symbol] if env.include?(symbol)
15
37
 
16
38
  fallback[symbol] if !fallback.nil?
17
39
  end
18
40
 
41
+ # Check if there is any binding for a {Symbol}
42
+ #
43
+ # @param symbol [Symbol] "label" for a (possibly) bound value
19
44
  def include?(symbol)
20
45
  env.include?(symbol) ||
21
46
  (!fallback.nil? && fallback.include?(symbol))
22
47
  end
23
48
 
49
+ # Creates a new {Context} assigning self as a #fallback to it.
50
+ #
51
+ # This means that if a {Symbol}
52
+ # is not present on the returned {Context},
53
+ # it will be searched on this one.
54
+ # Which allows for building some sort of stack of {Context}s.
55
+ #
56
+ # WARNING: The name <i>push</i> on this API is very questionable,
57
+ # I would like to change this to a visually more telling API.
58
+ #
59
+ # @param new_env [#[], #[]=] a Hash like or a {Context}
60
+ # that will serve as a fallback.
61
+ # @return {Context}
24
62
  def push(new_env = {})
25
- # Context is explicit here
26
- # (instead of self.class.new)
27
- # because we can inherit a context,
28
- # like the Lisp::Core does
29
- # and then we want a normal context when pushing to it
63
+ # Context is explicit here instead of self.class.new
64
+ # because we can inherit a context
65
+ # like the Lisp::Core does.
66
+ # In this case we want a normal context when pushing to it
30
67
  # (and not a Lisp::Core).
31
68
  Context.new(new_env).tap { |c| c.fallback = self }
32
69
  end
33
70
 
71
+ # Return the current bindings in an Array.
72
+ #
73
+ # @example A simple {Context} with two bound {Symbol}
74
+ # ctx = Context.new(
75
+ # Symbol.new("lol") => "what time is it?",
76
+ # Symbol.new("bbq") => 420,
77
+ # )
78
+ # ctx.bindings #=> [
79
+ # [Symbol.new("lol"), "what time is it?"],
80
+ # [Symbol.new("bbq"), 420]
81
+ # ]
34
82
  def bindings
35
83
  @bindings ||= env.is_a?(Context) ? env.bindings : env.to_a
36
84
  end
data/lib/campa/core.rb ADDED
@@ -0,0 +1,6 @@
1
+ module Campa
2
+ # Serves as a house to all functions
3
+ # that are essential for <i>Campa</i> to work
4
+ # but not necessary for <i>Lisp</i> itself.
5
+ module Core; end
6
+ end
@@ -1,10 +1,19 @@
1
1
  module Campa
2
2
  module Core
3
+ # Implements a <i>Campa</i> function
4
+ # that reads ({Reader}) and evaluates ({Evaler})
5
+ # files with valid <i>Campa</i> code in the given {Context}.
3
6
  class Load
4
7
  def initialize
5
8
  @evaler = Evaler.new
6
9
  end
7
10
 
11
+ # @param paths [Array<String>] Strings representing paths to files
12
+ # to be evaled in a given context
13
+ # @param env [Context] where the files pointed by <i>paths</i>
14
+ # will be evaled
15
+ # @return [Object] value of the last form evaled from the last file
16
+ # given by <i>paths</i>
8
17
  def call(*paths, env:)
9
18
  verify_presence(paths)
10
19
  paths.reduce(nil) do |_, file|
@@ -1,6 +1,18 @@
1
1
  module Campa
2
2
  module Core
3
+ # <i>Campa</i> function that print "anything" to the $stdout.
3
4
  class Print
5
+ # It uses {Printer} to transform an Object
6
+ # into a human readable form
7
+ # and sends it to $stdout.
8
+ #
9
+ # It is possible to override the preference for using $stdout
10
+ # by binding {SYMBOL_OUT} to a desired Object
11
+ # in the env given as a parameter to this method.
12
+ #
13
+ # @param stuff [Object] anything resulting from evaling a <i>Campa</i> expression
14
+ # @param env [Context] where {SYMBOL_OUT} will be searched
15
+ # to find an alternative to $stdout
4
16
  def call(*stuff, env:)
5
17
  string =
6
18
  stuff
@@ -1,6 +1,22 @@
1
1
  module Campa
2
2
  module Core
3
+ # <i>Campa</i> function that print "anything" to the $stdout.
3
4
  class PrintLn
5
+ # It uses {Printer} to transform an Object
6
+ # into a human readable form
7
+ # and sends it to $stdout.
8
+ #
9
+ # Uses <i>#puts</i> method in the output to add a new line
10
+ # after sending each String representation
11
+ # created via {Printer} be sent to the output.
12
+ #
13
+ # It is possible to override the preference for using $stdout
14
+ # by binding {SYMBOL_OUT} to a desired Object
15
+ # in the env given as a parameter to this method.
16
+ #
17
+ # @param stuff [Object] anything resulting from evaling a <i>Campa</i> expression
18
+ # @param env [Context] where {SYMBOL_OUT} will be searched
19
+ # to find an alternative to $stdout
4
20
  def call(*stuff, env:)
5
21
  out = env[SYMBOL_OUT] || $stdout
6
22
  stuff.each { |s| out.puts(s.is_a?(String) ? s : printer.call(s)) }
@@ -1,12 +1,40 @@
1
1
  module Campa
2
2
  module Core
3
+ # Searches functions with prefix test_ or test- (case insenstive)
4
+ # in a given context, invoke those and
5
+ # store their results in a Data Structure with the given form:
6
+ #
7
+ # (
8
+ # (success, (test-one, test-two)),
9
+ # (failures, (test-three, test-four))
10
+ # )
11
+ #
12
+ # In this example we are considering that
13
+ # functions <i>test-one</i> and <i>test-two</i> returned <b>true</b> and
14
+ # functions <i>test-three</i> and <i>test-four</i> returned <b>false</b>.
3
15
  class Test
4
- TEST_REGEXP = /\Atest(_|-)(.+)$/i
5
-
6
16
  def initialize
7
17
  @evaler = Campa::Evaler.new
8
18
  end
9
19
 
20
+ # Execute functions named test-* or test_* (case insentive)
21
+ # and collect the results.
22
+ #
23
+ # The param <i>tests</i> can be used to filter
24
+ # specific tests to be executed.
25
+ # For a context where functions
26
+ # test-great-one, test-great-two and test-awful exists,
27
+ # if we want to execute only the <i>great</i> ones, we could do:
28
+ #
29
+ # @example
30
+ # test = Test.new
31
+ # test.call("great", env: ctx)
32
+ #
33
+ # @param tests [Array<String>] if given will be used to "filter"
34
+ # functions by name
35
+ # @param env [Context] will be used to search functions
36
+ # named test-* or test_* (case insentive) and also
37
+ # to execute those functions
10
38
  def call(*tests, env:)
11
39
  summary = execute_all(tests, env)
12
40
  List.new(
@@ -17,6 +45,8 @@ module Campa
17
45
 
18
46
  private
19
47
 
48
+ TEST_REGEXP = /\Atest(_|-)(.+)$/i
49
+
20
50
  attr_reader :evaler
21
51
 
22
52
  def execute_all(tests, env)
@@ -1,6 +1,16 @@
1
1
  module Campa
2
2
  module Core
3
+ # Uses the result of a {Test#call} execution
4
+ # to show a human readable summary of a test execution.
3
5
  class TestReport
6
+ # Receives the result of {Test#call}
7
+ # and shows a (hopefully) useful test summary.
8
+ #
9
+ # @param result [List] result of calling {Test#call}
10
+ # @param env [Context] where object bound to {SYMBOL_OUT} will be searched
11
+ # as an alternative to $stdout
12
+ # @return [Boolean] <b>true</b> if all tests were successful
13
+ # (<b>false</b> otherwhise).
4
14
  def call(result, env:)
5
15
  success, failures = %i[success failures].map { |t| filter(t, result) }
6
16
  out = env[SYMBOL_OUT] || $stdout
data/lib/campa/evaler.rb CHANGED
@@ -1,9 +1,24 @@
1
1
  module Campa
2
+ # All the actual logic on how to evaluate
3
+ # the differente known forms
4
+ # in implemented in here.
2
5
  class Evaler
3
6
  def initialize
4
7
  @printer = Printer.new
5
8
  end
6
9
 
10
+ # Returns the result of a given form evaluation.
11
+ #
12
+ # The parameter expression is evaluated
13
+ # based on it's type.
14
+ # Primitives (like booleans, nil, strings...) are returned as is.
15
+ # {Symbol}s and {List}s are handled like this:
16
+ # - {Symbol}'s are searched in the given {Context} (env parameter).
17
+ # - {List}'s are considered function invocations.
18
+ #
19
+ # @param expression Can be any known form.
20
+ # @param env [#[], #[]=] Hash or {Context} containing the bindings
21
+ # for the current <i>Campa</i> execution.
7
22
  def call(expression, env = {})
8
23
  context = self.context(env)
9
24
 
@@ -17,6 +32,15 @@ module Campa
17
32
  end
18
33
  end
19
34
 
35
+ # Receives a {Reader} object
36
+ # and evaluate all forms returned
37
+ # by each <i>#next</i> call.
38
+ # Uses {#call} to do the actual evaluation.
39
+ #
40
+ # @param reader [Reader] representing the source code to be evaluated
41
+ # @param env [Context] to evaluate the code
42
+ # @return [Object] the result of evaluating the last form
43
+ # available in the given {Reader}
20
44
  def eval(reader, env = {})
21
45
  context = self.context(env)
22
46
 
@@ -1,3 +1,6 @@
1
1
  module Campa
2
+ # This exception serves as a base error.
3
+ #
4
+ # In a way it is the "StandardError" on Campa's runtime.
2
5
  class ExecutionError < StandardError; end
3
6
  end
data/lib/campa/lambda.rb CHANGED
@@ -1,7 +1,49 @@
1
1
  module Campa
2
+ # Represents an anonymous function
3
+ # that will be executed in a given {Context}.
4
+ #
5
+ # @example
6
+ # # given the representation of
7
+ # # ((lambda (something) (print something)) "hello world")
8
+ # lbd = Lambda.new(
9
+ # [Symbol.new("something")],
10
+ # [List.new(Symbol.new("print"), Symbol.new("something"))]
11
+ # )
12
+ #
13
+ # # sends "hello world" to the $stdout and returns
14
+ # lbd.call("hello world") #=> nil
15
+ #
16
+ # @example working with closures:
17
+ #
18
+ # # given the representation of
19
+ # # (label meaning 42)
20
+ # # ((lambda (time) (print "time: " time " meaning of life: " meaning)) 420)
21
+ # ctx = Context.new(Symbol.new("meaning") => 42)
22
+ #
23
+ # lbd = Lambda.new(
24
+ # [Symbol.new("time")],
25
+ # [
26
+ # List.new(
27
+ # Symbol.new("print"),
28
+ # "time: ", Symbol.new("time"),
29
+ # ", meaning of life: ", Symbol.new("meaning")
30
+ # )
31
+ # ],
32
+ # ctx
33
+ # )
34
+ #
35
+ # # sends "time: 420, meaning of life: 42" to $stdout and returns
36
+ # lbd.call(420) #=> nil
2
37
  class Lambda
3
38
  attr_reader :params, :body, :closure
4
39
 
40
+ # @param params [Array<Symbol>] {Symbol}s naming the parameters
41
+ # that a lambda can receive when being invoked
42
+ # @param body [Array<Object>] expressions composing the body,
43
+ # they are executed one by one in order
44
+ # in the given {Context}
45
+ # @param closure [Context] used as a fallback for the {Context}
46
+ # given when the {Lambda} is executed
5
47
  def initialize(params, body, closure = Context.new)
6
48
  @params = params
7
49
  @body = Array(body)
@@ -9,6 +51,21 @@ module Campa
9
51
  @evaler = Evaler.new
10
52
  end
11
53
 
54
+ # Executes the expressions contained in the {#body}
55
+ # one by one using as {Context} the parameter <i>env:</i>
56
+ # on this method.
57
+ #
58
+ # The <i>env:</i> param will be used here
59
+ # as a fallback to a brand new {Context}
60
+ # created in the moment of the invocation.
61
+ # This isolates the {Context} passed as a parameter
62
+ # of any mutations that would be created
63
+ # by the {Lambda} if there is any binding during the execution.
64
+ #
65
+ # @param args [Array<Object>] the values that will be bound
66
+ # to each of the {#params} given to the constructor
67
+ # @param env [Context] for the execution of a lambda
68
+ # @return [Object] result of evaluating the last expression on {#body}
12
69
  def call(*args, env:)
13
70
  raise arity_error(args) if params.to_a.length != args.length
14
71
 
@@ -20,6 +77,12 @@ module Campa
20
77
  end
21
78
  end
22
79
 
80
+ # Stablishes equality between {Lambda} objects
81
+ # by comparing {#params} and {#body}
82
+ #
83
+ # @param other [Lambda] another {Lambda}
84
+ # @return [Boolean] <b>true</b> if {#params} names and {#body} expressions
85
+ # are <i>#==</i> in both {Lambda}.
23
86
  def ==(other)
24
87
  return false if !other.is_a?(Campa::Lambda)
25
88
 
@@ -1,4 +1,12 @@
1
1
  module Campa
2
+ # {Context} wrapping the core bindings
3
+ # that form the core for <b>Campa</b> (language).
4
+ #
5
+ # It extends {Lisp::Core} to add
6
+ # - (test-run ...)
7
+ # - (tests-report)
8
+ # - (print ...)
9
+ # - (println ...)
2
10
  class Language < Lisp::Core
3
11
  def initialize
4
12
  super
@@ -1,6 +1,24 @@
1
1
  module Campa
2
2
  module Lisp
3
+ # {Context} representing the base for the minimal Lisp
4
+ # stablished by <i>Paul Graham's: <b>The Roots of Lisp</b></i>.
5
+ #
6
+ # Since this is a {Context}
7
+ # it can be instantiated and use as a parameter
8
+ # to any function invocation that
9
+ # want's to use <i>Lisp</i> functions on it's body.
10
+ # And also, it can be used as a fallback
11
+ # so if we want to "extend" <i>Lisp</i>
12
+ # we could Inherit it or {#push} a new {Context} on it.
3
13
  class Core < Context
14
+ def initialize
15
+ super Hash[
16
+ CORE_FUNCS_MAP.map { |label, handler| [sym(label), handler.new] }
17
+ ]
18
+ end
19
+
20
+ private
21
+
4
22
  CORE_FUNCS_MAP = {
5
23
  "quote" => Quote,
6
24
  "atom" => Atom,
@@ -19,14 +37,6 @@ module Campa
19
37
  "load" => Campa::Core::Load,
20
38
  }.freeze
21
39
 
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
40
  def sym(label)
31
41
  Campa::Symbol.new(label)
32
42
  end
data/lib/campa/list.rb CHANGED
@@ -1,9 +1,21 @@
1
1
  module Campa
2
+ # A minimalist implementation of a Linked List.
3
+ #
4
+ # An instance of it represents a function ivocation
5
+ # in the context of a {Evaler#call} or a {Evaler#eval} call.
6
+ #
7
+ # The importance of adding the adjective <i>"minimalist"</i>
8
+ # in this description
9
+ # is to give already the idea that this implementation
10
+ # does not come with some algorithms (deleting, for example)
11
+ # since for this also minimal Lisp implementation
12
+ # this won't be necessary.
2
13
  class List
3
14
  include Enumerable
4
15
 
5
16
  EMPTY = new
6
17
 
18
+ # @param elements [Array<Object>] elements linked on the current {List}
7
19
  def initialize(*elements)
8
20
  @first = nil
9
21
  @head = nil
@@ -12,24 +24,45 @@ module Campa
12
24
  with elements
13
25
  end
14
26
 
27
+ # Creates a new {List} with the parameter passed
28
+ # in front of it.
29
+ #
30
+ # @example
31
+ # List.new(2, 3).push(1) #=> List.new(1, 2, 3)
32
+ #
33
+ # @param element [Object] any thing to be pushed into the {List}
34
+ # @return [List] new {List} with new element in front of it
15
35
  def push(element)
16
36
  self.class.new.tap do |l|
17
37
  l.first = Node.new(value: element, next_node: first)
18
38
  end
19
39
  end
20
40
 
41
+ # First element of the current {List}.
42
+ #
43
+ # Return <i>nil</i> if the current list is {EMPTY}.
44
+ #
45
+ # @return [Object, nil]
21
46
  def head
22
47
  return nil if self == EMPTY
23
48
 
24
49
  first.value
25
50
  end
26
51
 
52
+ # Creates a new list
53
+ # with all elements of the current one BUT the head.
54
+ #
55
+ # @example
56
+ # List.new(1, 2, 3).tail #=> List.new(2, 3)
57
+ #
58
+ # @return [List] with the "rest" of the elements on this {List}
27
59
  def tail
28
60
  return EMPTY if first.nil? || first.next_node.nil?
29
61
 
30
62
  self.class.new.tap { |l| l.first = @first.next_node }
31
63
  end
32
64
 
65
+ # Yields each element starting from head.
33
66
  def each(&block)
34
67
  return if self == EMPTY
35
68
 
@@ -37,6 +70,11 @@ module Campa
37
70
  tail.each(&block) if tail != EMPTY
38
71
  end
39
72
 
73
+ # Stablishes equality by comparing each element in the {List}.
74
+ #
75
+ #
76
+ # @param other [List] to be compared
77
+ # @return [Boolean] <b>true</b> if of both {List} have equal (<i>#==</i>) elements
40
78
  def ==(other)
41
79
  return false if !other.is_a?(List)
42
80
 
@@ -56,6 +94,11 @@ module Campa
56
94
  end
57
95
  end
58
96
 
97
+ # Uses {Printer} to represent the current {List}
98
+ # in a human readable form.
99
+ #
100
+ # @example
101
+ # List.new(1, 2, 3).inspect #=> "(1 2 3)"
59
102
  def inspect
60
103
  @printer ||= Printer.new
61
104
  @printer.call(self)
data/lib/campa/node.rb CHANGED
@@ -1,13 +1,19 @@
1
1
  module Campa
2
+ # A node containing a value to form a linked {List}.
2
3
  class Node
3
4
  attr_accessor :next_node
4
5
  attr_reader :value
5
6
 
7
+ # @param value [Object] actual value in the {List} node
8
+ # @param next_node [Node] next node linked in the {List} chain,
9
+ # this {Node} is the last if <i>next_node:</i> is <i>nil</i>
6
10
  def initialize(value:, next_node: nil)
7
11
  @value = value
8
12
  @next_node = next_node
9
13
  end
10
14
 
15
+ # @param other [Node] to be compared
16
+ # @return [Boolean] <b>true</b> if {#value} is <i>#==</i> on both {Node}
11
17
  def ==(other)
12
18
  return false if !other.is_a?(Node)
13
19
 
data/lib/campa/printer.rb CHANGED
@@ -1,5 +1,32 @@
1
1
  module Campa
2
+ # Represents a <i>Campa</i> expression
3
+ # in a human readable form.
4
+ #
5
+ # In general it tries to create a representation
6
+ # that is valid <i>Campa</i> code
7
+ # to make it easy(ier) to copy and paste stuff
8
+ # from the REPL to a file.
9
+ #
10
+ # @example some results produced by {Printer}
11
+ # printer = Printer.new
12
+ #
13
+ # printer.call("lol") #=> "lol"
14
+ # printer.call("lol") #=> 123
15
+ # printer.call(List.new("bbq", 420, List.new("yes"))) #=> ("bbq" 420 ("yes"))
2
16
  class Printer
17
+ # @param expr [Object] <i>Campa</i> expression to be respresented in
18
+ # human readable form
19
+ # @return [String] human readable form (almost always valid code)
20
+ # of an Expression
21
+ def call(expr)
22
+ format = FORMATS.fetch(expr.class) do
23
+ expr.is_a?(Context) ? :context : :default
24
+ end
25
+ send(format, expr)
26
+ end
27
+
28
+ private
29
+
3
30
  FORMATS = {
4
31
  String => :string,
5
32
  Symbol => :symbol,
@@ -11,15 +38,6 @@ module Campa
11
38
  NilClass => :null,
12
39
  }.freeze
13
40
 
14
- def call(expr)
15
- format = FORMATS.fetch(expr.class) do
16
- expr.is_a?(Context) ? :context : :default
17
- end
18
- send(format, expr)
19
- end
20
-
21
- private
22
-
23
41
  def string(expr)
24
42
  "\"#{expr}\""
25
43
  end
data/lib/campa/reader.rb CHANGED
@@ -1,14 +1,28 @@
1
1
  require "stringio"
2
2
 
3
3
  module Campa
4
+ # Reads strings or files into <i>Campa</i> expressions.
4
5
  # rubocop: disable Metrics/ClassLength
5
6
  class Reader
6
7
  # rubocop: enable Metrics/ClassLength
8
+
9
+ # Given a String, a file pointer or any <i>#getc</i>, <i>#eof?</i>
10
+ # it allows fetch every valid <i>Campa</i> form from it.
11
+ #
12
+ # If the String is a valid file path
13
+ # it will be converted into a file pointer.
14
+ # Anything else will be converted into a StringIO
15
+ #
16
+ # @param input [String, (#getc, #eof?)]
7
17
  def initialize(input)
8
18
  @input = to_io_like(input)
9
19
  next_char
10
20
  end
11
21
 
22
+ # Return the next <i>Campa</i> form available
23
+ # in the underlying io like object.
24
+ #
25
+ # @return [Object] next available <i>Campa</i> form
12
26
  def next
13
27
  eat_separators
14
28
  return read if !@input.eof?
data/lib/campa/repl.rb CHANGED
@@ -1,5 +1,12 @@
1
1
  module Campa
2
2
  class Repl
3
+ # It creates a new {Context} that uses one
4
+ # given as a parameter to this contructor to evaluate
5
+ # expressions typed in the <b>REPL</b>.
6
+ #
7
+ # @param evaler [Evaler] used to extract value from expressions
8
+ # @param context [Context] against which expressions will be evaled
9
+ # @param reader [Reader] to return expressions to be evaled
3
10
  def initialize(evaler, context, reader: Reader)
4
11
  @reader = reader
5
12
  @evaler = evaler
@@ -8,6 +15,17 @@ module Campa
8
15
  @printer = Printer.new
9
16
  end
10
17
 
18
+ # Uses the <i>#getc</i> method from the parameter <i>input</i>
19
+ # to create a (probably blocking) loop to evaluate code "live"
20
+ # creating a <b>REPL</b> session.
21
+ #
22
+ # Captures the Interrupt exception to break the loop
23
+ # and finish the current <b>REPL</b> session.
24
+ #
25
+ # @param input [IO] an IO like object from where tokens will be extracted
26
+ # @param output [IO] IO like object to receive the output
27
+ # from evaluating the forms
28
+ #
11
29
  # rubocop: disable Metrics/MethodLength
12
30
  def run(input, output)
13
31
  output.print "=> "
data/lib/campa/symbol.rb CHANGED
@@ -1,21 +1,32 @@
1
1
  module Campa
2
+ # Represents the name to which a value is bound in a specific {Context}.
3
+ #
4
+ # Implements the necessary interface to be used with success
5
+ # as key in Hash objects.
2
6
  class Symbol
3
7
  attr_reader :label
4
8
 
9
+ # @param label [String] name to be bound to a value in a given {Context}
5
10
  def initialize(label)
6
11
  @label = label
7
12
  end
8
13
 
14
+ # @param other [Symbol] another {Symbol} to be compared
15
+ # @return [Boolean] <b>true</b> if both {#label} are <i>#==</i>
9
16
  def ==(other)
10
17
  return false if !other.is_a?(self.class)
11
18
 
12
19
  label == other.label
13
20
  end
14
21
 
22
+ # @param other [Symbol] another {Symbol} to be compared
23
+ # @return [Boolean] <b>true</b> if both {Symbol} are <i>#==</i>
24
+ # and both {#hash} are <i>#==</i>
15
25
  def eql?(other)
16
26
  self == other && hash == other.hash
17
27
  end
18
28
 
29
+ # @return [String] String based on <i>label#hash</i>.
19
30
  def hash
20
31
  @hash ||= "Campa::Symbol_#{label}".hash
21
32
  end
data/lib/campa/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Campa
4
- VERSION = "0.1.2"
4
+ VERSION = "0.1.3"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: campa
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ricardo Valeriano
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-07-29 00:00:00.000000000 Z
11
+ date: 2021-08-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: zeitwerk
@@ -32,6 +32,7 @@ executables:
32
32
  extensions: []
33
33
  extra_rdoc_files: []
34
34
  files:
35
+ - ".gitattributes"
35
36
  - ".gitignore"
36
37
  - ".rspec"
37
38
  - ".rubocop.yml"
@@ -52,6 +53,7 @@ files:
52
53
  - lib/campa.rb
53
54
  - lib/campa/cli.rb
54
55
  - lib/campa/context.rb
56
+ - lib/campa/core.rb
55
57
  - lib/campa/core/load.rb
56
58
  - lib/campa/core/print.rb
57
59
  - lib/campa/core/print_ln.rb