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,515 @@
3
3
  class Code
4
4
  class Object
5
5
  class Global < Object
6
+ CLASS_DOCUMENTATION = {
7
+ name: "Global",
8
+ description: "dispatches top-level functions, constructors, and control flow helpers.",
9
+ examples: [
10
+ "globals.keys.include?(:puts)",
11
+ "classes.keys.include?(:List)",
12
+ "evaluate(\"1 + 2\")"
13
+ ]
14
+ }.freeze
15
+ GETTING_STARTED_EXAMPLES = [
16
+ {
17
+ name: "expression",
18
+ description: "evaluate an expression and print the result.",
19
+ source: "1 + 2",
20
+ expected: "3",
21
+ command: "bin/code '1 + 2'"
22
+ },
23
+ {
24
+ name: "list",
25
+ description: "transform every item in a list with a block.",
26
+ source: "[1, 2, 3].map { |number| number * 2 }",
27
+ expected: "[2, 4, 6]",
28
+ command: "bin/code '[1, 2, 3].map { |number| number * 2 }'"
29
+ },
30
+ {
31
+ name: "dictionary",
32
+ description: "store named values in a dictionary and read them by key.",
33
+ source: "user = { name: :Dorian, age: 31 } user.name",
34
+ expected: ":Dorian",
35
+ command: "bin/code 'user = { name: :Dorian, age: 31 } user.name'"
36
+ },
37
+ {
38
+ name: "function",
39
+ description: "define a function and call it with an argument.",
40
+ source: "double = (number) => { number * 2 } double(21)",
41
+ expected: "42",
42
+ command: "bin/code 'double = (number) => { number * 2 } double(21)'"
43
+ },
44
+ {
45
+ name: "documentation",
46
+ description: "query built-in documentation from the language.",
47
+ source: "globals.fetch(:puts).examples.first",
48
+ expected: "\"puts(:hello)\"",
49
+ command: "bin/code 'globals.fetch(:puts).examples.first'"
50
+ }
51
+ ].freeze
52
+ GETTING_STARTED = {
53
+ title: "Getting started with Code",
54
+ description: "a short guide with steps and runnable examples.",
55
+ steps: [
56
+ {
57
+ title: "Install dependencies",
58
+ action: "run bundle install from the repository root.",
59
+ command: "bundle install"
60
+ },
61
+ {
62
+ title: "Run one expression",
63
+ action: "pass Code source to bin/code and read the printed result.",
64
+ example: GETTING_STARTED_EXAMPLES.fetch(0)
65
+ },
66
+ {
67
+ title: "Format source",
68
+ action: "use -f before a snippet or file to print formatted Code.",
69
+ command: "bin/code -f '{a:1}'",
70
+ expected: "{ a: 1 }"
71
+ },
72
+ {
73
+ title: "Work with data",
74
+ action: "use lists, dictionaries, and functions together.",
75
+ examples: GETTING_STARTED_EXAMPLES[1, 3]
76
+ },
77
+ {
78
+ title: "Find built-in functions",
79
+ action: "use globals, functions, instance_functions, and class_functions from inside Code.",
80
+ example: GETTING_STARTED_EXAMPLES.fetch(4)
81
+ }
82
+ ],
83
+ examples: GETTING_STARTED_EXAMPLES,
84
+ next_steps: [
85
+ "run bin/code 'getting_started'",
86
+ "run bin/code 'globals.keys'",
87
+ "run bin/code 'List.instance_functions.keys'"
88
+ ]
89
+ }.freeze
90
+ FUNCTIONS = {
91
+ "Base64" => {
92
+ name: "Base64",
93
+ description: "returns the Base64 class when called without arguments, otherwise calls Base64.new.",
94
+ examples: [
95
+ "Base64",
96
+ "Base64.new",
97
+ "Base64.new(nothing)"
98
+ ]
99
+ },
100
+ "Boolean" => {
101
+ name: "Boolean",
102
+ description: "returns the Boolean class when called without arguments, otherwise calls Boolean.new.",
103
+ examples: [
104
+ "Boolean",
105
+ "Boolean.new",
106
+ "Boolean.new(nothing)"
107
+ ]
108
+ },
109
+ "Class" => {
110
+ name: "Class",
111
+ description: "returns the Class class when called without arguments, otherwise calls Class.new.",
112
+ examples: [
113
+ "Class",
114
+ "Class.new",
115
+ "Class.new(nothing)"
116
+ ]
117
+ },
118
+ "Code" => {
119
+ name: "Code",
120
+ description: "returns the Code class when called without arguments, otherwise calls Code.new.",
121
+ examples: [
122
+ "Code",
123
+ "Code.new",
124
+ "Code.new(nothing)"
125
+ ]
126
+ },
127
+ "Context" => {
128
+ name: "Context",
129
+ description: "returns the Context class when called without arguments, otherwise calls Context.new.",
130
+ examples: [
131
+ "Context",
132
+ "Context.new",
133
+ "Context.new(nothing)"
134
+ ]
135
+ },
136
+ "Cryptography" => {
137
+ name: "Cryptography",
138
+ description: "returns the Cryptography class when called without arguments, otherwise calls Cryptography.new.",
139
+ examples: [
140
+ "Cryptography",
141
+ "Cryptography.new",
142
+ "Cryptography.new(nothing)"
143
+ ]
144
+ },
145
+ "Date" => {
146
+ name: "Date",
147
+ description: "returns the Date class when called without arguments, otherwise calls Date.new.",
148
+ examples: [
149
+ "Date",
150
+ "Date.new",
151
+ "Date.new(nothing)"
152
+ ]
153
+ },
154
+ "Decimal" => {
155
+ name: "Decimal",
156
+ description: "returns the Decimal class when called without arguments, otherwise calls Decimal.new.",
157
+ examples: [
158
+ "Decimal",
159
+ "Decimal.new",
160
+ "Decimal.new(nothing)"
161
+ ]
162
+ },
163
+ "Dictionary" => {
164
+ name: "Dictionary",
165
+ description: "returns the Dictionary class when called without arguments, otherwise calls Dictionary.new.",
166
+ examples: [
167
+ "Dictionary",
168
+ "Dictionary.new",
169
+ "Dictionary.new(nothing)"
170
+ ]
171
+ },
172
+ "Duration" => {
173
+ name: "Duration",
174
+ description: "returns the Duration class when called without arguments, otherwise calls Duration.new.",
175
+ examples: [
176
+ "Duration",
177
+ "Duration.new",
178
+ "Duration.new(nothing)"
179
+ ]
180
+ },
181
+ "Function" => {
182
+ name: "Function",
183
+ description: "returns the Function class when called without arguments, otherwise calls Function.new.",
184
+ examples: [
185
+ "Function",
186
+ "Function.new",
187
+ "Function.new(nothing)"
188
+ ]
189
+ },
190
+ "Html" => {
191
+ name: "Html",
192
+ description: "returns the Html class when called without arguments, otherwise calls Html.new.",
193
+ examples: [
194
+ "Html",
195
+ "Html.new",
196
+ "Html.new(nothing)"
197
+ ]
198
+ },
199
+ "Http" => {
200
+ name: "Http",
201
+ description: "returns the Http class when called without arguments, otherwise calls Http.new.",
202
+ examples: [
203
+ "Http",
204
+ "Http.new",
205
+ "Http.new(nothing)"
206
+ ]
207
+ },
208
+ "Ics" => {
209
+ name: "Ics",
210
+ description: "returns the Ics class when called without arguments, otherwise calls Ics.new.",
211
+ examples: [
212
+ "Ics",
213
+ "Ics.new",
214
+ "Ics.new(nothing)"
215
+ ]
216
+ },
217
+ "IdentifierList" => {
218
+ name: "IdentifierList",
219
+ description: "returns the IdentifierList class when called without arguments, otherwise calls IdentifierList.new.",
220
+ examples: [
221
+ "IdentifierList",
222
+ "IdentifierList.new",
223
+ "IdentifierList.new(nothing)"
224
+ ]
225
+ },
226
+ "Integer" => {
227
+ name: "Integer",
228
+ description: "returns the Integer class when called without arguments, otherwise calls Integer.new.",
229
+ examples: [
230
+ "Integer",
231
+ "Integer.new",
232
+ "Integer.new(nothing)"
233
+ ]
234
+ },
235
+ "Json" => {
236
+ name: "Json",
237
+ description: "returns the Json class when called without arguments, otherwise calls Json.new.",
238
+ examples: [
239
+ "Json",
240
+ "Json.new",
241
+ "Json.new(nothing)"
242
+ ]
243
+ },
244
+ "List" => {
245
+ name: "List",
246
+ description: "returns the List class when called without arguments, otherwise calls List.new.",
247
+ examples: [
248
+ "List",
249
+ "List.new",
250
+ "List.new(nothing)"
251
+ ]
252
+ },
253
+ "Nothing" => {
254
+ name: "Nothing",
255
+ description: "returns the Nothing class when called without arguments, otherwise calls Nothing.new.",
256
+ examples: [
257
+ "Nothing",
258
+ "Nothing.new",
259
+ "Nothing.new(nothing)"
260
+ ]
261
+ },
262
+ "Number" => {
263
+ name: "Number",
264
+ description: "returns the Number class when called without arguments, otherwise calls Number.new.",
265
+ examples: [
266
+ "Number",
267
+ "Number.new",
268
+ "Number.new(nothing)"
269
+ ]
270
+ },
271
+ "Object" => {
272
+ name: "Object",
273
+ description: "returns the Object class when called without arguments, otherwise calls Object.new.",
274
+ examples: [
275
+ "Object",
276
+ "Object.new",
277
+ "Object.new(nothing)"
278
+ ]
279
+ },
280
+ "Parameter" => {
281
+ name: "Parameter",
282
+ description: "returns the Parameter class when called without arguments, otherwise calls Parameter.new.",
283
+ examples: [
284
+ "Parameter",
285
+ "Parameter.new",
286
+ "Parameter.new(nothing)"
287
+ ]
288
+ },
289
+ "Range" => {
290
+ name: "Range",
291
+ description: "returns the Range class when called without arguments, otherwise calls Range.new.",
292
+ examples: [
293
+ "Range",
294
+ "Range.new",
295
+ "Range.new(nothing)"
296
+ ]
297
+ },
298
+ "Smtp" => {
299
+ name: "Smtp",
300
+ description: "returns the Smtp class when called without arguments, otherwise calls Smtp.new.",
301
+ examples: [
302
+ "Smtp",
303
+ "Smtp.new",
304
+ "Smtp.new(nothing)"
305
+ ]
306
+ },
307
+ "String" => {
308
+ name: "String",
309
+ description: "returns the String class when called without arguments, otherwise calls String.new.",
310
+ examples: [
311
+ "String",
312
+ "String.new",
313
+ "String.new(nothing)"
314
+ ]
315
+ },
316
+ "Time" => {
317
+ name: "Time",
318
+ description: "returns the Time class when called without arguments, otherwise calls Time.new.",
319
+ examples: [
320
+ "Time",
321
+ "Time.new",
322
+ "Time.new(nothing)"
323
+ ]
324
+ },
325
+ "Url" => {
326
+ name: "Url",
327
+ description: "returns the Url class when called without arguments, otherwise calls Url.new.",
328
+ examples: [
329
+ "Url",
330
+ "Url.new",
331
+ "Url.new(nothing)"
332
+ ]
333
+ },
334
+ "classes" => {
335
+ name: "classes",
336
+ description: "returns documentation for built-in classes exposed by globals.",
337
+ examples: [
338
+ "classes",
339
+ "classes.keys.include?(:List)",
340
+ "classes.fetch(:String).description"
341
+ ]
342
+ },
343
+ "break" => {
344
+ name: "break",
345
+ description: "exits the current loop and returns an optional value.",
346
+ examples: [
347
+ "[1].each { break }",
348
+ "[1].each { break 1 }",
349
+ "while true { break :done }"
350
+ ]
351
+ },
352
+ "continue" => {
353
+ name: "continue",
354
+ description: "skips the rest of the current loop iteration.",
355
+ examples: [
356
+ "[1].each { continue }",
357
+ "[1, 2].each { |value| continue if value == 1 }",
358
+ "while false { continue }"
359
+ ]
360
+ },
361
+ "context" => {
362
+ name: "context",
363
+ description: "returns the current evaluation context dictionary.",
364
+ examples: [
365
+ "context",
366
+ "context.keys",
367
+ "context.has_key?(:context)"
368
+ ]
369
+ },
370
+ "evaluate" => {
371
+ name: "evaluate",
372
+ description: "evaluates a string as Code source and returns its result.",
373
+ examples: [
374
+ "evaluate(\"1 + 2\")",
375
+ "evaluate(\":hello\")",
376
+ "evaluate(\"[1, 2].size\")"
377
+ ]
378
+ },
379
+ "getting_started" => {
380
+ name: "getting_started",
381
+ description: "returns a short guide with runnable examples and commands.",
382
+ examples: [
383
+ "getting_started.title",
384
+ "getting_started.steps.first.command",
385
+ "getting_started.examples.map(&:name)"
386
+ ]
387
+ },
388
+ "globals" => {
389
+ name: "globals",
390
+ description: "returns documentation for top-level functions and constructors.",
391
+ examples: [
392
+ "globals",
393
+ "globals.keys.include?(:List)",
394
+ "globals.fetch(:puts).description"
395
+ ]
396
+ },
397
+ "next" => {
398
+ name: "next",
399
+ description: "exits the current block iteration and returns an optional value.",
400
+ examples: [
401
+ "[1].map { next }",
402
+ "[1].map { next 2 }",
403
+ "[1, 2].map { next it if it > 1 }"
404
+ ]
405
+ },
406
+ "p" => {
407
+ name: "p",
408
+ description: "writes inspected values to output and returns nothing.",
409
+ examples: [
410
+ "p(:hello)",
411
+ "p(1, 2)",
412
+ "p([1, 2])"
413
+ ]
414
+ },
415
+ "print" => {
416
+ name: "print",
417
+ description: "writes values to output without adding a newline.",
418
+ examples: [
419
+ "print(:hello)",
420
+ "print(1, 2)",
421
+ "print(\"hello\")"
422
+ ]
423
+ },
424
+ "puts" => {
425
+ name: "puts",
426
+ description: "writes values to output with newlines and returns nothing.",
427
+ examples: [
428
+ "puts(:hello)",
429
+ "puts(1, 2)",
430
+ "puts(\"hello\")"
431
+ ]
432
+ },
433
+ "read" => {
434
+ name: "read",
435
+ description: "reads one line from input.",
436
+ examples: [
437
+ "read",
438
+ "name = read",
439
+ "read.to_string"
440
+ ]
441
+ },
442
+ "redo" => {
443
+ name: "redo",
444
+ description: "retries the current loop iteration.",
445
+ examples: [
446
+ "[1].each { redo if false }",
447
+ "while false { redo }",
448
+ "[1, 2].each { |value| redo if value == 0 }"
449
+ ]
450
+ },
451
+ "retry" => {
452
+ name: "retry",
453
+ description: "retries the current loop iteration.",
454
+ examples: [
455
+ "[1].each { retry if false }",
456
+ "while false { retry }",
457
+ "[1, 2].each { |value| retry if value == 0 }"
458
+ ]
459
+ },
460
+ "return" => {
461
+ name: "return",
462
+ description: "exits the current function and returns an optional value.",
463
+ examples: [
464
+ "() => { return }",
465
+ "() => { return 1 }",
466
+ "f = () => { return :done } f()"
467
+ ]
468
+ }
469
+ }.freeze
470
+
471
+ def self.functions
472
+ Object.sorted_dictionary(FUNCTIONS)
473
+ end
474
+
475
+ def self.classes
476
+ Object.sorted_dictionary(
477
+ documented_classes.to_h do |name, klass|
478
+ [name, Object.documentation_for(klass)]
479
+ end
480
+ )
481
+ end
482
+
483
+ def self.documented_classes
484
+ {
485
+ "Base64" => Object::Base64,
486
+ "Boolean" => Boolean,
487
+ "Class" => Class,
488
+ "Code" => Code,
489
+ "Context" => Context,
490
+ "Cryptography" => Cryptography,
491
+ "Date" => Date,
492
+ "Decimal" => Decimal,
493
+ "Dictionary" => Dictionary,
494
+ "Duration" => Duration,
495
+ "Function" => Function,
496
+ "Html" => Html,
497
+ "Http" => Http,
498
+ "Ics" => Ics,
499
+ "IdentifierList" => IdentifierList,
500
+ "Integer" => Integer,
501
+ "Json" => Json,
502
+ "List" => List,
503
+ "Nothing" => Nothing,
504
+ "Number" => Number,
505
+ "Object" => Object,
506
+ "Parameter" => Parameter,
507
+ "Range" => Range,
508
+ "Smtp" => Smtp,
509
+ "String" => String,
510
+ "Time" => Time,
511
+ "Url" => Url
512
+ }
513
+ end
514
+
6
515
  def call(**args)
7
516
  code_operator = args.fetch(:operator, nil).to_code
8
517
  code_arguments = args.fetch(:arguments, []).to_code
@@ -12,6 +521,21 @@ class Code
12
521
  code_value = code_arguments.code_first
13
522
  globals = multi_fetch(args, *GLOBALS)
14
523
 
524
+ if code_context.code_has_key?(code_operator).truthy?
525
+ code_result = code_context.code_fetch(code_operator)
526
+
527
+ if code_result.is_a?(Super) ||
528
+ (
529
+ code_result.is_a?(Function) &&
530
+ args.fetch(:explicit_arguments, false)
531
+ )
532
+ return code_result.call(**args, operator: nil)
533
+ end
534
+
535
+ sig(args)
536
+ return code_result
537
+ end
538
+
15
539
  case code_operator.to_s
16
540
  when "Boolean"
17
541
  sig(args) { Object.repeat }
@@ -73,6 +597,12 @@ class Code
73
597
  else
74
598
  Class.new(Function)
75
599
  end
600
+ when "globals"
601
+ sig(args)
602
+ self.class.functions
603
+ when "classes"
604
+ sig(args)
605
+ self.class.classes
76
606
  when "Integer"
77
607
  sig(args) { Object.repeat }
78
608
  if code_arguments.any?
@@ -92,7 +622,7 @@ class Code
92
622
  end
93
623
  when "context"
94
624
  sig(args)
95
- code_context
625
+ code_context.code_deep_duplicate
96
626
  when "Object"
97
627
  sig(args)
98
628
  if code_arguments.any?
@@ -173,6 +703,9 @@ class Code
173
703
  when "evaluate"
174
704
  sig(args) { Object }
175
705
  Code.code_evaluate(code_value.code_to_string, **globals)
706
+ when "getting_started"
707
+ sig(args)
708
+ GETTING_STARTED.to_code
176
709
  when "p"
177
710
  sig(args) { Object.repeat }
178
711
  output.puts(*code_arguments.raw.map(&:inspect))