code-ruby 3.1.1 → 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.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/bin/code +97 -20
- data/lib/code/concerns/shared.rb +331 -15
- data/lib/code/format.rb +22 -2
- data/lib/code/network.rb +87 -0
- data/lib/code/node/call.rb +79 -2
- data/lib/code/node/call_argument.rb +14 -0
- data/lib/code/node/code.rb +5 -4
- data/lib/code/node/function_parameter.rb +7 -4
- data/lib/code/node/list.rb +29 -1
- data/lib/code/node/while.rb +21 -0
- data/lib/code/object/base_64.rb +132 -6
- data/lib/code/object/boolean.rb +60 -0
- data/lib/code/object/class.rb +138 -2
- data/lib/code/object/code.rb +111 -3
- data/lib/code/object/context.rb +57 -1
- data/lib/code/object/cryptography.rb +63 -0
- data/lib/code/object/date.rb +13339 -462
- data/lib/code/object/decimal.rb +1725 -0
- data/lib/code/object/dictionary.rb +1835 -12
- data/lib/code/object/duration.rb +28 -0
- data/lib/code/object/function.rb +261 -23
- data/lib/code/object/global.rb +534 -1
- data/lib/code/object/html.rb +227 -16
- data/lib/code/object/http.rb +244 -14
- data/lib/code/object/ics.rb +75 -13
- data/lib/code/object/identifier_list.rb +17 -2
- data/lib/code/object/integer.rb +1941 -2
- data/lib/code/object/json.rb +75 -1
- data/lib/code/object/list.rb +3417 -10
- data/lib/code/object/nothing.rb +53 -0
- data/lib/code/object/number.rb +110 -0
- data/lib/code/object/parameter.rb +140 -0
- data/lib/code/object/range.rb +596 -14
- data/lib/code/object/smtp.rb +95 -12
- data/lib/code/object/string.rb +944 -3
- data/lib/code/object/super.rb +10 -1
- data/lib/code/object/time.rb +13358 -498
- data/lib/code/object/url.rb +65 -0
- data/lib/code/object.rb +543 -0
- data/lib/code/parser.rb +177 -26
- data/lib/code-ruby.rb +3 -0
- data/lib/code.rb +30 -3
- metadata +135 -84
- data/.github/dependabot.yml +0 -15
- data/.github/workflows/ci.yml +0 -38
- data/.gitignore +0 -30
- data/.node-version +0 -1
- data/.npm-version +0 -1
- data/.prettierignore +0 -2
- data/.rspec +0 -1
- data/.rubocop.yml +0 -140
- data/.ruby-version +0 -1
- data/.tool-versions +0 -3
- data/AGENTS.md +0 -43
- data/Gemfile +0 -22
- data/Gemfile.lock +0 -292
- data/Rakefile +0 -5
- data/bin/bundle +0 -123
- data/bin/bundle-audit +0 -31
- data/bin/bundler-audit +0 -31
- data/bin/dorian +0 -31
- data/bin/rspec +0 -31
- data/bin/rubocop +0 -31
- data/bin/test +0 -5
- data/code-ruby.gemspec +0 -34
- data/docs/precedence.txt +0 -36
- data/package-lock.json +0 -14
- data/package.json +0 -7
- data/spec/bin/code_spec.rb +0 -48
- data/spec/code/format_spec.rb +0 -153
- data/spec/code/node/call_spec.rb +0 -11
- data/spec/code/object/boolean_spec.rb +0 -18
- data/spec/code/object/cryptography_spec.rb +0 -25
- data/spec/code/object/decimal_spec.rb +0 -50
- data/spec/code/object/dictionary_spec.rb +0 -98
- data/spec/code/object/function_spec.rb +0 -268
- data/spec/code/object/http_spec.rb +0 -33
- data/spec/code/object/ics_spec.rb +0 -50
- data/spec/code/object/integer_spec.rb +0 -42
- data/spec/code/object/list_spec.rb +0 -22
- data/spec/code/object/nothing_spec.rb +0 -14
- data/spec/code/object/range_spec.rb +0 -23
- data/spec/code/object/string_spec.rb +0 -26
- data/spec/code/parser/boolean_spec.rb +0 -11
- data/spec/code/parser/chained_call_spec.rb +0 -16
- data/spec/code/parser/dictionary_spec.rb +0 -18
- data/spec/code/parser/function_spec.rb +0 -16
- data/spec/code/parser/group_spec.rb +0 -11
- data/spec/code/parser/if_modifier_spec.rb +0 -18
- data/spec/code/parser/list_spec.rb +0 -17
- data/spec/code/parser/number_spec.rb +0 -11
- data/spec/code/parser/string_spec.rb +0 -20
- data/spec/code/parser_spec.rb +0 -52
- data/spec/code/type_spec.rb +0 -21
- data/spec/code_spec.rb +0 -642
- data/spec/spec_helper.rb +0 -21
- data/spec/zeitwerk/loader_spec.rb +0 -7
data/lib/code/parser.rb
CHANGED
|
@@ -73,7 +73,7 @@ class Code
|
|
|
73
73
|
SUFFIX_PUNCTUATION = %w[! ?].freeze
|
|
74
74
|
|
|
75
75
|
ASSIGNMENT_RHS_MIN_BP = 20
|
|
76
|
-
|
|
76
|
+
MAX_NESTING = 200
|
|
77
77
|
INFIX_PRECEDENCE = {
|
|
78
78
|
"if" => [10, 9],
|
|
79
79
|
"unless" => [10, 9],
|
|
@@ -129,6 +129,7 @@ class Code
|
|
|
129
129
|
|
|
130
130
|
def initialize(input)
|
|
131
131
|
@input = input.to_s
|
|
132
|
+
ensure_source_nesting_limit!(@input)
|
|
132
133
|
@tokens = lex(@input)
|
|
133
134
|
@index = 0
|
|
134
135
|
end
|
|
@@ -141,6 +142,10 @@ class Code
|
|
|
141
142
|
Node::Code.new(parse_code)
|
|
142
143
|
end
|
|
143
144
|
|
|
145
|
+
def lex_source(source)
|
|
146
|
+
lex(source.to_s)
|
|
147
|
+
end
|
|
148
|
+
|
|
144
149
|
private
|
|
145
150
|
|
|
146
151
|
attr_reader :input, :tokens
|
|
@@ -284,7 +289,12 @@ class Code
|
|
|
284
289
|
case current.value
|
|
285
290
|
when ".", "::", "&."
|
|
286
291
|
operator = advance.value
|
|
287
|
-
statement =
|
|
292
|
+
statement =
|
|
293
|
+
if current.type == :keyword
|
|
294
|
+
{ call: { name: advance.value } }
|
|
295
|
+
else
|
|
296
|
+
parse_expression(151)
|
|
297
|
+
end
|
|
288
298
|
append_left_operation(left, operator, statement)
|
|
289
299
|
when "["
|
|
290
300
|
advance
|
|
@@ -407,12 +417,26 @@ class Code
|
|
|
407
417
|
skip_newlines
|
|
408
418
|
|
|
409
419
|
statement = parse_expression unless operator == "loop"
|
|
410
|
-
|
|
420
|
+
|
|
421
|
+
if operator == "loop" &&
|
|
422
|
+
(match?(:punctuation, "{") || match?(:keyword, "do"))
|
|
423
|
+
block = parse_block(current.value)
|
|
424
|
+
body = block[:body]
|
|
425
|
+
parameters = block[:parameters]
|
|
426
|
+
else
|
|
427
|
+
body = parse_body(%w[end])
|
|
428
|
+
end
|
|
429
|
+
|
|
411
430
|
skip_newlines
|
|
412
431
|
advance if match?(:keyword, "end")
|
|
413
432
|
|
|
414
433
|
{
|
|
415
|
-
while: {
|
|
434
|
+
while: {
|
|
435
|
+
operator: operator,
|
|
436
|
+
statement: statement,
|
|
437
|
+
parameters: parameters,
|
|
438
|
+
body: body
|
|
439
|
+
}.compact
|
|
416
440
|
}
|
|
417
441
|
end
|
|
418
442
|
|
|
@@ -551,6 +575,19 @@ class Code
|
|
|
551
575
|
end
|
|
552
576
|
|
|
553
577
|
def parse_argument
|
|
578
|
+
if current.type == :operator && %w[* ** & && ...].include?(current.value)
|
|
579
|
+
operator = advance.value
|
|
580
|
+
value =
|
|
581
|
+
if %w[, )].include?(next_significant_token.value)
|
|
582
|
+
skip_newlines
|
|
583
|
+
nil
|
|
584
|
+
else
|
|
585
|
+
parse_code(stop_values: %w[, )])
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
return { operator: operator, value: value }.compact
|
|
589
|
+
end
|
|
590
|
+
|
|
554
591
|
if label_name_start?(current) && next_token_value == ":"
|
|
555
592
|
name = advance.value
|
|
556
593
|
advance
|
|
@@ -639,10 +676,11 @@ class Code
|
|
|
639
676
|
|
|
640
677
|
{
|
|
641
678
|
name: name,
|
|
642
|
-
regular_splat: (
|
|
643
|
-
keyword_splat: (
|
|
644
|
-
block: (
|
|
645
|
-
|
|
679
|
+
regular_splat: (prefix if prefix == "*"),
|
|
680
|
+
keyword_splat: (prefix if prefix == "**"),
|
|
681
|
+
block: (prefix if prefix == "&"),
|
|
682
|
+
blocks: (prefix if prefix == "&&"),
|
|
683
|
+
spread: (prefix if %w[. .. ...].include?(prefix)),
|
|
646
684
|
default: default
|
|
647
685
|
}.compact
|
|
648
686
|
end
|
|
@@ -650,7 +688,7 @@ class Code
|
|
|
650
688
|
def parse_parameter_prefix
|
|
651
689
|
return unless current.type == :operator
|
|
652
690
|
|
|
653
|
-
advance.value if %w[* ** & .. ... .].include?(current.value)
|
|
691
|
+
advance.value if %w[* ** & && .. ... .].include?(current.value)
|
|
654
692
|
end
|
|
655
693
|
|
|
656
694
|
def parse_optional_code(stop_values)
|
|
@@ -938,6 +976,65 @@ class Code
|
|
|
938
976
|
raise Error, "#{message} at #{token.position}"
|
|
939
977
|
end
|
|
940
978
|
|
|
979
|
+
def ensure_source_nesting_limit!(source)
|
|
980
|
+
depth = 0
|
|
981
|
+
quote = nil
|
|
982
|
+
escaped = false
|
|
983
|
+
index = 0
|
|
984
|
+
|
|
985
|
+
while index < source.length
|
|
986
|
+
char = source[index]
|
|
987
|
+
if quote
|
|
988
|
+
if escaped
|
|
989
|
+
escaped = false
|
|
990
|
+
elsif char == "\\"
|
|
991
|
+
escaped = true
|
|
992
|
+
elsif char == quote
|
|
993
|
+
quote = nil
|
|
994
|
+
end
|
|
995
|
+
index += 1
|
|
996
|
+
next
|
|
997
|
+
end
|
|
998
|
+
|
|
999
|
+
if %w[' "].include?(char)
|
|
1000
|
+
quote = char
|
|
1001
|
+
elsif char == "#"
|
|
1002
|
+
index += 1
|
|
1003
|
+
index += 1 while index < source.length &&
|
|
1004
|
+
!NEWLINE_CHARACTERS.include?(source[index])
|
|
1005
|
+
next
|
|
1006
|
+
elsif source[index, 2] == "//"
|
|
1007
|
+
index += 2
|
|
1008
|
+
index += 1 while index < source.length &&
|
|
1009
|
+
!NEWLINE_CHARACTERS.include?(source[index])
|
|
1010
|
+
next
|
|
1011
|
+
elsif source[index, 2] == "/*"
|
|
1012
|
+
index += 2
|
|
1013
|
+
index += 1 while index < source.length && source[index, 2] != "*/"
|
|
1014
|
+
index += 2 if source[index, 2] == "*/"
|
|
1015
|
+
next
|
|
1016
|
+
elsif "([{".include?(char)
|
|
1017
|
+
depth += 1
|
|
1018
|
+
raise_parse_error_at("source is too deeply nested", index) if depth > MAX_NESTING
|
|
1019
|
+
elsif ")]}".include?(char)
|
|
1020
|
+
depth -= 1 if depth.positive?
|
|
1021
|
+
end
|
|
1022
|
+
index += 1
|
|
1023
|
+
end
|
|
1024
|
+
end
|
|
1025
|
+
|
|
1026
|
+
def raise_parse_error_at(message, position)
|
|
1027
|
+
token =
|
|
1028
|
+
Token.new(
|
|
1029
|
+
type: :unknown,
|
|
1030
|
+
value: "",
|
|
1031
|
+
position: position,
|
|
1032
|
+
newline_before: false,
|
|
1033
|
+
space_before: false
|
|
1034
|
+
)
|
|
1035
|
+
raise_parse_error(message, token)
|
|
1036
|
+
end
|
|
1037
|
+
|
|
941
1038
|
def lex(source)
|
|
942
1039
|
tokens = []
|
|
943
1040
|
index = 0
|
|
@@ -1186,10 +1283,16 @@ class Code
|
|
|
1186
1283
|
end
|
|
1187
1284
|
|
|
1188
1285
|
if char == "{"
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1286
|
+
code, i, closed = extract_braced(source, i)
|
|
1287
|
+
|
|
1288
|
+
if closed
|
|
1289
|
+
parts << { type: :text, value: text } unless text.empty?
|
|
1290
|
+
text = +""
|
|
1291
|
+
parts << { type: :code, value: code }
|
|
1292
|
+
else
|
|
1293
|
+
text << "{" << code
|
|
1294
|
+
text << "}" if closed
|
|
1295
|
+
end
|
|
1193
1296
|
next
|
|
1194
1297
|
end
|
|
1195
1298
|
|
|
@@ -1205,28 +1308,74 @@ class Code
|
|
|
1205
1308
|
depth = 1
|
|
1206
1309
|
i = index + 1
|
|
1207
1310
|
body = +""
|
|
1311
|
+
quote = nil
|
|
1312
|
+
escaped = false
|
|
1208
1313
|
|
|
1209
1314
|
while i < source.length
|
|
1210
1315
|
char = source[i]
|
|
1211
1316
|
|
|
1212
|
-
if
|
|
1317
|
+
if quote
|
|
1318
|
+
body << char
|
|
1319
|
+
if escaped
|
|
1320
|
+
escaped = false
|
|
1321
|
+
elsif char == "\\"
|
|
1322
|
+
escaped = true
|
|
1323
|
+
elsif char == quote
|
|
1324
|
+
quote = nil
|
|
1325
|
+
end
|
|
1326
|
+
i += 1
|
|
1327
|
+
next
|
|
1328
|
+
end
|
|
1329
|
+
|
|
1330
|
+
if %w[' "].include?(char)
|
|
1331
|
+
quote = char
|
|
1332
|
+
elsif char == "#"
|
|
1333
|
+
while i < source.length && !NEWLINE_CHARACTERS.include?(source[i])
|
|
1334
|
+
body << source[i]
|
|
1335
|
+
i += 1
|
|
1336
|
+
end
|
|
1337
|
+
next
|
|
1338
|
+
elsif source[i, 2] == "//"
|
|
1339
|
+
2.times do
|
|
1340
|
+
body << source[i]
|
|
1341
|
+
i += 1
|
|
1342
|
+
end
|
|
1343
|
+
while i < source.length && !NEWLINE_CHARACTERS.include?(source[i])
|
|
1344
|
+
body << source[i]
|
|
1345
|
+
i += 1
|
|
1346
|
+
end
|
|
1347
|
+
next
|
|
1348
|
+
elsif source[i, 2] == "/*"
|
|
1349
|
+
2.times do
|
|
1350
|
+
body << source[i]
|
|
1351
|
+
i += 1
|
|
1352
|
+
end
|
|
1353
|
+
while i < source.length && source[i, 2] != "*/"
|
|
1354
|
+
body << source[i]
|
|
1355
|
+
i += 1
|
|
1356
|
+
end
|
|
1357
|
+
if source[i, 2] == "*/"
|
|
1358
|
+
2.times do
|
|
1359
|
+
body << source[i]
|
|
1360
|
+
i += 1
|
|
1361
|
+
end
|
|
1362
|
+
end
|
|
1363
|
+
next
|
|
1364
|
+
elsif char == "{"
|
|
1213
1365
|
depth += 1
|
|
1214
1366
|
elsif char == "}"
|
|
1215
1367
|
depth -= 1
|
|
1216
|
-
return body, i + 1 if depth.zero?
|
|
1368
|
+
return body, i + 1, true if depth.zero?
|
|
1217
1369
|
end
|
|
1218
1370
|
body << char
|
|
1219
1371
|
i += 1
|
|
1220
1372
|
end
|
|
1221
1373
|
|
|
1222
|
-
[body, i]
|
|
1374
|
+
[body, i, false]
|
|
1223
1375
|
end
|
|
1224
1376
|
|
|
1225
1377
|
def scan_number(source, index)
|
|
1226
|
-
|
|
1227
|
-
return unless rest
|
|
1228
|
-
|
|
1229
|
-
if (match = /\A0[xX][0-9a-fA-F](?:_?[0-9a-fA-F])*/.match(rest))
|
|
1378
|
+
if (match = /\G0[xX][0-9a-fA-F](?:_?[0-9a-fA-F])*/.match(source, index))
|
|
1230
1379
|
return(
|
|
1231
1380
|
{
|
|
1232
1381
|
raw: {
|
|
@@ -1239,7 +1388,7 @@ class Code
|
|
|
1239
1388
|
)
|
|
1240
1389
|
end
|
|
1241
1390
|
|
|
1242
|
-
if (match = /\
|
|
1391
|
+
if (match = /\G0[oO][0-7](?:_?[0-7])*/.match(source, index))
|
|
1243
1392
|
return(
|
|
1244
1393
|
{
|
|
1245
1394
|
raw: {
|
|
@@ -1252,7 +1401,7 @@ class Code
|
|
|
1252
1401
|
)
|
|
1253
1402
|
end
|
|
1254
1403
|
|
|
1255
|
-
if (match = /\
|
|
1404
|
+
if (match = /\G0[bB][01](?:_?[01])*/.match(source, index))
|
|
1256
1405
|
return(
|
|
1257
1406
|
{
|
|
1258
1407
|
raw: {
|
|
@@ -1267,8 +1416,9 @@ class Code
|
|
|
1267
1416
|
|
|
1268
1417
|
if (
|
|
1269
1418
|
match =
|
|
1270
|
-
/\
|
|
1271
|
-
|
|
1419
|
+
/\G[0-9](?:_?[0-9])*\.[0-9](?:_?[0-9])*(?:[eE][0-9](?:_?[0-9])*(?:\.[0-9](?:_?[0-9])*)?)?/.match(
|
|
1420
|
+
source,
|
|
1421
|
+
index
|
|
1272
1422
|
)
|
|
1273
1423
|
)
|
|
1274
1424
|
decimal, exponent = match[0].split(/[eE]/, 2)
|
|
@@ -1281,8 +1431,9 @@ class Code
|
|
|
1281
1431
|
|
|
1282
1432
|
if (
|
|
1283
1433
|
match =
|
|
1284
|
-
/\
|
|
1285
|
-
|
|
1434
|
+
/\G[0-9](?:_?[0-9])*(?:[eE][0-9](?:_?[0-9])*(?:\.[0-9](?:_?[0-9])*)?)?/.match(
|
|
1435
|
+
source,
|
|
1436
|
+
index
|
|
1286
1437
|
)
|
|
1287
1438
|
)
|
|
1288
1439
|
whole, exponent = match[0].split(/[eE]/, 2)
|
data/lib/code-ruby.rb
CHANGED
|
@@ -8,10 +8,13 @@ require "date"
|
|
|
8
8
|
require "did_you_mean"
|
|
9
9
|
require "digest"
|
|
10
10
|
require "icalendar"
|
|
11
|
+
require "ipaddr"
|
|
11
12
|
require "json"
|
|
12
13
|
require "mail"
|
|
13
14
|
require "net/http"
|
|
14
15
|
require "nokogiri"
|
|
16
|
+
require "openssl"
|
|
17
|
+
require "resolv"
|
|
15
18
|
require "stringio"
|
|
16
19
|
require "timeout"
|
|
17
20
|
require "uri"
|
data/lib/code.rb
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class Code
|
|
4
|
-
GLOBALS = %i[context error input object output source].freeze
|
|
5
|
-
DEFAULT_TIMEOUT =
|
|
4
|
+
GLOBALS = %i[context error input object output root_object source].freeze
|
|
5
|
+
DEFAULT_TIMEOUT = 1.hour.to_f
|
|
6
|
+
MAX_INPUT_BYTES = 10.megabytes
|
|
6
7
|
LOCALES = %w[en fr].freeze
|
|
7
8
|
|
|
8
9
|
def initialize(
|
|
@@ -20,13 +21,18 @@ class Code
|
|
|
20
21
|
@object = object
|
|
21
22
|
@output = output
|
|
22
23
|
@source = source
|
|
23
|
-
@timeout = timeout
|
|
24
|
+
@timeout = self.class.normalize_timeout!(timeout)
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
def self.parse(source, timeout: DEFAULT_TIMEOUT)
|
|
28
|
+
timeout = normalize_timeout!(timeout)
|
|
29
|
+
|
|
30
|
+
ensure_input_size!(source, label: "source")
|
|
27
31
|
Timeout.timeout(timeout) { Parser.parse(source).to_raw }
|
|
28
32
|
rescue Timeout::Error
|
|
29
33
|
raise Error, "timeout"
|
|
34
|
+
rescue SystemStackError
|
|
35
|
+
raise Error, "source is too deeply nested"
|
|
30
36
|
end
|
|
31
37
|
|
|
32
38
|
def self.evaluate(...)
|
|
@@ -44,7 +50,23 @@ class Code
|
|
|
44
50
|
Format.format(parse_tree)
|
|
45
51
|
end
|
|
46
52
|
|
|
53
|
+
def self.ensure_input_size!(source, limit: MAX_INPUT_BYTES, label: "input")
|
|
54
|
+
return if source.to_s.bytesize <= limit
|
|
55
|
+
|
|
56
|
+
raise Error, "#{label} is too large"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.normalize_timeout!(timeout)
|
|
60
|
+
timeout = DEFAULT_TIMEOUT if timeout.nil?
|
|
61
|
+
timeout = timeout.to_f
|
|
62
|
+
raise Error, "timeout must be positive" unless timeout.positive?
|
|
63
|
+
|
|
64
|
+
timeout
|
|
65
|
+
end
|
|
66
|
+
|
|
47
67
|
def evaluate
|
|
68
|
+
time_zone = ::Time.zone
|
|
69
|
+
|
|
48
70
|
Timeout.timeout(timeout) do
|
|
49
71
|
Node::Code.new(Code.parse(source)).evaluate(
|
|
50
72
|
context: context,
|
|
@@ -53,6 +75,7 @@ class Code
|
|
|
53
75
|
input: input,
|
|
54
76
|
object: object,
|
|
55
77
|
output: output,
|
|
78
|
+
root_object: object,
|
|
56
79
|
source: source,
|
|
57
80
|
timeout: timeout
|
|
58
81
|
)
|
|
@@ -61,6 +84,10 @@ class Code
|
|
|
61
84
|
raise Error, "timeout"
|
|
62
85
|
rescue Interrupt
|
|
63
86
|
raise Error, "interrupt"
|
|
87
|
+
rescue SystemStackError
|
|
88
|
+
raise Error, "source is too deeply nested"
|
|
89
|
+
ensure
|
|
90
|
+
::Time.zone = time_zone
|
|
64
91
|
end
|
|
65
92
|
|
|
66
93
|
private
|