code-ruby 0.2.4 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/Gemfile +2 -1
  4. data/Gemfile.lock +6 -1
  5. data/bin/code +51 -0
  6. data/bin/template +6 -2
  7. data/docs/euler/4.template +0 -1
  8. data/docs/fibonnaci.template +14 -0
  9. data/docs/precedence.template +6 -31
  10. data/lib/code/node/code.rb +7 -1
  11. data/lib/code/node/list.rb +7 -7
  12. data/lib/code/node/name.rb +6 -1
  13. data/lib/code/node/string.rb +11 -6
  14. data/lib/code/node/string_characters.rb +13 -0
  15. data/lib/code/node/string_component.rb +23 -0
  16. data/lib/code/node/string_interpolation.rb +13 -0
  17. data/lib/code/object/argument.rb +1 -1
  18. data/lib/code/object/decimal.rb +22 -1
  19. data/lib/code/object/dictionnary.rb +24 -0
  20. data/lib/code/object/function.rb +2 -1
  21. data/lib/code/object/integer.rb +18 -20
  22. data/lib/code/object/list.rb +4 -0
  23. data/lib/code/object/string.rb +2 -2
  24. data/lib/code/object.rb +7 -0
  25. data/lib/code/parser/and_operator.rb +4 -4
  26. data/lib/code/parser/call.rb +3 -3
  27. data/lib/code/parser/code.rb +3 -4
  28. data/lib/code/parser/defined.rb +2 -2
  29. data/lib/code/parser/dictionnary.rb +1 -1
  30. data/lib/code/parser/equality.rb +4 -4
  31. data/lib/code/parser/function.rb +3 -2
  32. data/lib/code/parser/group.rb +1 -1
  33. data/lib/code/parser/if.rb +3 -3
  34. data/lib/code/parser/list.rb +1 -1
  35. data/lib/code/parser/not_keyword.rb +2 -2
  36. data/lib/code/parser/statement.rb +1 -1
  37. data/lib/code/parser/string.rb +19 -4
  38. data/lib/code/parser/ternary.rb +3 -3
  39. data/lib/code-ruby.rb +3 -1
  40. data/lib/code.rb +10 -9
  41. data/lib/template/version.rb +1 -1
  42. data/lib/template-ruby.rb +3 -1
  43. data/lib/template.rb +14 -8
  44. data/spec/code/error/type_error_spec.rb +0 -2
  45. data/spec/code/parser/dictionnary_spec.rb +5 -32
  46. data/spec/code/parser/string_spec.rb +15 -16
  47. data/spec/code_spec.rb +12 -0
  48. data/spec/template_spec.rb +6 -0
  49. metadata +8 -4
  50. data/TODO.md +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 82ade8726d95b0c6a5305197d56eeba0470ba5ae02df49ae96ec5fb1d16a46bf
4
- data.tar.gz: 004cc8befedb42093761f65464c24d937e8437b14adba8d8bcd0998831be972e
3
+ metadata.gz: 1dfeedb1e668e150c3039e1cee85eee9072ea16d0f619f45e71263c8c94ae707
4
+ data.tar.gz: 1c2fb6de918462b02df14191ab14f7d27a92f0bea9113a54fa8344e7b8bab48e
5
5
  SHA512:
6
- metadata.gz: 817a6ae6b29e967cca049d5b31c39348a8dfb9c4e84c78bae33053389f3d8e2768689e07b290b6e3ed66e3929871d95c5efd0df190f2b54623b00be283847603
7
- data.tar.gz: 4535e7a6cfad6639b837c75fc54ad369b6b7666007cc360300aa3ef520f4da5e8b4ce402e6e65bf74460a3fd8eecc8ff25db7c94097c9cfadb38e53874658a19
6
+ metadata.gz: c810a7b5a9ca1cbfc9c710e1ec615dd043dccf047fd220f35b71cf8e7fa2feab9cbf3fde9b1f31b57c690e25f5be1e71f4881a0efced0baea0b9f51e4a9abd78
7
+ data.tar.gz: 45571c9a8247d75f3c513e52dfebc4ed3e3fe527996c5a1dc8717fb740c6fa4736c6fb26da93c57a73558318f45a4bf5dc3d2ccf38b460d570b58d1171e8a776
data/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## 0.3.1 / 2022-10-13
9
+
10
+ - Fix Zeitwerk auto-loading issue
11
+
12
+ ## 0.3.0 / 2022-10-10
13
+
14
+ - `bin/template` accepts `--timeout` (or `-t`) parameter
15
+ - Adds `bin/code` with same options as `bin/template`
16
+ - Prevent loose syntax like `{ a: }`, `[1,,,]` and `()`
17
+ - Change precedence of defined? (to allow `defined?(name) ? name : nothing`)
18
+ - Updates parsers to allow `while false end == nothing`
19
+ - String interpolations like `"1 + 1 = {1 + 1}"`
20
+ - `context(:name)` to get a function without calling it for instance
21
+ - `.to_string` on all objects
22
+ - `1 + "a"` and `"a" + 1.0` for instance now convert to strings
23
+ - `Dictionnary#each` e.g. `{ a: 1 }.each { |k, v| print(k) }`
24
+ - Fix context duplication issue that was preventing implementation of recursive
25
+ functions like Fibonacci
26
+
8
27
  ## 0.2.4 / 2022-08-02
9
28
 
10
29
  - Add method `String#*`, e.g. `{"Dorian " \* 2}" -> "Dorian Dorian "
data/Gemfile CHANGED
@@ -1,3 +1,4 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- gemspec
3
+ gemspec name: "template-ruby"
4
+ gemspec name: "code-ruby"
data/Gemfile.lock CHANGED
@@ -1,7 +1,11 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- template-ruby (0.2.3)
4
+ code-ruby (0.2.4)
5
+ activesupport (~> 7)
6
+ parslet (~> 2)
7
+ zeitwerk (~> 2.6)
8
+ template-ruby (0.2.4)
5
9
  activesupport (~> 7)
6
10
  parslet (~> 2)
7
11
  zeitwerk (~> 2.6)
@@ -62,6 +66,7 @@ PLATFORMS
62
66
  arm64-darwin-21
63
67
 
64
68
  DEPENDENCIES
69
+ code-ruby!
65
70
  prettier (~> 3)
66
71
  rspec (~> 3)
67
72
  template-ruby!
data/bin/code ADDED
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "optparse"
4
+ require_relative "../lib/template-ruby"
5
+
6
+ options = { timeout: 0 }
7
+
8
+ OptionParser.new do |opts|
9
+ opts.banner = "Usage: bin/code [options]"
10
+
11
+ opts.on(
12
+ "-i INPUT",
13
+ "--input=INPUT",
14
+ "Input in the code language (String or File)"
15
+ ) do |input|
16
+ if File.exists?(input)
17
+ input = File.read(input)
18
+ end
19
+
20
+ options[:input] = input
21
+ end
22
+
23
+ opts.on(
24
+ "-c CONTEXT",
25
+ "--context=CONTEXT",
26
+ "Context in the code language (String or File)"
27
+ ) do |context|
28
+ if File.exists?(context)
29
+ context = File.read(context)
30
+ end
31
+
32
+ options[:context] = context
33
+ end
34
+
35
+ opts.on("-p", "--parse", "Get parser results for input") do |parse|
36
+ options[:parse] = parse
37
+ end
38
+
39
+ opts.on("-t TIMEOUT", "--timeout=TIMEOUT", "Set timeout in seconds") do |timeout|
40
+ options[:timeout] = timeout.to_f
41
+ end
42
+ end.parse!
43
+
44
+ input = options.fetch(:input, "")
45
+ context = options.fetch(:context, "")
46
+
47
+ if options[:parse]
48
+ pp ::Code::Parser::Code.new.parse(input)
49
+ else
50
+ print Code.evaluate(input, context, io: $stdout, timeout: options[:timeout]).to_s
51
+ end
data/bin/template CHANGED
@@ -3,7 +3,7 @@
3
3
  require "optparse"
4
4
  require_relative "../lib/template-ruby"
5
5
 
6
- options = {}
6
+ options = { timeout: 0 }
7
7
 
8
8
  OptionParser.new do |opts|
9
9
  opts.banner = "Usage: template [options]"
@@ -27,6 +27,10 @@ OptionParser.new do |opts|
27
27
  opts.on("-p", "--parse", "Get parser results for input") do |parse|
28
28
  options[:parse] = parse
29
29
  end
30
+
31
+ opts.on("-t TIMEOUT", "--timeout=TIMEOUT", "Set timeout in seconds") do |timeout|
32
+ options[:timeout] = timeout.to_f
33
+ end
30
34
  end.parse!
31
35
 
32
36
  input = options.fetch(:input, "")
@@ -35,5 +39,5 @@ context = options.fetch(:context, "")
35
39
  if options[:parse]
36
40
  pp ::Template::Parser::Template.new.parse(input)
37
41
  else
38
- Template.render(input, context, io: $stdout)
42
+ Template.render(input, context, io: $stdout, timeout: options[:timeout])
39
43
  end
@@ -3,7 +3,6 @@
3
3
  min = 1
4
4
  max = 999
5
5
 
6
-
7
6
  (min..max).map do |i|
8
7
  ((min..max).to_list.reverse.detect do |j|
9
8
  (i * j).to_string.reverse == (i * j).to_string
@@ -0,0 +1,14 @@
1
+ {
2
+ fibonnaci = (n) => {
3
+ if n < 2
4
+ n
5
+ else
6
+ fibonnaci(n - 1) + fibonnaci(n - 2)
7
+ end
8
+ }
9
+
10
+ (0..20).each do |i|
11
+ puts("fibonnaci({i}) = {fibonnaci(i)}")
12
+ end
13
+
14
+ nothing
@@ -2,58 +2,33 @@
2
2
  - {a = "Go" a += "od" a}
3
3
  - Statement
4
4
  - {a = "Good"}
5
- - {
6
- a = 0
7
- while a < 10
8
- a += 1
9
- "Good"
10
- end
11
- }
5
+ - Equality
6
+ - {1 == 1 ? "Good"}
12
7
  - While
13
- - {
14
- a = 0
15
- until a > 10
16
- a += 1
17
- "Good"
18
- end
19
- }
20
- - {
21
- a = 0
22
- while
23
- if a > 10
24
- false
25
- else
26
- true
27
- end
28
- a += 1
29
- "Good"
30
- end
31
- }
8
+ - {a = 0
9
+ while a < 10 a += 1 "Good" end}
32
10
  - If
33
11
  - {if true "Good" end}
34
- - {if true if false "Bad" else "Good" end}
35
12
  - IfModifier
36
13
  - {"Good" if true}
37
14
  - OrKeyword
38
15
  - {false or "Good"}
39
16
  - NotKeyword
40
17
  - {not false and "Good"}
41
- - Defined
42
- - {a = 1 "Good" if defined?(a)}
43
18
  - Equal
44
19
  - {a = "Good"}
45
20
  - Rescue
46
21
  - {0 > "String" rescue "Good"}
47
22
  - Ternary
48
23
  - {nothing ? "Bad" : "Good"}
24
+ - Defined
25
+ - {a = 1 "Good" if defined?(a)}
49
26
  - Range
50
27
  - {("Good".."Bad").first}
51
28
  - OrOperator
52
29
  - {false || "Good"}
53
30
  - AndOperator
54
31
  - {"Bad" && "Good"}
55
- - Equality
56
- - {1 == 1 ? "Good"}
57
32
  - GreaterThan
58
33
  - {2 > 1 ? "Good"}
59
34
  - BitwiseOr
@@ -9,7 +9,13 @@ class Code
9
9
  end
10
10
 
11
11
  def evaluate(**args)
12
- @statements.map { |statement| statement.evaluate(**args) }.last
12
+ last = ::Code::Object::Nothing.new
13
+
14
+ @statements.each do |statement|
15
+ last = statement.evaluate(**args)
16
+ end
17
+
18
+ last
13
19
  end
14
20
  end
15
21
  end
@@ -2,13 +2,13 @@ class Code
2
2
  class Node
3
3
  class List < Node
4
4
  def initialize(codes)
5
- @codes =
6
- codes
7
- .map do |code|
8
- code.fetch(:code).presence &&
9
- ::Code::Node::Code.new(code.fetch(:code))
10
- end
11
- .compact
5
+ if codes.to_s.blank?
6
+ @codes = []
7
+ else
8
+ @codes = codes.map do |code|
9
+ ::Code::Node::Code.new(code.fetch(:code))
10
+ end
11
+ end
12
12
  end
13
13
 
14
14
  def evaluate(**args)
@@ -33,8 +33,13 @@ class Code
33
33
  end
34
34
  elsif name == "puts"
35
35
  arguments.each { |argument| io.puts argument.value }
36
-
37
36
  ::Code::Object::Nothing.new
37
+ elsif name == "print"
38
+ arguments.each { |argument| io.print argument.value }
39
+ ::Code::Object::Nothing.new
40
+ elsif name == "context"
41
+ return ::Code::Object::Nothing.new if arguments.size != 1
42
+ context[arguments.first&.value] || ::Code::Object::Nothing.new
38
43
  else
39
44
  raise ::Code::Error::Undefined.new("#{name} undefined")
40
45
  end
@@ -2,16 +2,21 @@ class Code
2
2
  class Node
3
3
  class String < Node
4
4
  def initialize(string)
5
- @string = string
5
+ if string.to_s.blank?
6
+ @string = []
7
+ elsif string.is_a?(Array)
8
+ @string = string.map do |component|
9
+ ::Code::Node::StringComponent.new(component)
10
+ end
11
+ else
12
+ @string = [::Code::Node::StringCharacters.new(string)]
13
+ end
6
14
  end
7
15
 
8
16
  def evaluate(**args)
9
- ::Code::Object::String.new(string.to_s)
17
+ string = @string.map { |component| component.evaluate(**args) }.map(&:to_s).join
18
+ ::Code::Object::String.new(string)
10
19
  end
11
-
12
- private
13
-
14
- attr_reader :string
15
20
  end
16
21
  end
17
22
  end
@@ -0,0 +1,13 @@
1
+ class Code
2
+ class Node
3
+ class StringCharacters < Node
4
+ def initialize(characters)
5
+ @characters = characters
6
+ end
7
+
8
+ def evaluate(**args)
9
+ ::Code::Object::String.new(@characters.to_s)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,23 @@
1
+ class Code
2
+ class Node
3
+ class StringComponent < Node
4
+ def initialize(component)
5
+ if component.key?(:characters)
6
+ @component = ::Code::Node::StringCharacters.new(
7
+ component.fetch(:characters)
8
+ )
9
+ elsif component.key?(:interpolation)
10
+ @component = ::Code::Node::StringInterpolation.new(
11
+ component.fetch(:interpolation)
12
+ )
13
+ else
14
+ raise NotImplementedError.new(component.inspect)
15
+ end
16
+ end
17
+
18
+ def evaluate(**args)
19
+ @component.evaluate(**args)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,13 @@
1
+ class Code
2
+ class Node
3
+ class StringInterpolation < Node
4
+ def initialize(interpolation)
5
+ @interpolation = ::Code::Node::Code.new(interpolation)
6
+ end
7
+
8
+ def evaluate(**args)
9
+ @interpolation.evaluate(**args)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -30,7 +30,7 @@ class Code
30
30
  end
31
31
 
32
32
  def to_s
33
- "argument"
33
+ "<Argument #{value.inspect}>"
34
34
  end
35
35
 
36
36
  def inspect
@@ -19,10 +19,14 @@ class Code
19
19
  operator = args.fetch(:operator, nil)
20
20
  arguments = args.fetch(:arguments, [])
21
21
 
22
- if %w[% - + / * **].detect { |o| operator == o }
22
+ if %w[% - / * **].detect { |o| operator == o }
23
23
  number_operation(operator.to_sym, arguments)
24
24
  elsif %w[< <= > >=].detect { |o| operator == o }
25
25
  comparaison(operator.to_sym, arguments)
26
+ elsif %w[<< >> & | ^].detect { |o| operator == o }
27
+ integer_operation(operator.to_sym, arguments)
28
+ elsif operator == "+"
29
+ plus(arguments)
26
30
  else
27
31
  super
28
32
  end
@@ -44,11 +48,28 @@ class Code
44
48
  ::Code::Object::Decimal.new(raw.public_send(operator, other.raw))
45
49
  end
46
50
 
51
+ def integer_operation(operator, arguments)
52
+ sig(arguments, ::Code::Object::Number)
53
+ other = arguments.first.value
54
+ ::Code::Object::Integer.new(raw.to_i.public_send(operator, other.raw.to_i))
55
+ end
56
+
47
57
  def comparaison(operator, arguments)
48
58
  sig(arguments, ::Code::Object::Number)
49
59
  other = arguments.first.value
50
60
  ::Code::Object::Boolean.new(raw.public_send(operator, other.raw))
51
61
  end
62
+
63
+ def plus(arguments)
64
+ sig(arguments, ::Code::Object)
65
+ other = arguments.first.value
66
+
67
+ if other.is_a?(::Code::Object::Number)
68
+ ::Code::Object::Decimal.new(raw + other.raw)
69
+ else
70
+ ::Code::Object::String.new(to_s + other.to_s)
71
+ end
72
+ end
52
73
  end
53
74
  end
54
75
  end
@@ -10,9 +10,13 @@ class Code
10
10
  def call(**args)
11
11
  operator = args.fetch(:operator, nil)
12
12
  arguments = args.fetch(:arguments, [])
13
+ context = args.fetch(:context)
14
+ io = args.fetch(:io)
13
15
 
14
16
  if operator == "values"
15
17
  values(arguments)
18
+ elsif operator == "each"
19
+ each(arguments, context: context, io: io)
16
20
  elsif key?(operator)
17
21
  fetch(operator)
18
22
  else
@@ -36,6 +40,10 @@ class Code
36
40
  raw.key?(key)
37
41
  end
38
42
 
43
+ def deep_dup
44
+ ::Code::Object::Dictionnary.new(raw.deep_dup)
45
+ end
46
+
39
47
  def to_s
40
48
  "{#{raw.map { |key, value| "#{key.inspect} => #{value.inspect}" }.join(", ")}}"
41
49
  end
@@ -50,6 +58,22 @@ class Code
50
58
  sig(arguments)
51
59
  ::Code::Object::List.new(raw.values)
52
60
  end
61
+
62
+ def each(arguments, context:, io:)
63
+ sig(arguments, ::Code::Object::Function)
64
+ argument = arguments.first.value
65
+ raw.each do |key, value|
66
+ argument.call(
67
+ arguments: [
68
+ ::Code::Object::Argument.new(key),
69
+ ::Code::Object::Argument.new(value)
70
+ ],
71
+ context: context,
72
+ io: io,
73
+ )
74
+ end
75
+ self
76
+ end
53
77
  end
54
78
  end
55
79
  end
@@ -32,7 +32,8 @@ class Code
32
32
  attr_reader :arguments, :body
33
33
 
34
34
  def call_function(args:, context:, io:)
35
- new_context = context.dup
35
+ new_context = context.deep_dup
36
+
36
37
  arguments.each.with_index do |argument, index|
37
38
  if argument.regular?
38
39
  if argument.splat?
@@ -21,13 +21,13 @@ class Code
21
21
 
22
22
  if operator == "even?"
23
23
  even?(arguments)
24
- elsif operator == "to_string"
25
- code_to_s(arguments)
26
24
  elsif operator == "*"
27
25
  multiplication(arguments)
28
26
  elsif operator == "/"
29
27
  division(arguments)
30
- elsif %w[% - + **].detect { |o| operator == o }
28
+ elsif operator == "+"
29
+ plus(arguments)
30
+ elsif %w[% - **].detect { |o| operator == o }
31
31
  integer_or_decimal_operation(operator.to_sym, arguments)
32
32
  elsif %w[> >= < <=].detect { |o| operator == o }
33
33
  comparaison(operator.to_sym, arguments)
@@ -38,16 +38,6 @@ class Code
38
38
  end
39
39
  end
40
40
 
41
- def +(other)
42
- if other.is_a?(::Code::Object::Integer)
43
- ::Code::Object::Integer.new(raw + other.raw)
44
- elsif other.is_a?(::Code::Object::Decimal)
45
- ::Code::Object::Decimal.new(raw + other.raw)
46
- else
47
- raise ::Code::Error::TypeError
48
- end
49
- end
50
-
51
41
  def succ
52
42
  ::Code::Object::Integer.new(raw + 1)
53
43
  end
@@ -67,11 +57,6 @@ class Code
67
57
  ::Code::Object::Boolean.new(raw.even?)
68
58
  end
69
59
 
70
- def code_to_s(arguments)
71
- sig(arguments)
72
- ::Code::Object::String.new(raw.to_s)
73
- end
74
-
75
60
  def multiplication(arguments)
76
61
  sig(arguments, [::Code::Object::Number, ::Code::Object::String])
77
62
  other = arguments.first.value
@@ -84,6 +69,19 @@ class Code
84
69
  end
85
70
  end
86
71
 
72
+ def plus(arguments)
73
+ sig(arguments, ::Code::Object)
74
+ other = arguments.first.value
75
+
76
+ if other.is_a?(::Code::Object::Integer)
77
+ ::Code::Object::Integer.new(raw + other.raw)
78
+ elsif other.is_a?(::Code::Object::Decimal)
79
+ ::Code::Object::Decimal.new(raw + other.raw)
80
+ else
81
+ ::Code::Object::String.new(to_s + other.to_s)
82
+ end
83
+ end
84
+
87
85
  def division(arguments)
88
86
  sig(arguments, ::Code::Object::Number)
89
87
  other = arguments.first.value
@@ -101,9 +99,9 @@ class Code
101
99
  end
102
100
 
103
101
  def integer_operation(operator, arguments)
104
- sig(arguments, ::Code::Object::Integer)
102
+ sig(arguments, ::Code::Object::Number)
105
103
  other = arguments.first.value
106
- ::Code::Object::Integer.new(raw.public_send(operator, other.raw))
104
+ ::Code::Object::Integer.new(raw.to_i.public_send(operator, other.raw.to_i))
107
105
  end
108
106
 
109
107
  def comparaison(operator, arguments)
@@ -63,6 +63,10 @@ class Code
63
63
  )
64
64
  end
65
65
 
66
+ def deep_dup
67
+ ::Code::Object::List.new(raw.deep_dup)
68
+ end
69
+
66
70
  def to_s
67
71
  "[#{raw.map(&:inspect).join(", ")}]"
68
72
  end
@@ -48,9 +48,9 @@ class Code
48
48
  end
49
49
 
50
50
  def plus(arguments)
51
- sig(arguments, ::Code::Object::String)
51
+ sig(arguments, ::Code::Object)
52
52
  other = arguments.first.value
53
- ::Code::Object::String.new(raw + other.raw)
53
+ ::Code::Object::String.new(raw + other.to_s)
54
54
  end
55
55
 
56
56
  def multiplication(arguments)
data/lib/code/object.rb CHANGED
@@ -13,6 +13,8 @@ class Code
13
13
  and_operator(arguments)
14
14
  elsif operator == "||"
15
15
  or_operator(arguments)
16
+ elsif operator == "to_string"
17
+ to_string(arguments)
16
18
  else
17
19
  raise ::Code::Error::Undefined.new(
18
20
  "#{operator} not defined on #{inspect}",
@@ -126,5 +128,10 @@ class Code
126
128
  other = arguments.first.value
127
129
  truthy? ? self : other
128
130
  end
131
+
132
+ def to_string(arguments)
133
+ sig(arguments)
134
+ ::Code::Object::String.new(to_s)
135
+ end
129
136
  end
130
137
  end
@@ -1,7 +1,7 @@
1
1
  class Code
2
2
  class Parser
3
3
  class AndOperator < Parslet::Parser
4
- rule(:equality) { ::Code::Parser::Equality.new }
4
+ rule(:greater_than) { ::Code::Parser::GreaterThan.new }
5
5
 
6
6
  rule(:ampersand) { str("&") }
7
7
 
@@ -14,12 +14,12 @@ class Code
14
14
 
15
15
  rule(:and_operator) do
16
16
  (
17
- equality.as(:first) >>
17
+ greater_than.as(:first) >>
18
18
  (
19
19
  whitespace? >> operator.as(:operator) >> whitespace? >>
20
- equality.as(:statement)
20
+ greater_than.as(:statement)
21
21
  ).repeat(1).as(:rest)
22
- ).as(:and_operator) | equality
22
+ ).as(:and_operator) | greater_than
23
23
  end
24
24
 
25
25
  root(:and_operator)
@@ -2,7 +2,7 @@ class Code
2
2
  class Parser
3
3
  class Call < Parslet::Parser
4
4
  rule(:dictionnary) { ::Code::Parser::Dictionnary.new }
5
- rule(:code) { ::Code::Parser::Code.new }
5
+ rule(:code) { ::Code::Parser::Code.new.present }
6
6
  rule(:name) { ::Code::Parser::Name.new }
7
7
  rule(:function_arguments) { ::Code::Parser::Function.new.arguments }
8
8
 
@@ -72,12 +72,12 @@ class Code
72
72
  rule(:block) do
73
73
  (
74
74
  whitespace >> do_keyword >> whitespace >>
75
- block_arguments.as(:arguments).maybe >> code.as(:body) >>
75
+ block_arguments.as(:arguments).maybe >> code.as(:body).maybe >>
76
76
  end_keyword
77
77
  ) |
78
78
  (
79
79
  whitespace? >> opening_curly_bracket >> whitespace >>
80
- block_arguments.as(:arguments).maybe >> code.as(:body) >>
80
+ block_arguments.as(:arguments).maybe >> code.as(:body).maybe >>
81
81
  closing_curly_bracket
82
82
  )
83
83
  end
@@ -8,11 +8,10 @@ class Code
8
8
  rule(:whitespace) { (space | newline).repeat(1) }
9
9
  rule(:whitespace?) { whitespace.maybe }
10
10
 
11
- rule(:code) do
12
- (whitespace?.ignore >> statement >> whitespace?.ignore).repeat(1) |
13
- whitespace?.ignore
11
+ rule(:present) do
12
+ (whitespace?.ignore >> statement >> whitespace?.ignore).repeat(1)
14
13
  end
15
-
14
+ rule(:code) { present | whitespace?.ignore }
16
15
  root(:code)
17
16
  end
18
17
  end
@@ -1,7 +1,7 @@
1
1
  class Code
2
2
  class Parser
3
3
  class Defined < Parslet::Parser
4
- rule(:equal) { ::Code::Parser::Equal.new }
4
+ rule(:range) { ::Code::Parser::Range.new }
5
5
  rule(:name) { ::Code::Parser::Name.new }
6
6
 
7
7
  rule(:defined_keyword) { str("defined?") }
@@ -11,7 +11,7 @@ class Code
11
11
  rule(:defined) do
12
12
  (
13
13
  defined_keyword >> opening_parenthesis >> name >> closing_parenthesis
14
- ).as(:defined) | equal
14
+ ).as(:defined) | range
15
15
  end
16
16
 
17
17
  root(:defined)
@@ -2,7 +2,7 @@ class Code
2
2
  class Parser
3
3
  class Dictionnary < Parslet::Parser
4
4
  rule(:list) { ::Code::Parser::List.new }
5
- rule(:code) { ::Code::Parser::Code.new }
5
+ rule(:code) { ::Code::Parser::Code.new.present }
6
6
  rule(:string) { ::Code::Parser::String.new }
7
7
 
8
8
  rule(:opening_curly_bracket) { str("{") }
@@ -1,7 +1,7 @@
1
1
  class Code
2
2
  class Parser
3
3
  class Equality < Parslet::Parser
4
- rule(:greater_than) { ::Code::Parser::GreaterThan.new }
4
+ rule(:while_parser) { ::Code::Parser::While.new }
5
5
 
6
6
  rule(:right_caret) { str(">") }
7
7
  rule(:left_caret) { str("<") }
@@ -22,12 +22,12 @@ class Code
22
22
 
23
23
  rule(:equality) do
24
24
  (
25
- greater_than.as(:first) >>
25
+ while_parser.as(:first) >>
26
26
  (
27
27
  whitespace? >> operator.as(:operator) >> whitespace? >>
28
- greater_than.as(:statement)
28
+ while_parser.as(:statement)
29
29
  ).repeat(1).as(:rest)
30
- ).as(:equality) | greater_than
30
+ ).as(:equality) | while_parser
31
31
  end
32
32
 
33
33
  root(:equality)
@@ -2,6 +2,7 @@ class Code
2
2
  class Parser
3
3
  class Function < Parslet::Parser
4
4
  rule(:call) { ::Code::Parser::Call.new }
5
+ rule(:code_present) { ::Code::Parser::Code.new.present }
5
6
  rule(:code) { ::Code::Parser::Code.new }
6
7
  rule(:name) { ::Code::Parser::Name.new }
7
8
 
@@ -22,14 +23,14 @@ class Code
22
23
  rule(:whitespace?) { whitespace.maybe }
23
24
 
24
25
  rule(:keyword_argument) do
25
- name >> whitespace? >> colon >> (whitespace? >> code.as(:default)).maybe
26
+ name >> whitespace? >> colon >> code_present.as(:default).maybe
26
27
  end
27
28
 
28
29
  rule(:regular_argument) do
29
30
  ampersand.as(:block).maybe >>
30
31
  (asterisk >> asterisk).as(:keyword_splat).maybe >>
31
32
  asterisk.as(:splat).maybe >> name >>
32
- (whitespace? >> equal >> whitespace? >> code.as(:default)).maybe
33
+ (whitespace? >> equal >> whitespace? >> code_present.as(:default)).maybe
33
34
  end
34
35
 
35
36
  rule(:argument) do
@@ -2,7 +2,7 @@ class Code
2
2
  class Parser
3
3
  class Group < Parslet::Parser
4
4
  rule(:name) { ::Code::Parser::Name.new }
5
- rule(:code) { ::Code::Parser::Code.new }
5
+ rule(:code) { ::Code::Parser::Code.new.present }
6
6
 
7
7
  rule(:opening_parenthesis) { str("(") }
8
8
  rule(:closing_parenthesis) { str(")") }
@@ -2,7 +2,7 @@ class Code
2
2
  class Parser
3
3
  class If < Parslet::Parser
4
4
  rule(:if_modifier) { ::Code::Parser::IfModifier.new }
5
- rule(:code) { ::Code::Parser::Code.new }
5
+ rule(:code) { ::Code::Parser::Code.new.present }
6
6
 
7
7
  rule(:if_keyword) { str("if") }
8
8
  rule(:else_keyword) { str("else") }
@@ -16,13 +16,13 @@ class Code
16
16
  rule(:if_rule) do
17
17
  (
18
18
  (if_keyword | unless_keyword).as(:if_operator) >> whitespace >>
19
- if_modifier.as(:if_statement) >> code.as(:if_body) >>
19
+ if_modifier.as(:if_statement) >> code.as(:if_body).maybe >>
20
20
  (
21
21
  else_keyword >>
22
22
  (
23
23
  whitespace >> (if_keyword | unless_keyword).as(:operator) >>
24
24
  whitespace >> if_modifier.as(:statement)
25
- ).maybe >> code.as(:body)
25
+ ).maybe >> code.as(:body).maybe
26
26
  ).repeat(1).as(:elses).maybe >> end_keyword
27
27
  ).as(:if) | if_modifier
28
28
  end
@@ -2,7 +2,7 @@ class Code
2
2
  class Parser
3
3
  class List < Parslet::Parser
4
4
  rule(:string) { ::Code::Parser::String.new }
5
- rule(:code) { ::Code::Parser::Code.new }
5
+ rule(:code) { ::Code::Parser::Code.new.present }
6
6
 
7
7
  rule(:opening_square_bracket) { str("[") }
8
8
  rule(:closing_square_bracket) { str("]") }
@@ -1,7 +1,7 @@
1
1
  class Code
2
2
  class Parser
3
3
  class NotKeyword < Parslet::Parser
4
- rule(:defined) { ::Code::Parser::Defined.new }
4
+ rule(:equal) { ::Code::Parser::Equal.new }
5
5
 
6
6
  rule(:not_keyword) { str("not") }
7
7
 
@@ -12,7 +12,7 @@ class Code
12
12
  rule(:whitespace) { (space | newline).repeat(1) }
13
13
 
14
14
  rule(:not_rule) do
15
- (not_keyword >> whitespace >> not_rule).as(:not_keyword) | defined
15
+ (not_keyword >> whitespace >> not_rule).as(:not_keyword) | equal
16
16
  end
17
17
 
18
18
  root(:not_rule)
@@ -1,7 +1,7 @@
1
1
  class Code
2
2
  class Parser
3
3
  class Statement < Parslet::Parser
4
- rule(:statement) { ::Code::Parser::While.new }
4
+ rule(:statement) { ::Code::Parser::Equality.new }
5
5
  root(:statement)
6
6
  end
7
7
  end
@@ -3,11 +3,14 @@ class Code
3
3
  class String < Parslet::Parser
4
4
  rule(:number) { ::Code::Parser::Number.new }
5
5
  rule(:name) { ::Code::Parser::Name.new.name }
6
+ rule(:code) { ::Code::Parser::Code.new }
6
7
 
7
8
  rule(:single_quote) { str("'") }
8
9
  rule(:double_quote) { str('"') }
9
10
  rule(:backslash) { str("\\") }
10
11
  rule(:colon) { str(":") }
12
+ rule(:opening_curly_bracket) { str("{") }
13
+ rule(:closing_curly_bracket) { str("}") }
11
14
 
12
15
  rule(:zero) { str("0") }
13
16
  rule(:one) { str("1") }
@@ -31,6 +34,10 @@ class Code
31
34
  rule(:t) { str("t") | str("T") }
32
35
  rule(:u) { str("u") | str("U") }
33
36
 
37
+ rule(:interpolation) do
38
+ opening_curly_bracket >> code >> closing_curly_bracket
39
+ end
40
+
34
41
  rule(:base_16_digit) do
35
42
  zero | one | two | three | four | five | six | seven | eight | nine |
36
43
  a | b | c | d | e | f
@@ -42,20 +49,28 @@ class Code
42
49
  end
43
50
 
44
51
  rule(:single_quoted_character) do
45
- escaped_character | (single_quote.absent? >> any)
52
+ escaped_character | (opening_curly_bracket.absent? >> single_quote.absent? >> any)
46
53
  end
47
54
 
48
55
  rule(:double_quoted_character) do
49
- escaped_character | (double_quote.absent? >> any)
56
+ escaped_character | (opening_curly_bracket.absent? >> double_quote.absent? >> any)
50
57
  end
51
58
 
52
59
  rule(:single_quoted_string) do
53
- single_quote.ignore >> single_quoted_character.repeat >>
60
+ single_quote.ignore >>
61
+ (
62
+ interpolation.as(:interpolation) |
63
+ single_quoted_character.repeat(1).as(:characters)
64
+ ).repeat >>
54
65
  single_quote.ignore
55
66
  end
56
67
 
57
68
  rule(:double_quoted_string) do
58
- double_quote.ignore >> double_quoted_character.repeat >>
69
+ double_quote.ignore >>
70
+ (
71
+ interpolation.as(:interpolation) |
72
+ double_quoted_character.repeat(1).as(:characters)
73
+ ).repeat >>
59
74
  double_quote.ignore
60
75
  end
61
76
 
@@ -1,7 +1,7 @@
1
1
  class Code
2
2
  class Parser
3
3
  class Ternary < Parslet::Parser
4
- rule(:range) { ::Code::Parser::Range.new }
4
+ rule(:defined) { ::Code::Parser::Defined.new }
5
5
 
6
6
  rule(:question_mark) { str("?") }
7
7
  rule(:colon) { str(":") }
@@ -13,10 +13,10 @@ class Code
13
13
 
14
14
  rule(:ternary) do
15
15
  (
16
- range.as(:left) >> whitespace >> question_mark >> whitespace? >>
16
+ defined.as(:left) >> whitespace >> question_mark >> whitespace? >>
17
17
  ternary.as(:middle) >>
18
18
  (whitespace? >> colon >> whitespace? >> ternary.as(:right)).maybe
19
- ).as(:ternary) | range
19
+ ).as(:ternary) | defined
20
20
  end
21
21
 
22
22
  root(:ternary)
data/lib/code-ruby.rb CHANGED
@@ -3,9 +3,11 @@ require "zeitwerk"
3
3
  require "bigdecimal"
4
4
  require "active_support"
5
5
  require "active_support/core_ext/object/blank"
6
+ require "active_support/core_ext/object/deep_dup"
6
7
  require "stringio"
7
8
  require "timeout"
8
9
 
9
10
  loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
10
- loader.ignore(__FILE__)
11
+ loader.ignore("#{__dir__}/template-ruby.rb")
12
+ loader.ignore("#{__dir__}/code-ruby.rb")
11
13
  loader.setup
data/lib/code.rb CHANGED
@@ -1,29 +1,30 @@
1
1
  class Code
2
- def initialize(input, io: $stdout, timeout: 10)
2
+ DEFAULT_TIMEOUT = Template::DEFAULT_TIMEOUT
3
+
4
+ def initialize(input, io: $stdout, timeout: DEFAULT_TIMEOUT)
3
5
  @input = input
4
- @parsed =
5
- Timeout.timeout(timeout) { ::Code::Parser::Code.new.parse(@input) }
6
+ @parsed = Timeout.timeout(timeout) { ::Code::Parser::Code.new.parse(@input) }
6
7
  @io = io
7
- @timeout = timeout
8
+ @timeout = timeout || DEFAULT_TIMEOUT
8
9
  end
9
10
 
10
- def self.evaluate(input, context = "", io: $stdout, timeout: 10)
11
+ def self.evaluate(input, context = "", io: $stdout, timeout: DEFAULT_TIMEOUT)
11
12
  new(input, io: io, timeout: timeout).evaluate(context)
12
13
  end
13
14
 
14
15
  def evaluate(context = "")
15
- Timeout.timeout(@timeout) do
16
+ Timeout.timeout(timeout) do
16
17
  if context.present?
17
- context = ::Code.evaluate(context, timeout: @timeout)
18
+ context = ::Code.evaluate(context, timeout: timeout)
18
19
  else
19
20
  context = ::Code::Object::Dictionnary.new
20
21
  end
21
22
 
22
- ::Code::Node::Code.new(parsed).evaluate(context: context, io: @io)
23
+ ::Code::Node::Code.new(parsed).evaluate(context: context, io: io)
23
24
  end
24
25
  end
25
26
 
26
27
  private
27
28
 
28
- attr_reader :input, :parsed
29
+ attr_reader :input, :parsed, :timeout, :io
29
30
  end
@@ -1,3 +1,3 @@
1
1
  require_relative "../template"
2
2
 
3
- Template::Version = Gem::Version.new("0.2.4")
3
+ Template::Version = Gem::Version.new("0.3.1")
data/lib/template-ruby.rb CHANGED
@@ -3,9 +3,11 @@ require "zeitwerk"
3
3
  require "bigdecimal"
4
4
  require "active_support"
5
5
  require "active_support/core_ext/object/blank"
6
+ require "active_support/core_ext/object/deep_dup"
6
7
  require "stringio"
7
8
  require "timeout"
8
9
 
9
10
  loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
10
- loader.ignore(__FILE__)
11
+ loader.ignore("#{__dir__}/template-ruby.rb")
12
+ loader.ignore("#{__dir__}/code-ruby.rb")
11
13
  loader.setup
data/lib/template.rb CHANGED
@@ -1,34 +1,40 @@
1
1
  class Template
2
- def initialize(input, io: StringIO.new, timeout: 10)
2
+ DEFAULT_TIMEOUT = 10
3
+
4
+ def initialize(input, io: StringIO.new, timeout: DEFAULT_TIMEOUT)
3
5
  @input = input
4
6
  @parsed =
5
7
  Timeout.timeout(timeout) do
6
8
  ::Template::Parser::Template.new.parse(@input)
7
9
  end
8
10
  @io = io
9
- @timeout = timeout
11
+ @timeout = timeout || DEFAULT_TIMEOUT
10
12
  end
11
13
 
12
- def self.render(input, context = "", io: StringIO.new, timeout: 10)
14
+ def self.render(input, context = "", io: StringIO.new, timeout: DEFAULT_TIMEOUT)
13
15
  new(input, io: io, timeout: timeout).render(context)
14
16
  end
15
17
 
16
18
  def render(context = "")
17
- Timeout.timeout(@timeout) do
19
+ Timeout.timeout(timeout) do
18
20
  if context.present?
19
- context = ::Code.evaluate(context, timeout: @timeout)
21
+ context = ::Code.evaluate(context, timeout: timeout)
20
22
  else
21
23
  context = ::Code::Object::Dictionnary.new
22
24
  end
23
25
 
24
- ::Template::Node::Template.new(@parsed).evaluate(
26
+ ::Template::Node::Template.new(parsed).evaluate(
25
27
  context: context,
26
- io: @io,
28
+ io: io,
27
29
  )
28
30
 
29
- @io.is_a?(StringIO) ? @io.string : nil
31
+ io.is_a?(StringIO) ? io.string : nil
30
32
  end
31
33
  end
34
+
35
+ private
36
+
37
+ attr_reader :parsed, :io, :timeout
32
38
  end
33
39
 
34
40
  require_relative "template/version"
@@ -14,7 +14,6 @@ RSpec.describe ::Code::Error::TypeError do
14
14
  '1 / ""',
15
15
  '1 ** ""',
16
16
  '1 % ""',
17
- '1 + ""',
18
17
  '1 - ""',
19
18
  '1 << ""',
20
19
  '1 >> ""',
@@ -48,7 +47,6 @@ RSpec.describe ::Code::Error::TypeError do
48
47
  '1.0 ** ""',
49
48
  '1.0 * ""',
50
49
  '1.0 % ""',
51
- '1.0 + ""',
52
50
  '1.0 - ""',
53
51
  '1.0 > ""',
54
52
  '1.0 >= ""',
@@ -4,42 +4,15 @@ RSpec.describe Code::Parser::Dictionnary do
4
4
  subject { described_class.new.parse(input) }
5
5
 
6
6
  [
7
- [
8
- '{name: "Dorian"}',
9
- {
10
- dictionnary: [{ key: { name: "name" }, value: [{ string: "Dorian" }] }],
11
- },
12
- ],
13
- [
14
- '{a: true, "b": false}',
15
- {
16
- dictionnary: [
17
- { key: { name: "a" }, value: [{ boolean: "true" }] },
18
- { key: { string: "b" }, value: [{ boolean: "false" }] },
19
- ],
20
- },
21
- ],
22
- [
23
- "{ true => 1, false => 2}",
24
- {
25
- dictionnary: [
26
- {
27
- key: [{ boolean: "true" }],
28
- value: [{ number: { base_10: { integer: { whole: "1" } } } }],
29
- },
30
- {
31
- key: [{ boolean: "false" }],
32
- value: [{ number: { base_10: { integer: { whole: "2" } } } }],
33
- },
34
- ],
35
- },
36
- ],
37
- ].each do |(input, expected)|
7
+ '{name: "Dorian"}',
8
+ '{a: true, "b": false}',
9
+ "{ true => 1, false => 2}",
10
+ ].each do |input|
38
11
  context input.inspect do
39
12
  let(:input) { input }
40
13
 
41
14
  it "succeeds" do
42
- expect(subject).to eq(expected)
15
+ expect { subject }.to_not raise_error
43
16
  end
44
17
  end
45
18
  end
@@ -4,26 +4,25 @@ RSpec.describe Code::Parser::String do
4
4
  subject { described_class.new.parse(input) }
5
5
 
6
6
  [
7
- ["'hello'", { string: "hello" }],
8
- ['"hello"', { string: "hello" }],
9
- ["''", { string: "" }],
10
- ['""', { string: "" }],
11
- ["'\\''", { string: "'" }],
12
- ['"\\t"', { string: "\\t" }],
13
- ["'\\r'", { string: "\\r" }],
14
- ['"\\b\\f\\n\\r\\t"', { string: "\\b\\f\\n\\r\\t" }],
15
- ['"\\uABCG"', { string: "uABCG" }],
16
- [
17
- "'\\u0123\\u4567\\u89aA\\ubBcC\\UdDeE\\ufFfF'",
18
- { string: "\\u0123\\u4567\\u89aA\\ubBcC\\UdDeE\\ufFfF" },
19
- ],
20
- [":asc", { string: "asc" }],
21
- ].each do |(input, expected)|
7
+ "'hello'",
8
+ '"hello"',
9
+ "''",
10
+ '""',
11
+ "'\\''",
12
+ '"\\t"',
13
+ "'\\r'",
14
+ '"\\b\\f\\n\\r\\t"',
15
+ '"\\uABCG"',
16
+ "'\\u0123\\u4567\\u89aA\\ubBcC\\UdDeE\\ufFfF'",
17
+ ":asc",
18
+ "'1 + 1 = {1 + 1}'",
19
+ "'a + b = {'{'a'}{'b'}'}'"
20
+ ].each do |input|
22
21
  context input.inspect do
23
22
  let(:input) { input }
24
23
 
25
24
  it "succeeds" do
26
- expect(subject).to eq(expected)
25
+ expect { subject }.to_not raise_error
27
26
  end
28
27
  end
29
28
  end
data/spec/code_spec.rb CHANGED
@@ -96,6 +96,18 @@ RSpec.describe Code do
96
96
  ["until true\nend", ""],
97
97
  %w[("Good".."Bad").first Good],
98
98
  ['"Dorian " * 2', "Dorian Dorian "],
99
+ ["while false end == nothing", "true"],
100
+ ['"1 + 1 = {1 + 1}"', "1 + 1 = 2"],
101
+ ["'1 + 1 = {1 + 1}'", "1 + 1 = 2"],
102
+ ["{}.to_string + [].to_string", "{}[]"],
103
+ ["'a' + 1", "a1"],
104
+ ["'a' + 1.0", "a1.0"],
105
+ ["1 + 'a'", "1a"],
106
+ ["1.0 + 'a'", "1.0a"],
107
+ ["1 << 1", "2"],
108
+ ["1.0 << 1", "2"],
109
+ ["1 << 1.0", "2"],
110
+ ["1.0 << 1.0", "2"],
99
111
  ].each do |(input, expected)|
100
112
  context input.inspect do
101
113
  let(:input) { input }
@@ -12,7 +12,13 @@ RSpec.describe Template do
12
12
  '{ user: { first_name: "Dorian" } }',
13
13
  "Hello Dorian",
14
14
  ],
15
+ [
16
+ "{add(1, 2)",
17
+ 'add = (a, b) => { a + b } { add: context(:add) }',
18
+ "3",
19
+ ],
15
20
  ["Hello {", "", "Hello "],
21
+ ["{{a: 1}.each { |k, v| print(k) } nothing", "", "a"],
16
22
  ["", "", ""],
17
23
  ].each do |(input, input_context, expected)|
18
24
  context "#{input.inspect} #{input_context.inspect}" do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: code-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dorian Marié
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-09-02 00:00:00.000000000 Z
11
+ date: 2022-10-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -96,7 +96,7 @@ files:
96
96
  - Gemfile.lock
97
97
  - LICENSE
98
98
  - README.md
99
- - TODO.md
99
+ - bin/code
100
100
  - bin/template
101
101
  - code-ruby.gemspec
102
102
  - docs/euler/1.template
@@ -104,6 +104,7 @@ files:
104
104
  - docs/euler/3.template
105
105
  - docs/euler/4.template
106
106
  - docs/euler/5.template
107
+ - docs/fibonnaci.template
107
108
  - docs/precedence.template
108
109
  - lib/code-ruby.rb
109
110
  - lib/code.rb
@@ -147,6 +148,9 @@ files:
147
148
  - lib/code/node/rescue.rb
148
149
  - lib/code/node/statement.rb
149
150
  - lib/code/node/string.rb
151
+ - lib/code/node/string_characters.rb
152
+ - lib/code/node/string_component.rb
153
+ - lib/code/node/string_interpolation.rb
150
154
  - lib/code/node/ternary.rb
151
155
  - lib/code/node/unary_minus.rb
152
156
  - lib/code/node/while.rb
@@ -243,7 +247,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
243
247
  - !ruby/object:Gem::Version
244
248
  version: '0'
245
249
  requirements: []
246
- rubygems_version: 3.3.18
250
+ rubygems_version: 3.3.7
247
251
  signing_key:
248
252
  specification_version: 4
249
253
  summary: A programming language
data/TODO.md DELETED
@@ -1 +0,0 @@
1
- - Code gem