code-ruby 0.2.4 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/Gemfile +2 -1
- data/Gemfile.lock +6 -1
- data/bin/code +51 -0
- data/bin/template +6 -2
- data/docs/euler/4.template +0 -1
- data/docs/fibonnaci.template +14 -0
- data/docs/precedence.template +6 -31
- data/lib/code/node/code.rb +7 -1
- data/lib/code/node/list.rb +7 -7
- data/lib/code/node/name.rb +6 -1
- data/lib/code/node/string.rb +11 -6
- data/lib/code/node/string_characters.rb +13 -0
- data/lib/code/node/string_component.rb +23 -0
- data/lib/code/node/string_interpolation.rb +13 -0
- data/lib/code/object/argument.rb +1 -1
- data/lib/code/object/decimal.rb +22 -1
- data/lib/code/object/dictionnary.rb +24 -0
- data/lib/code/object/function.rb +2 -1
- data/lib/code/object/integer.rb +18 -20
- data/lib/code/object/list.rb +4 -0
- data/lib/code/object/string.rb +2 -2
- data/lib/code/object.rb +7 -0
- data/lib/code/parser/and_operator.rb +4 -4
- data/lib/code/parser/call.rb +3 -3
- data/lib/code/parser/code.rb +3 -4
- data/lib/code/parser/defined.rb +2 -2
- data/lib/code/parser/dictionnary.rb +1 -1
- data/lib/code/parser/equality.rb +4 -4
- data/lib/code/parser/function.rb +3 -2
- data/lib/code/parser/group.rb +1 -1
- data/lib/code/parser/if.rb +3 -3
- data/lib/code/parser/list.rb +1 -1
- data/lib/code/parser/not_keyword.rb +2 -2
- data/lib/code/parser/statement.rb +1 -1
- data/lib/code/parser/string.rb +19 -4
- data/lib/code/parser/ternary.rb +3 -3
- data/lib/code-ruby.rb +2 -1
- data/lib/code.rb +10 -9
- data/lib/template/version.rb +1 -1
- data/lib/template-ruby.rb +3 -1
- data/lib/template.rb +14 -8
- data/spec/code/error/type_error_spec.rb +0 -2
- data/spec/code/parser/dictionnary_spec.rb +5 -32
- data/spec/code/parser/string_spec.rb +15 -16
- data/spec/code_spec.rb +12 -0
- data/spec/template_spec.rb +6 -0
- metadata +8 -4
- data/TODO.md +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4ad087a2b12a2284ef01385799e9f9c4b299a6946891994887579908856a0ebb
|
4
|
+
data.tar.gz: 414b3a15535b0503c3739a801edfd3e68db9fa6ee9b0023fc049aa981e7ce412
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5e24c277e43e5693003d076f8abafdafb42662c3b9b06b0f52173d3a052ef57e8f810f13ed4e6809da0b1f45eedc60c62f643a567ca8138f7bd454f6709f392b
|
7
|
+
data.tar.gz: 605ba84886638a0d98ab959dd2f95842e402498d08a0a148ae88d85753c263e3d29645c9b9f6f830271e8c7ee60dff126f441a160d7467035f7b4e85b1b23b46
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,21 @@ 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.0 / ?
|
9
|
+
|
10
|
+
- `bin/template` accepts `--timeout` (or `-t`) parameter
|
11
|
+
- Adds `bin/code` with same options as `bin/template`
|
12
|
+
- Prevent loose syntax like `{ a: }`, `[1,,,]` and `()`
|
13
|
+
- Change precedence of defined? (to allow `defined?(name) ? name : nothing`)
|
14
|
+
- Updates parsers to allow `while false end == nothing`
|
15
|
+
- String interpolations like `"1 + 1 = {1 + 1}"`
|
16
|
+
- `context(:name)` to get a function without calling it for instance
|
17
|
+
- `.to_string` on all objects
|
18
|
+
- `1 + "a"` and `"a" + 1.0` for instance now convert to strings
|
19
|
+
- `Dictionnary#each` e.g. `{ a: 1 }.each { |k, v| print(k) }`
|
20
|
+
- Fix context duplication issue that was preventing implementation of recursive
|
21
|
+
functions like Fibonacci
|
22
|
+
|
8
23
|
## 0.2.4 / 2022-08-02
|
9
24
|
|
10
25
|
- Add method `String#*`, e.g. `{"Dorian " \* 2}" -> "Dorian Dorian "
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
|
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
|
data/docs/euler/4.template
CHANGED
data/docs/precedence.template
CHANGED
@@ -2,58 +2,33 @@
|
|
2
2
|
- {a = "Go" a += "od" a}
|
3
3
|
- Statement
|
4
4
|
- {a = "Good"}
|
5
|
-
|
6
|
-
|
7
|
-
while a < 10
|
8
|
-
a += 1
|
9
|
-
"Good"
|
10
|
-
end
|
11
|
-
}
|
5
|
+
- Equality
|
6
|
+
- {1 == 1 ? "Good"}
|
12
7
|
- While
|
13
|
-
- {
|
14
|
-
a
|
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
|
data/lib/code/node/code.rb
CHANGED
@@ -9,7 +9,13 @@ class Code
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def evaluate(**args)
|
12
|
-
|
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
|
data/lib/code/node/list.rb
CHANGED
@@ -2,13 +2,13 @@ class Code
|
|
2
2
|
class Node
|
3
3
|
class List < Node
|
4
4
|
def initialize(codes)
|
5
|
-
|
6
|
-
codes
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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)
|
data/lib/code/node/name.rb
CHANGED
@@ -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
|
data/lib/code/node/string.rb
CHANGED
@@ -2,16 +2,21 @@ class Code
|
|
2
2
|
class Node
|
3
3
|
class String < Node
|
4
4
|
def initialize(string)
|
5
|
-
|
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
|
-
|
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,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
|
data/lib/code/object/argument.rb
CHANGED
data/lib/code/object/decimal.rb
CHANGED
@@ -19,10 +19,14 @@ class Code
|
|
19
19
|
operator = args.fetch(:operator, nil)
|
20
20
|
arguments = args.fetch(:arguments, [])
|
21
21
|
|
22
|
-
if %w[% -
|
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
|
data/lib/code/object/function.rb
CHANGED
data/lib/code/object/integer.rb
CHANGED
@@ -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
|
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::
|
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)
|
data/lib/code/object/list.rb
CHANGED
data/lib/code/object/string.rb
CHANGED
@@ -48,9 +48,9 @@ class Code
|
|
48
48
|
end
|
49
49
|
|
50
50
|
def plus(arguments)
|
51
|
-
sig(arguments, ::Code::Object
|
51
|
+
sig(arguments, ::Code::Object)
|
52
52
|
other = arguments.first.value
|
53
|
-
::Code::Object::String.new(raw + other.
|
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(:
|
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
|
-
|
17
|
+
greater_than.as(:first) >>
|
18
18
|
(
|
19
19
|
whitespace? >> operator.as(:operator) >> whitespace? >>
|
20
|
-
|
20
|
+
greater_than.as(:statement)
|
21
21
|
).repeat(1).as(:rest)
|
22
|
-
).as(:and_operator) |
|
22
|
+
).as(:and_operator) | greater_than
|
23
23
|
end
|
24
24
|
|
25
25
|
root(:and_operator)
|
data/lib/code/parser/call.rb
CHANGED
@@ -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
|
data/lib/code/parser/code.rb
CHANGED
@@ -8,11 +8,10 @@ class Code
|
|
8
8
|
rule(:whitespace) { (space | newline).repeat(1) }
|
9
9
|
rule(:whitespace?) { whitespace.maybe }
|
10
10
|
|
11
|
-
rule(:
|
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
|
data/lib/code/parser/defined.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
class Code
|
2
2
|
class Parser
|
3
3
|
class Defined < Parslet::Parser
|
4
|
-
rule(:
|
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) |
|
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("{") }
|
data/lib/code/parser/equality.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
class Code
|
2
2
|
class Parser
|
3
3
|
class Equality < Parslet::Parser
|
4
|
-
rule(:
|
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
|
-
|
25
|
+
while_parser.as(:first) >>
|
26
26
|
(
|
27
27
|
whitespace? >> operator.as(:operator) >> whitespace? >>
|
28
|
-
|
28
|
+
while_parser.as(:statement)
|
29
29
|
).repeat(1).as(:rest)
|
30
|
-
).as(:equality) |
|
30
|
+
).as(:equality) | while_parser
|
31
31
|
end
|
32
32
|
|
33
33
|
root(:equality)
|
data/lib/code/parser/function.rb
CHANGED
@@ -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 >>
|
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? >>
|
33
|
+
(whitespace? >> equal >> whitespace? >> code_present.as(:default)).maybe
|
33
34
|
end
|
34
35
|
|
35
36
|
rule(:argument) do
|
data/lib/code/parser/group.rb
CHANGED
@@ -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(")") }
|
data/lib/code/parser/if.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/code/parser/list.rb
CHANGED
@@ -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(:
|
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) |
|
15
|
+
(not_keyword >> whitespace >> not_rule).as(:not_keyword) | equal
|
16
16
|
end
|
17
17
|
|
18
18
|
root(:not_rule)
|
data/lib/code/parser/string.rb
CHANGED
@@ -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 >>
|
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 >>
|
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
|
|
data/lib/code/parser/ternary.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
class Code
|
2
2
|
class Parser
|
3
3
|
class Ternary < Parslet::Parser
|
4
|
-
rule(:
|
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
|
-
|
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) |
|
19
|
+
).as(:ternary) | defined
|
20
20
|
end
|
21
21
|
|
22
22
|
root(:ternary)
|
data/lib/code-ruby.rb
CHANGED
data/lib/code.rb
CHANGED
@@ -1,29 +1,30 @@
|
|
1
1
|
class Code
|
2
|
-
|
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:
|
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(
|
16
|
+
Timeout.timeout(timeout) do
|
16
17
|
if context.present?
|
17
|
-
context = ::Code.evaluate(context, 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:
|
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
|
data/lib/template/version.rb
CHANGED
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(
|
11
|
+
loader.ignore(File.expand_path("lib/template-ruby.rb"))
|
12
|
+
loader.ignore(File.expand_path("lib/code-ruby.rb"))
|
11
13
|
loader.setup
|
data/lib/template.rb
CHANGED
@@ -1,34 +1,40 @@
|
|
1
1
|
class Template
|
2
|
-
|
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:
|
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(
|
19
|
+
Timeout.timeout(timeout) do
|
18
20
|
if context.present?
|
19
|
-
context = ::Code.evaluate(context, 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(
|
26
|
+
::Template::Node::Template.new(parsed).evaluate(
|
25
27
|
context: context,
|
26
|
-
io:
|
28
|
+
io: io,
|
27
29
|
)
|
28
30
|
|
29
|
-
|
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
|
-
|
9
|
-
|
10
|
-
|
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
|
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
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
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 }
|
data/spec/template_spec.rb
CHANGED
@@ -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.
|
4
|
+
version: 0.3.0
|
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-
|
11
|
+
date: 2022-10-10 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
|
-
-
|
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.
|
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
|