code-ruby 3.1.2 → 4.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.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/bin/code +97 -20
  4. data/lib/code/concerns/shared.rb +331 -15
  5. data/lib/code/format.rb +15 -1
  6. data/lib/code/network.rb +87 -0
  7. data/lib/code/node/call.rb +79 -2
  8. data/lib/code/node/call_argument.rb +14 -0
  9. data/lib/code/node/code.rb +5 -4
  10. data/lib/code/node/function_parameter.rb +7 -4
  11. data/lib/code/node/list.rb +29 -1
  12. data/lib/code/object/base_64.rb +132 -6
  13. data/lib/code/object/boolean.rb +60 -0
  14. data/lib/code/object/class.rb +138 -2
  15. data/lib/code/object/code.rb +111 -3
  16. data/lib/code/object/context.rb +57 -1
  17. data/lib/code/object/cryptography.rb +63 -0
  18. data/lib/code/object/date.rb +13339 -462
  19. data/lib/code/object/decimal.rb +1725 -0
  20. data/lib/code/object/dictionary.rb +1790 -11
  21. data/lib/code/object/duration.rb +28 -0
  22. data/lib/code/object/function.rb +261 -23
  23. data/lib/code/object/global.rb +534 -1
  24. data/lib/code/object/html.rb +179 -7
  25. data/lib/code/object/http.rb +244 -14
  26. data/lib/code/object/ics.rb +75 -13
  27. data/lib/code/object/identifier_list.rb +17 -2
  28. data/lib/code/object/integer.rb +1937 -2
  29. data/lib/code/object/json.rb +75 -1
  30. data/lib/code/object/list.rb +3383 -10
  31. data/lib/code/object/nothing.rb +53 -0
  32. data/lib/code/object/number.rb +110 -0
  33. data/lib/code/object/parameter.rb +140 -0
  34. data/lib/code/object/range.rb +576 -14
  35. data/lib/code/object/smtp.rb +95 -12
  36. data/lib/code/object/string.rb +944 -3
  37. data/lib/code/object/super.rb +10 -1
  38. data/lib/code/object/time.rb +13358 -498
  39. data/lib/code/object/url.rb +65 -0
  40. data/lib/code/object.rb +543 -0
  41. data/lib/code/parser.rb +161 -24
  42. data/lib/code-ruby.rb +3 -0
  43. data/lib/code.rb +30 -3
  44. metadata +135 -84
  45. data/.github/dependabot.yml +0 -15
  46. data/.github/workflows/ci.yml +0 -38
  47. data/.gitignore +0 -30
  48. data/.node-version +0 -1
  49. data/.npm-version +0 -1
  50. data/.prettierignore +0 -2
  51. data/.rspec +0 -1
  52. data/.rubocop.yml +0 -140
  53. data/.ruby-version +0 -1
  54. data/.tool-versions +0 -3
  55. data/AGENTS.md +0 -43
  56. data/Gemfile +0 -22
  57. data/Gemfile.lock +0 -292
  58. data/Rakefile +0 -5
  59. data/bin/bundle +0 -123
  60. data/bin/bundle-audit +0 -31
  61. data/bin/bundler-audit +0 -31
  62. data/bin/dorian +0 -31
  63. data/bin/rspec +0 -31
  64. data/bin/rubocop +0 -31
  65. data/bin/test +0 -5
  66. data/code-ruby.gemspec +0 -34
  67. data/docs/precedence.txt +0 -36
  68. data/package-lock.json +0 -14
  69. data/package.json +0 -7
  70. data/spec/bin/code_spec.rb +0 -48
  71. data/spec/code/format_spec.rb +0 -153
  72. data/spec/code/node/call_spec.rb +0 -11
  73. data/spec/code/object/boolean_spec.rb +0 -18
  74. data/spec/code/object/cryptography_spec.rb +0 -25
  75. data/spec/code/object/decimal_spec.rb +0 -50
  76. data/spec/code/object/dictionary_spec.rb +0 -98
  77. data/spec/code/object/function_spec.rb +0 -268
  78. data/spec/code/object/http_spec.rb +0 -33
  79. data/spec/code/object/ics_spec.rb +0 -50
  80. data/spec/code/object/integer_spec.rb +0 -42
  81. data/spec/code/object/list_spec.rb +0 -22
  82. data/spec/code/object/nothing_spec.rb +0 -14
  83. data/spec/code/object/range_spec.rb +0 -23
  84. data/spec/code/object/string_spec.rb +0 -26
  85. data/spec/code/parser/boolean_spec.rb +0 -11
  86. data/spec/code/parser/chained_call_spec.rb +0 -16
  87. data/spec/code/parser/dictionary_spec.rb +0 -18
  88. data/spec/code/parser/function_spec.rb +0 -16
  89. data/spec/code/parser/group_spec.rb +0 -11
  90. data/spec/code/parser/if_modifier_spec.rb +0 -18
  91. data/spec/code/parser/list_spec.rb +0 -17
  92. data/spec/code/parser/number_spec.rb +0 -11
  93. data/spec/code/parser/string_spec.rb +0 -20
  94. data/spec/code/parser_spec.rb +0 -52
  95. data/spec/code/type_spec.rb +0 -21
  96. data/spec/code_spec.rb +0 -717
  97. data/spec/spec_helper.rb +0 -21
  98. data/spec/zeitwerk/loader_spec.rb +0 -7
@@ -3,6 +3,34 @@
3
3
  class Code
4
4
  class Object
5
5
  class Duration < Object
6
+ CLASS_DOCUMENTATION = {
7
+ name: "Duration",
8
+ description: "stores a length of time for shifting dates and times.",
9
+ examples: [
10
+ "1.day",
11
+ "2.hours",
12
+ "Duration.new(\"P1D\")"
13
+ ]
14
+ }.freeze
15
+ INSTANCE_FUNCTIONS = {
16
+ "ago" => {
17
+ name: "ago",
18
+ description: "returns the current time minus this duration.",
19
+ examples: ["1.day.ago", "2.hours.ago", "30.minutes.ago"]
20
+ },
21
+ "from_now" => {
22
+ name: "from_now",
23
+ description: "returns the current time plus this duration.",
24
+ examples: ["1.day.from_now", "2.hours.from_now", "30.minutes.from_now"]
25
+ }
26
+ }.freeze
27
+
28
+ def self.function_documentation(scope)
29
+ return INSTANCE_FUNCTIONS if scope == :instance
30
+
31
+ {}
32
+ end
33
+
6
34
  def initialize(*args, **_kargs, &_block)
7
35
  self.raw =
8
36
  if args.first.is_an?(::ActiveSupport::Duration)
@@ -3,9 +3,83 @@
3
3
  class Code
4
4
  class Object
5
5
  class Function < Object
6
- attr_reader :code_parameters, :code_body, :definition_context, :parent
6
+ CLASS_DOCUMENTATION = {
7
+ name: "Function",
8
+ description: "stores a callable block with parameters, captured context, and documentation.",
9
+ examples: [
10
+ "Function.new",
11
+ "((value) => { value + 1 }).call(1)",
12
+ "((value) => { value }).parameters.length"
13
+ ]
14
+ }.freeze
15
+ INSTANCE_FUNCTIONS = {
16
+ "call" => {
17
+ name: "call",
18
+ description: "evaluates the function body with the provided arguments.",
19
+ examples: [
20
+ "((value) => { value + 1 }).call(1)",
21
+ "((name:) => { name }).call(name: :ada)",
22
+ "((a, b = 2) => { a + b }).call(3)"
23
+ ]
24
+ },
25
+ "extend" => {
26
+ name: "extend",
27
+ description: "creates a child function that can call the receiver through super.",
28
+ examples: [
29
+ "Base = () => { self.name = :base } Child = Base.extend(() => { super() }) Child().name",
30
+ "Base = () => { 1 } Child = Base.extend(() => { super() }) Child()",
31
+ "Base = (name) => { self.name = name } Child = Base.extend((...) => { super(...) }) Child(:a).name"
32
+ ]
33
+ },
34
+ "documentation" => {
35
+ name: "documentation",
36
+ description: "returns or replaces the function documentation dictionary.",
37
+ examples: [
38
+ "f = () => { 1 } f.documentation",
39
+ "f = () => { 1 } f.documentation(description: \"returns one\", examples: [\"f()\"])",
40
+ "f = () => { 1 } f.documentation(description: \"returns one\", examples: [\"f()\"]).description"
41
+ ]
42
+ },
43
+ "documentation=" => {
44
+ name: "documentation=",
45
+ description: "replaces the function documentation dictionary.",
46
+ examples: [
47
+ "f = () => { 1 } f.documentation = { description: \"returns one\", examples: [\"f()\"] }",
48
+ "f = () => { 1 } f.documentation = { name: \"one\", description: \"returns one\", examples: [\"one()\"] } f.documentation.name",
49
+ "f = () => { 1 } f.documentation = { description: \"returns one\", examples: [\"f()\"] } f.documentation.examples.first"
50
+ ]
51
+ },
52
+ "parameters" => {
53
+ name: "parameters",
54
+ description: "returns a list of parameter descriptors for the function.",
55
+ examples: [
56
+ "((name) => { name }).parameters.first.name",
57
+ "((name = :a) => { name }).parameters.first.default",
58
+ "((...values) => { values }).parameters.first.spread?"
59
+ ]
60
+ },
61
+ "to_string" => {
62
+ name: "to_string",
63
+ description: "formats the function as source code.",
64
+ examples: [
65
+ "(() => { 1 }).to_string",
66
+ "((value) => { value + 1 }).to_string",
67
+ "((name:) => { name }).to_string"
68
+ ]
69
+ }
70
+ }.freeze
71
+
72
+ def self.function_documentation(scope)
73
+ return INSTANCE_FUNCTIONS if scope == :instance
74
+
75
+ {}
76
+ end
77
+
78
+ attr_accessor :documentation
79
+ attr_reader :code_parameters, :code_body, :definition_context,
80
+ :instance_functions, :parent
7
81
 
8
- def initialize(*args, parent: nil, methods: nil, **_kargs, &_block)
82
+ def initialize(*args, parent: nil, functions: nil, **_kargs, &_block)
9
83
  @code_parameters =
10
84
  List
11
85
  .new(args.first)
@@ -16,8 +90,14 @@ class Code
16
90
  @code_body = Code.new(args.second.presence)
17
91
  @definition_context = args.third if args.third.is_a?(Context)
18
92
  @parent = parent.to_code
19
- self.methods = methods.to_code
20
- self.methods = Dictionary.new if self.methods.nothing?
93
+ self.functions = functions.to_code
94
+ self.functions = Dictionary.new if self.functions.nothing?
95
+ @instance_functions = Dictionary.new
96
+ self.documentation = Dictionary.new(
97
+ "name" => String.new(""),
98
+ "description" => String.new(""),
99
+ "examples" => List.new
100
+ )
21
101
 
22
102
  self.raw = List.new([code_parameters, code_body])
23
103
  end
@@ -25,6 +105,7 @@ class Code
25
105
  def call(**args)
26
106
  code_operator = args.fetch(:operator, nil).to_code
27
107
  code_arguments = args.fetch(:arguments, List.new).to_code
108
+ code_value = code_arguments.code_first
28
109
  globals = multi_fetch(args, *GLOBALS)
29
110
 
30
111
  case code_operator.to_s
@@ -34,11 +115,27 @@ class Code
34
115
  *code_arguments.raw,
35
116
  explicit_arguments: args.fetch(:explicit_arguments, true),
36
117
  bound_self: args.fetch(:bound_self, nil),
118
+ constructing_literal_classes:
119
+ args.fetch(:constructing_literal_classes, nil),
37
120
  **globals
38
121
  )
39
122
  when "extend"
40
123
  sig(args) { Function }
41
124
  code_extend(code_arguments.code_first)
125
+ when "documentation"
126
+ if code_arguments.any?
127
+ sig(args) { Dictionary }
128
+ self.documentation = code_value.code_to_dictionary
129
+ else
130
+ sig(args)
131
+ documentation
132
+ end
133
+ when "documentation="
134
+ sig(args) { Dictionary }
135
+ self.documentation = code_value.code_to_dictionary
136
+ when "parameters"
137
+ sig(args)
138
+ code_parameters
42
139
  when /=$/
43
140
  sig(args) { Object }
44
141
  code_set(code_operator.to_s.chop, code_value)
@@ -60,18 +157,22 @@ class Code
60
157
  *arguments,
61
158
  explicit_arguments: true,
62
159
  bound_self: nil,
160
+ constructing_literal_classes: nil,
63
161
  **globals
64
162
  )
65
163
  code_arguments = arguments.to_code
66
164
  code_context = Context.new({}, definition_context || globals[:context])
67
165
  code_self = bound_self.to_code
166
+ if (code_self.nil? || code_self.nothing?) && parent.is_a?(Class)
167
+ code_self = parent.code_call(*arguments, **globals)
168
+ end
68
169
  code_self = Dictionary.new if code_self.nil? || code_self.nothing?
69
170
  code_parent = captured_self.to_code
70
171
 
71
172
  code_context.code_set("self", code_self)
72
173
  bind_parent(code_context, code_self, code_parent)
73
174
 
74
- if parent.is_a?(Function)
175
+ if parent.is_a?(Function) || parent.is_a?(Class)
75
176
  code_context.code_set(
76
177
  "super",
77
178
  Super.new(
@@ -84,19 +185,38 @@ class Code
84
185
  )
85
186
  end
86
187
 
188
+ captures_function_arguments =
189
+ code_parameters.raw.any? { |parameter| parameter.block? || parameter.blocks? }
190
+ reserved_function_arguments =
191
+ captures_function_arguments ? code_arguments.raw.grep(Function) : []
192
+ code_block_argument =
193
+ if code_parameters.raw.any?(&:block?)
194
+ code_arguments.raw.detect { |argument| argument.is_a?(Function) }
195
+ end
196
+
87
197
  code_parameters.raw.each.with_index do |code_parameter, index|
88
198
  code_argument =
89
- if code_parameter.spread? || code_parameter.regular_splat?
199
+ if code_parameter.spread?
90
200
  code_arguments
201
+ elsif code_parameter.regular_splat?
202
+ Object::List.new(
203
+ code_arguments.raw.reject do |argument|
204
+ argument.is_a?(Dictionary) ||
205
+ reserved_function_arguments.include?(argument)
206
+ end
207
+ )
91
208
  elsif code_parameter.keyword_splat?
92
- code_arguments.raw.detect do |code_argument|
93
- code_argument.is_a?(Dictionary)
94
- end || Dictionary.new
209
+ code_arguments.raw.grep(Dictionary).inject(Dictionary.new) do |memo, argument|
210
+ memo.code_merge!(argument)
211
+ end
95
212
  elsif code_parameter.block?
96
- code_arguments
97
- .raw
98
- .detect { |code_argument| code_argument.is_a?(Function) }
99
- .to_code
213
+ code_block_argument.to_code
214
+ elsif code_parameter.blocks?
215
+ Object::List.new(
216
+ code_arguments.raw.grep(Function).reject do |argument|
217
+ argument == code_block_argument
218
+ end
219
+ )
100
220
  elsif code_parameter.keyword?
101
221
  code_arguments
102
222
  .raw
@@ -118,18 +238,28 @@ class Code
118
238
  if code_default.is_a?(Code)
119
239
  code_default.code_evaluate(
120
240
  **globals,
121
- context: code_context
241
+ context: code_context,
242
+ trusted_evaluation: true
122
243
  )
123
244
  else
124
245
  code_default
125
246
  end
126
247
  end
127
248
 
128
- code_context.code_set(code_parameter.code_name, code_argument)
249
+ code_name = code_parameter.code_name
250
+ code_context.code_set(code_name, code_argument) unless code_name.blank?
129
251
  end
130
252
 
131
- code_body.code_evaluate(**globals, context: code_context)
253
+ code_body.code_evaluate(
254
+ **globals,
255
+ constructing_literal_classes: constructing_literal_classes,
256
+ context: code_context,
257
+ trusted_evaluation: true
258
+ ).tap do
259
+ persist_instance_functions(code_self)
260
+ end
132
261
  rescue Error::Return => e
262
+ persist_instance_functions(code_self)
133
263
  e.code_value
134
264
  end
135
265
 
@@ -139,8 +269,10 @@ class Code
139
269
  .inject([]) do |signature, code_parameter|
140
270
  if code_parameter.spread? || code_parameter.regular_splat?
141
271
  signature + [Object.repeat]
272
+ elsif code_parameter.blocks?
273
+ signature + [Function.repeat]
142
274
  elsif code_parameter.block?
143
- signature + [Function]
275
+ signature + [Function.maybe]
144
276
  elsif code_parameter.keyword_splat?
145
277
  signature + [Dictionary.maybe]
146
278
  elsif code_parameter.keyword? && code_parameter.required?
@@ -191,28 +323,130 @@ class Code
191
323
  code_function.code_body.raw,
192
324
  code_function.definition_context,
193
325
  parent: self,
194
- methods: methods.code_deep_duplicate
326
+ functions: functions.code_deep_duplicate
327
+ ).tap do |extended_function|
328
+ extended_function.instance_functions.code_merge!(instance_functions)
329
+ end
330
+ end
331
+
332
+ def code_deep_duplicate(seen = {})
333
+ seen.compare_by_identity unless seen.compare_by_identity?
334
+ return seen[self] if seen.key?(self)
335
+
336
+ duplicate = Function.new
337
+ seen[self] = duplicate
338
+
339
+ duplicate.code_replace(
340
+ code_parameters: code_parameters.code_deep_duplicate(seen),
341
+ code_body: code_body.code_deep_duplicate(seen),
342
+ definition_context: definition_context&.code_deep_duplicate(seen),
343
+ parent: parent.is_a?(Function) ? parent.code_deep_duplicate(seen) : parent,
344
+ functions: functions.code_deep_duplicate(seen),
345
+ instance_functions: instance_functions.code_deep_duplicate(seen),
346
+ documentation: documentation.code_deep_duplicate(seen)
195
347
  )
348
+ duplicate
349
+ end
350
+
351
+ def code_replace(
352
+ code_parameters:,
353
+ code_body:,
354
+ definition_context:,
355
+ parent:,
356
+ functions:,
357
+ instance_functions:,
358
+ documentation:
359
+ )
360
+ @code_parameters = code_parameters
361
+ @code_body = code_body
362
+ @definition_context = definition_context
363
+ @parent = parent
364
+ self.functions = functions
365
+ @instance_functions = instance_functions
366
+ self.documentation = documentation
367
+ self.raw = List.new([code_parameters, code_body])
368
+ self
369
+ end
370
+
371
+ def code_functions
372
+ code_class_functions
373
+ end
374
+
375
+ def code_instance_functions
376
+ parent_functions =
377
+ if parent.is_a?(Function)
378
+ parent.code_instance_functions
379
+ else
380
+ Dictionary.new
381
+ end
382
+
383
+ Object.sorted_dictionary(
384
+ parent_functions.code_merge(function_dictionary_for(instance_functions)).raw
385
+ )
386
+ end
387
+
388
+ def code_class_functions
389
+ function_dictionary_for(functions)
196
390
  end
197
391
 
198
392
  def code_fetch(key)
199
- methods.code_fetch(key)
393
+ functions.code_fetch(key)
200
394
  end
201
395
 
202
396
  def code_get(key)
203
- methods.code_get(key)
397
+ functions.code_get(key)
204
398
  end
205
399
 
206
400
  def code_has_key?(key)
207
- methods.code_has_key?(key)
401
+ functions.code_has_key?(key)
208
402
  end
209
403
 
210
404
  def code_set(key, value)
211
- methods.code_set(key, value)
405
+ functions.code_set(key, value)
212
406
  end
213
407
 
214
408
  private
215
409
 
410
+ def function_dictionary_for(dictionary)
411
+ Object.sorted_dictionary(
412
+ dictionary.raw.to_h do |key, value|
413
+ name = key.to_s
414
+ [
415
+ name,
416
+ Dictionary.new(
417
+ "name" => String.new(name),
418
+ "description" => String.new(function_description(value)),
419
+ "examples" => List.new(function_examples(value))
420
+ )
421
+ ]
422
+ end
423
+ )
424
+ end
425
+
426
+ def function_description(value)
427
+ code_value = value.to_code
428
+ return "" unless code_value.is_a?(Function)
429
+
430
+ code_value.documentation.code_get("description").to_s
431
+ end
432
+
433
+ def function_examples(value)
434
+ code_value = value.to_code
435
+ return [] unless code_value.is_a?(Function)
436
+
437
+ code_value.documentation.code_get("examples").to_code.code_to_list.raw
438
+ end
439
+
440
+ def persist_instance_functions(code_self)
441
+ return unless code_self.is_a?(Object)
442
+
443
+ instance_functions.code_merge!(documentable_instance_functions(code_self))
444
+ end
445
+
446
+ def documentable_instance_functions(code_self)
447
+ code_self.code_documentable_functions
448
+ end
449
+
216
450
  def bind_parent(code_context, code_self, code_parent)
217
451
  return if code_parent.nothing?
218
452
 
@@ -247,7 +481,9 @@ class Code
247
481
 
248
482
  def parameter_to_raw(parameter)
249
483
  code_parameter = parameter.to_code
250
- raw_parameter = { name: code_parameter.code_name.to_s }
484
+ raw_parameter = {}
485
+ code_name = code_parameter.code_name
486
+ raw_parameter[:name] = code_name.to_s unless code_name.blank?
251
487
 
252
488
  if code_parameter.keyword?
253
489
  raw_parameter[:keyword] = ":"
@@ -257,6 +493,8 @@ class Code
257
493
  raw_parameter[:regular_splat] = "*"
258
494
  elsif code_parameter.block?
259
495
  raw_parameter[:block] = "&"
496
+ elsif code_parameter.blocks?
497
+ raw_parameter[:blocks] = "&&"
260
498
  elsif code_parameter.spread?
261
499
  raw_parameter[:spread] = "..."
262
500
  end