template-ruby 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rspec.yml +14 -0
  3. data/CHANGELOG.md +20 -0
  4. data/Gemfile +2 -1
  5. data/Gemfile.lock +6 -1
  6. data/LICENSE +7 -0
  7. data/README.md +98 -5
  8. data/bin/code +51 -0
  9. data/bin/template +9 -5
  10. data/code-ruby.gemspec +22 -0
  11. data/docs/euler/4.template +0 -1
  12. data/docs/fibonnaci.template +14 -0
  13. data/docs/precedence.template +6 -31
  14. data/lib/code/node/code.rb +7 -1
  15. data/lib/code/node/list.rb +7 -7
  16. data/lib/code/node/name.rb +6 -1
  17. data/lib/code/node/string.rb +11 -6
  18. data/lib/code/node/string_characters.rb +13 -0
  19. data/lib/code/node/string_component.rb +23 -0
  20. data/lib/code/node/string_interpolation.rb +13 -0
  21. data/lib/code/object/argument.rb +1 -1
  22. data/lib/code/object/decimal.rb +22 -1
  23. data/lib/code/object/dictionnary.rb +24 -0
  24. data/lib/code/object/function.rb +2 -1
  25. data/lib/code/object/integer.rb +18 -20
  26. data/lib/code/object/list.rb +4 -0
  27. data/lib/code/object/string.rb +10 -2
  28. data/lib/code/object.rb +7 -0
  29. data/lib/code/parser/and_operator.rb +4 -4
  30. data/lib/code/parser/call.rb +3 -3
  31. data/lib/code/parser/code.rb +3 -4
  32. data/lib/code/parser/defined.rb +2 -2
  33. data/lib/code/parser/dictionnary.rb +1 -1
  34. data/lib/code/parser/equality.rb +4 -4
  35. data/lib/code/parser/function.rb +3 -2
  36. data/lib/code/parser/group.rb +1 -1
  37. data/lib/code/parser/if.rb +3 -3
  38. data/lib/code/parser/list.rb +1 -1
  39. data/lib/code/parser/not_keyword.rb +2 -2
  40. data/lib/code/parser/statement.rb +1 -1
  41. data/lib/code/parser/string.rb +19 -4
  42. data/lib/code/parser/ternary.rb +3 -3
  43. data/lib/code-ruby.rb +12 -0
  44. data/lib/code.rb +10 -10
  45. data/lib/template/version.rb +3 -0
  46. data/lib/template-ruby.rb +3 -1
  47. data/lib/template.rb +21 -11
  48. data/spec/code/error/type_error_spec.rb +0 -2
  49. data/spec/code/parser/dictionnary_spec.rb +5 -32
  50. data/spec/code/parser/string_spec.rb +15 -16
  51. data/spec/code_spec.rb +13 -0
  52. data/spec/template_spec.rb +6 -0
  53. data/template-ruby.gemspec +3 -2
  54. metadata +15 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d5e2a863316056d011b034cc0c819984d710899e8b51657dd15636e45db058b0
4
- data.tar.gz: 47974cad2e705fb70706b68a496523f414b3027c6f57ec6d22344c588cdce143
3
+ metadata.gz: 0d53d39478e69a97de7680a29e4a47634804ed0bb82badf8ec255ef70ca32968
4
+ data.tar.gz: 431593c814bf56bcecc3f28e6564b6e3799e674183fdf5b298b4e913b8604d43
5
5
  SHA512:
6
- metadata.gz: 1ba52300910fb32458e115c200842c3a2ef18a19e8fddee92c38a961d60637d3c3c73906304184ef0ec5771ad85cc5dd19d8dbe0803a872a2c5c0dd69cc82a16
7
- data.tar.gz: d526bbd95df4375aee06d4c9cc8bb8c6f2927b725514527dd9d3e29229223235bf0c8870d917eb61ae30cbce52e2c27a5f18ba31a2b1fcc94b0dfc574bf77bd1
6
+ metadata.gz: b11d8a9daf795dfd5c90eaed1246f85951b76c417e7b4775fe55997b88255a290bd17ed0427c7d7c4d927b20b537cc5353e8a91f76bc2d442527415b209a812b
7
+ data.tar.gz: 8266b19ec1b8d73a35490264ec593ef8c6609229335d99c30bc196b7719455791f240f08ea56890a40d4f0e0a6dea1973a4043ff40f3308f0b96d4ef07e61f28
@@ -0,0 +1,14 @@
1
+ name: RSpec
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ name: RSpec
8
+ runs-on: ubuntu-latest
9
+
10
+ steps:
11
+ - uses: actions/checkout@v1
12
+ - uses: actions/setup-ruby@v1
13
+ - run: bundle install
14
+ - run: bundle exec rspec
data/CHANGELOG.md CHANGED
@@ -5,6 +5,26 @@ 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
+
23
+ ## 0.2.4 / 2022-08-02
24
+
25
+ - Add method `String#*`, e.g. `{"Dorian " \* 2}" -> "Dorian Dorian "
26
+ - Add executable to gem, e.g. `template --help`
27
+
8
28
  ## 0.2.3 / 2022-08-31
9
29
 
10
30
  - Add default timeout for code and template parsing and evaluation
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.2)
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/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2022 Dorian Marié <dorian@dorianmarie.fr>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,10 +1,103 @@
1
- # `template-ruby`
1
+ # Template
2
2
 
3
- A templating programming language
3
+ [![RSpec](https://github.com/dorianmariefr/template-ruby/actions/workflows/rspec.yml/badge.svg)](https://github.com/dorianmariefr/template-ruby/actions/workflows/rspec.yml)
4
4
 
5
- Like `Hello {name}` with `{name: "Dorian"}` gives `Hello Dorian`.
5
+ See [templatelang.com](https://templatelang.com) for the full documentation and
6
+ live code editing.
7
+
8
+ ## The programming language
9
+
10
+ Hi, I'm [Dorian Marié](https://dorianmarie.fr), I created Template to let users of my websites provide templates to customize their experience.
11
+
12
+ Template is meant to be:
13
+
14
+ - **Simple**: `Hello` and `Hello {name}`
15
+ - **Safe**: Can be provided user input
16
+ - **Powerful**: Functions, object-oriented, built-in methods
17
+
18
+ Template is currently written in Ruby and embeddable as a Ruby gem.
19
+
20
+ ## Install
21
+
22
+ ### As a command line tool:
23
+
24
+ ```bash
25
+ $ gem install template-ruby
26
+ $ template --input "Hello {name}" --context '{ name: "Dorian" }'
27
+ Hello Dorian
28
+ $ template --input "1 + 2 = {1 + 2}"
29
+ 1 + 2 = 3
30
+ ```
31
+
32
+ ### As a Ruby gem:
33
+
34
+ In a `Gemfile`:
6
35
 
7
36
  ```ruby
8
- > Template.render("Hello {name}", '{name: "Dorian"}')
9
- => "Hello Dorian"
37
+ gem "template-ruby"
10
38
  ```
39
+
40
+ Then `$ bundle install`
41
+
42
+ Then you can use Template like:
43
+
44
+ ```ruby
45
+ Template.render("Hello {name}", '{ name: "Dorian" }')
46
+ # => "Hello Dorian"
47
+ Template.render("1 + 2 = {1 + 2}")
48
+ # => "1 + 2 = 3"
49
+ Template.render(input, context, io: StringIO.new, timeout: 10)
50
+ ```
51
+
52
+ The context is a sub-language called Code, you can use it like:
53
+
54
+ ```ruby
55
+ Code.evaluate("1 + 2") # => 3
56
+ ```
57
+
58
+ ## Future work
59
+
60
+ - Extend standard library
61
+ - Global methods from Ruby, e.g. `{markdown "**bold**"}`
62
+ - Object methods from Ruby, e.g. `{"**bold**".markdown}`
63
+ - Classes, e.g. `{class User end}`
64
+ - Write JavaScript version
65
+ - Write Crystal version
66
+
67
+ ## Contributing
68
+
69
+ Feel free to open [issues](https://github.com/dorianmariefr/template-ruby/issues),
70
+ and [pull requests](https://github.com/dorianmariefr/template-ruby/pulls).
71
+
72
+ To develop locally:
73
+
74
+ ```text
75
+ $ git clone https://github.com/dorianmariefr/template-ruby
76
+ $ cd template-ruby
77
+ $ bundle
78
+ $ rspec
79
+ $ bin/template -i docs/...
80
+ ```
81
+
82
+ ## Credits
83
+
84
+ Thanks to [thoughtbot](https://thoughtbot.com) who let me work on this programming
85
+ language as a Friday project.
86
+
87
+ Thanks to [Kaspar Schiess](https://github.com/kschiess) who made
88
+ [Parslet](https://kschiess.github.io/parslet/), the gem that helped me write the parser.
89
+
90
+ Inspiration from [Ruby](https://www.ruby-lang.org/en/) and
91
+ [Liquid](https://shopify.github.io/liquid/).
92
+
93
+ ## License
94
+
95
+ MIT
96
+
97
+ Copyright 2022 Dorian Marié <dorian@dorianmarie.fr>
98
+
99
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
100
+
101
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
102
+
103
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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,12 +3,12 @@
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
- opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
9
+ opts.banner = "Usage: template [options]"
10
10
 
11
- opts.on("-i INPUT", "--input=INPUT", "Input in the template language") do |input|
11
+ opts.on("-i INPUT", "--input=INPUT", "Input in the template language (String or File)") do |input|
12
12
  if File.exists?(input)
13
13
  input = File.read(input)
14
14
  end
@@ -16,7 +16,7 @@ OptionParser.new do |opts|
16
16
  options[:input] = input
17
17
  end
18
18
 
19
- opts.on("-c CONTEXT", "--context=CONTEXT", "Context in the code language") do |context|
19
+ opts.on("-c CONTEXT", "--context=CONTEXT", "Context in the code language (String or File)") do |context|
20
20
  if File.exists?(context)
21
21
  context = File.read(context)
22
22
  end
@@ -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/code-ruby.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ require_relative "lib/template/version"
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "code-ruby"
5
+ s.version = ::Template::Version
6
+ s.summary = "A programming language"
7
+ s.description = 'A programming language, like Code.evaluate("1 + 1") # => 2'
8
+ s.authors = ["Dorian Marié"]
9
+ s.email = "dorian@dorianmarie.fr"
10
+ s.files = `git ls-files`.split($/)
11
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
12
+ s.require_paths = ["lib"]
13
+ s.homepage = "https://github.com/dorianmariefr/template-ruby"
14
+ s.license = "MIT"
15
+
16
+ s.add_dependency "activesupport", "~> 7"
17
+ s.add_dependency "parslet", "~> 2"
18
+ s.add_dependency "zeitwerk", "~> 2.6"
19
+
20
+ s.add_development_dependency "prettier", "~> 3"
21
+ s.add_development_dependency "rspec", "~> 3"
22
+ 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?