code-ruby 1.9.12 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a8c532c56c64f318cc116ef97c0bc587edde1f306d5f8e53d75be893bf38b1f
4
- data.tar.gz: ffc94431ea3528b547ab9b76e03d4efea7f9e2281a30da04e7528ea881d2d308
3
+ metadata.gz: f52de5a641a1d879b4ac472d5f291a1c101a42797362019d6a2c3854e6bf90a2
4
+ data.tar.gz: 32645dbb24c34523d7848852f59a2de98786de33b8f1782ad514c8e8a317ebb7
5
5
  SHA512:
6
- metadata.gz: 468c2777dab28cabbff2e918d5ee3a9a5a417063be54ad44026d20a096d0f234c6ee7fb633c17d707dbaf565eb61614ea4d6d67bc3888aa8895d9df092c37a50
7
- data.tar.gz: 1f29b8a374565925fcbdf636bf7a1d60f45a5d5a23ba4bf24550c4f2964f695ac4691d0ea60d25825bd5e487c18e05feb14eab0b2df37c1c0c1f054c7d012b4b
6
+ metadata.gz: 1dc8fe275e70f5fdc948f082408ae64cb1ab8aaaa73d1f73fb9b8c9eccb180f987dc2d10d7ace3a94ecb85678a9c8ccd95b3c7737c95cd349d6ff3cdb35872a5
7
+ data.tar.gz: 6317f857f1c7984212ca971650dda29e5d9b6fff92d677c4b6c069041cbfda0f92a2bb78d1345c5f11e5eb51d0f9a9d2285bfb60423ef0dd94fb21344e9bec12
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- code-ruby (1.9.12)
4
+ code-ruby (2.0.0)
5
5
  activesupport
6
6
  base64
7
7
  bigdecimal
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.9.12
1
+ 2.0.0
@@ -13,8 +13,8 @@ class Code
13
13
  @body = Code.new(parsed.delete(:body).presence)
14
14
  end
15
15
 
16
- def evaluate(**_args)
17
- Object::Function.new(@parameters, @body)
16
+ def evaluate(**args)
17
+ Object::Function.new(@parameters, @body, args.fetch(:context))
18
18
  end
19
19
  end
20
20
 
@@ -22,7 +22,9 @@ class Code
22
22
  return if parsed.blank?
23
23
 
24
24
  @name = parsed.delete(:name).presence
25
+ @explicit_arguments = parsed.key?(:arguments)
25
26
  @arguments = parsed.delete(:arguments).presence || []
27
+ @arguments = [@arguments] unless @arguments.is_a?(Array)
26
28
  @arguments.map! { |argument| CallArgument.new(argument) }
27
29
 
28
30
  return unless parsed.key?(:block)
@@ -52,6 +54,7 @@ class Code
52
54
  args.fetch(:object).call(
53
55
  operator: name,
54
56
  arguments: Object::List.new(arguments),
57
+ explicit_arguments: @explicit_arguments,
55
58
  **args
56
59
  )
57
60
  end
@@ -12,8 +12,8 @@ class Code
12
12
  @body = Code.new(parsed.delete(:body).presence)
13
13
  end
14
14
 
15
- def evaluate(**_args)
16
- Object::Function.new(@parameters, @body)
15
+ def evaluate(**args)
16
+ Object::Function.new(@parameters, @body, args.fetch(:context))
17
17
  end
18
18
  end
19
19
  end
@@ -41,6 +41,10 @@ class Code
41
41
  right = @right&.evaluate(**args) || Object::Nothing.new
42
42
  left = @left&.resolve(**args) || Object::Nothing.new
43
43
 
44
+ if @operator != EQUAL && right.nothing?
45
+ return @left&.evaluate(**args) || Object::Nothing.new
46
+ end
47
+
44
48
  left.call(
45
49
  operator: @operator,
46
50
  arguments: Object::List.new([right]),
@@ -542,7 +542,7 @@ class Code
542
542
  result = code_fetch(code_operator)
543
543
 
544
544
  if result.is_a?(Function)
545
- result.call(**args, operator: nil)
545
+ result.call(**args, operator: nil, bound_self: self)
546
546
  else
547
547
  sig(args)
548
548
  result
@@ -3,9 +3,9 @@
3
3
  class Code
4
4
  class Object
5
5
  class Function < Object
6
- attr_reader :code_parameters, :code_body
6
+ attr_reader :code_parameters, :code_body, :definition_context, :parent
7
7
 
8
- def initialize(*args, **_kargs, &_block)
8
+ def initialize(*args, parent: nil, methods: nil, **_kargs, &_block)
9
9
  @code_parameters =
10
10
  List
11
11
  .new(args.first)
@@ -14,6 +14,10 @@ class Code
14
14
  .to_code
15
15
 
16
16
  @code_body = Code.new(args.second.presence)
17
+ @definition_context = args.third if args.third.is_a?(Context)
18
+ @parent = parent.to_code
19
+ self.methods = methods.to_code
20
+ self.methods = Dictionary.new if self.methods.nothing?
17
21
 
18
22
  self.raw = List.new([code_parameters, code_body])
19
23
  end
@@ -26,15 +30,52 @@ class Code
26
30
  case code_operator.to_s
27
31
  when "", "call"
28
32
  sig(args) { signature_for_call }
29
- code_call(*code_arguments.raw, **globals)
33
+ code_call(
34
+ *code_arguments.raw,
35
+ explicit_arguments: args.fetch(:explicit_arguments, true),
36
+ **globals
37
+ )
38
+ when "extend"
39
+ sig(args) { Function }
40
+ code_extend(code_arguments.code_first)
41
+ when /=$/
42
+ sig(args) { Object }
43
+ code_set(code_operator.to_s.chop, code_value)
44
+ when ->(operator) { code_has_key?(operator).truthy? }
45
+ result = code_fetch(code_operator)
46
+
47
+ if result.is_a?(Function)
48
+ result.call(**args, operator: nil, bound_self: self)
49
+ else
50
+ sig(args)
51
+ result
52
+ end
30
53
  else
31
54
  super
32
55
  end
33
56
  end
34
57
 
35
- def code_call(*arguments, **globals)
58
+ def code_call(*arguments, explicit_arguments: true, bound_self: nil, **globals)
36
59
  code_arguments = arguments.to_code
37
- code_context = Context.new({}, globals[:context])
60
+ code_context = Context.new({}, definition_context || globals[:context])
61
+ code_self = bound_self.to_code
62
+ code_self = captured_self if code_self.nothing? && captured_self
63
+ code_self = Dictionary.new if code_self.nil? || code_self.nothing?
64
+
65
+ code_context.code_set("self", code_self)
66
+
67
+ if parent.is_a?(Function)
68
+ code_context.code_set(
69
+ "super",
70
+ Super.new(
71
+ parent,
72
+ code_arguments,
73
+ code_self,
74
+ definition_context || globals[:context],
75
+ explicit_arguments: explicit_arguments
76
+ )
77
+ )
78
+ end
38
79
 
39
80
  code_parameters.raw.each.with_index do |code_parameter, index|
40
81
  code_argument =
@@ -113,6 +154,54 @@ class Code
113
154
  def code_to_string
114
155
  String.new("<#{self.class.name} #{raw}>")
115
156
  end
157
+
158
+ def code_extend(function)
159
+ code_function = function.to_code
160
+
161
+ Function.new(
162
+ code_function.code_parameters,
163
+ code_function.code_body.raw,
164
+ code_function.definition_context,
165
+ parent: self,
166
+ methods: methods.code_deep_duplicate
167
+ )
168
+ end
169
+
170
+ def code_fetch(key)
171
+ methods.code_fetch(key)
172
+ end
173
+
174
+ def code_get(key)
175
+ methods.code_get(key)
176
+ end
177
+
178
+ def code_has_key?(key)
179
+ methods.code_has_key?(key)
180
+ end
181
+
182
+ def code_set(key, value)
183
+ methods.code_set(key, value)
184
+ end
185
+
186
+ private
187
+
188
+ def captured_self
189
+ self_from(definition_context)
190
+ end
191
+
192
+ def self_from(context)
193
+ return if context.blank?
194
+
195
+ current = context
196
+
197
+ while current
198
+ return current.code_fetch("self") if current.code_has_key?("self").truthy?
199
+
200
+ current = current.parent
201
+ end
202
+
203
+ nil
204
+ end
116
205
  end
117
206
  end
118
207
  end
@@ -199,7 +199,10 @@ class Code
199
199
  code_context = code_context.code_lookup!(code_operator)
200
200
  code_result = code_context.code_fetch(code_operator)
201
201
 
202
- if code_result.is_a?(Function)
202
+ if code_result.is_a?(Super)
203
+ code_result.call(**args, operator: nil)
204
+ elsif code_result.is_a?(Function) &&
205
+ args.fetch(:explicit_arguments, false)
203
206
  code_result.call(**args, operator: nil)
204
207
  else
205
208
  sig(args)
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Code
4
+ class Object
5
+ class Super < Function
6
+ def initialize(
7
+ parent,
8
+ forwarded_arguments,
9
+ code_self,
10
+ definition_context,
11
+ explicit_arguments: false
12
+ )
13
+ @parent = parent.to_code
14
+ @forwarded_arguments = forwarded_arguments.to_code
15
+ @code_self = code_self.to_code
16
+ @definition_context = definition_context
17
+ self.methods = Dictionary.new
18
+ self.raw = @parent.raw
19
+ end
20
+
21
+ def call(**args)
22
+ code_arguments = args.fetch(:arguments, List.new).to_code
23
+ explicit_arguments = args.fetch(:explicit_arguments, false)
24
+
25
+ @parent.code_call(
26
+ *(arguments_for(code_arguments, explicit_arguments).raw),
27
+ explicit_arguments: explicit_arguments,
28
+ bound_self: @code_self,
29
+ **multi_fetch(args, *GLOBALS).merge(context: parent_context)
30
+ )
31
+ end
32
+
33
+ private
34
+
35
+ def parent_context
36
+ context = Context.new({}, @definition_context)
37
+ context.code_set("self", @code_self)
38
+ context
39
+ end
40
+
41
+ def arguments_for(code_arguments, explicit_arguments)
42
+ if explicit_arguments && code_arguments.raw.empty?
43
+ List.new
44
+ elsif code_arguments.any?
45
+ code_arguments
46
+ else
47
+ @forwarded_arguments
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -151,7 +151,8 @@ class Code
151
151
 
152
152
  def root
153
153
  (
154
- name.aka(:name) << (whitespace? << arguments.aka(:arguments)).maybe <<
154
+ name.aka(:name) <<
155
+ (whiltespace_without_newline? << arguments.aka(:arguments)).maybe <<
155
156
  (whiltespace_without_newline? << block.aka(:block)).maybe
156
157
  ).aka(:call)
157
158
  end
@@ -28,7 +28,7 @@ RSpec.describe Code::Object::Function do
28
28
 
29
29
  context "invalid" do
30
30
  [
31
- "f = (x) => {} f",
31
+ "f = (x) => {} f()",
32
32
  "f = (x:) => {} f(1)",
33
33
  "f = (x:) => {} f(y: 1)"
34
34
  ].each do |input|
@@ -37,4 +37,129 @@ RSpec.describe Code::Object::Function do
37
37
  end
38
38
  end
39
39
  end
40
+
41
+ it "captures self for constructor-like functions that return self" do
42
+ result =
43
+ Code.evaluate(<<~CODE)
44
+ User = (given_name:, family_name:, birth_date:) => {
45
+ self.given_name = given_name.to_string.presence
46
+ self.family_name = family_name.to_string.presence
47
+ self.birth_date = birth_date.presence&.to_date
48
+ self.full_name = () => {
49
+ [self.given_name, self.family_name].compact.join(" ")
50
+ }
51
+ self.age = () => {
52
+ return unless(self.birth_date)
53
+
54
+ self.birth_date.year
55
+ }
56
+
57
+ return(self)
58
+ }
59
+
60
+ user = User(given_name: "Dorian", family_name: "Marie", birth_date: "1992-08-11")
61
+ [user.given_name, user.family_name, user.full_name, user.age]
62
+ CODE
63
+
64
+ expect(result).to eq(
65
+ Code::Object::List.new(
66
+ [
67
+ Code::Object::String.new("Dorian"),
68
+ Code::Object::String.new("Marie"),
69
+ Code::Object::String.new("Dorian Marie"),
70
+ Code::Object::Integer.new(1992)
71
+ ]
72
+ )
73
+ )
74
+ end
75
+
76
+ it "supports constructor methods on functions" do
77
+ result =
78
+ Code.evaluate(<<~CODE)
79
+ User = (given_name:, family_name:) => {
80
+ self.given_name = given_name
81
+ self.family_name = family_name
82
+ self.full_name = () => {
83
+ [self.given_name, self.family_name].join(" ")
84
+ }
85
+ return(self)
86
+ }
87
+
88
+ User.all = () => {
89
+ [
90
+ User(given_name: "Dorian", family_name: "Marie"),
91
+ User(given_name: "Ada", family_name: "Lovelace")
92
+ ]
93
+ }
94
+
95
+ User.first = () => {
96
+ User.all.first
97
+ }
98
+
99
+ User.first.full_name
100
+ CODE
101
+
102
+ expect(result).to eq(Code::Object::String.new("Dorian Marie"))
103
+ end
104
+
105
+ it "supports extending constructors and forwarding super arguments" do
106
+ result =
107
+ Code.evaluate(<<~CODE)
108
+ Person = (given_name:, family_name:) => {
109
+ self.given_name = given_name
110
+ self.family_name = family_name
111
+ self.full_name = () => {
112
+ [self.given_name, self.family_name].join(" ")
113
+ }
114
+ return(self)
115
+ }
116
+
117
+ Employee = Person.extend((employee_id:, given_name:, family_name:) => {
118
+ super
119
+ self.employee_id = employee_id
120
+ return(self)
121
+ })
122
+
123
+ employee = Employee(employee_id: "EMP-001", given_name: "Dorian", family_name: "Marie")
124
+ [employee.employee_id, employee.full_name]
125
+ CODE
126
+
127
+ expect(result).to eq(
128
+ Code::Object::List.new(
129
+ [
130
+ Code::Object::String.new("EMP-001"),
131
+ Code::Object::String.new("Dorian Marie")
132
+ ]
133
+ )
134
+ )
135
+ end
136
+
137
+ it "distinguishes super from super()" do
138
+ result =
139
+ Code.evaluate(<<~CODE)
140
+ Person = (given_name:, family_name:) => {
141
+ self.full_name = () => {
142
+ [given_name, family_name].join(" ")
143
+ }
144
+ return(self)
145
+ }
146
+
147
+ Anonymous = Person.extend(() => {
148
+ super()
149
+ self.full_name = () => { "anonymous" }
150
+ return(self)
151
+ })
152
+
153
+ [Anonymous().full_name, Person(given_name: "Ada", family_name: "Lovelace").full_name]
154
+ CODE
155
+
156
+ expect(result).to eq(
157
+ Code::Object::List.new(
158
+ [
159
+ Code::Object::String.new("anonymous"),
160
+ Code::Object::String.new("Ada Lovelace")
161
+ ]
162
+ )
163
+ )
164
+ end
40
165
  end
data/spec/code_spec.rb CHANGED
@@ -345,6 +345,8 @@ RSpec.describe Code do
345
345
  ["a = 1 a += 1 a", "2"],
346
346
  ["a = 1 a += 1 a", "2"],
347
347
  ["a = 1 a -= 1 a", "0"],
348
+ ["a = 3 a -= 1 if false a", "3"],
349
+ ["a = 3\n(a -= 1) if false\na", "3"],
348
350
  ["a = 1 a /= 2 a", "0.5"],
349
351
  ["a = 1 a <<= 1 a", "2"],
350
352
  ["a = 1 a >>= 1 a", "0"],
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: code-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.12
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dorian Marié
@@ -285,6 +285,7 @@ files:
285
285
  - lib/code/object/range.rb
286
286
  - lib/code/object/smtp.rb
287
287
  - lib/code/object/string.rb
288
+ - lib/code/object/super.rb
288
289
  - lib/code/object/time.rb
289
290
  - lib/code/object/url.rb
290
291
  - lib/code/parser.rb