code-ruby 3.1.2 → 4.0.1

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