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.
- 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 +15 -1
- 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/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 +1790 -11
- 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 +179 -7
- 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 +1937 -2
- data/lib/code/object/json.rb +75 -1
- data/lib/code/object/list.rb +3383 -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 +576 -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 +161 -24
- 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 -717
- 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
|
|
@@ -565,6 +575,19 @@ class Code
|
|
|
565
575
|
end
|
|
566
576
|
|
|
567
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
|
+
|
|
568
591
|
if label_name_start?(current) && next_token_value == ":"
|
|
569
592
|
name = advance.value
|
|
570
593
|
advance
|
|
@@ -653,10 +676,11 @@ class Code
|
|
|
653
676
|
|
|
654
677
|
{
|
|
655
678
|
name: name,
|
|
656
|
-
regular_splat: (
|
|
657
|
-
keyword_splat: (
|
|
658
|
-
block: (
|
|
659
|
-
|
|
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)),
|
|
660
684
|
default: default
|
|
661
685
|
}.compact
|
|
662
686
|
end
|
|
@@ -664,7 +688,7 @@ class Code
|
|
|
664
688
|
def parse_parameter_prefix
|
|
665
689
|
return unless current.type == :operator
|
|
666
690
|
|
|
667
|
-
advance.value if %w[* ** & .. ... .].include?(current.value)
|
|
691
|
+
advance.value if %w[* ** & && .. ... .].include?(current.value)
|
|
668
692
|
end
|
|
669
693
|
|
|
670
694
|
def parse_optional_code(stop_values)
|
|
@@ -952,6 +976,65 @@ class Code
|
|
|
952
976
|
raise Error, "#{message} at #{token.position}"
|
|
953
977
|
end
|
|
954
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
|
+
|
|
955
1038
|
def lex(source)
|
|
956
1039
|
tokens = []
|
|
957
1040
|
index = 0
|
|
@@ -1200,10 +1283,16 @@ class Code
|
|
|
1200
1283
|
end
|
|
1201
1284
|
|
|
1202
1285
|
if char == "{"
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
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
|
|
1207
1296
|
next
|
|
1208
1297
|
end
|
|
1209
1298
|
|
|
@@ -1219,28 +1308,74 @@ class Code
|
|
|
1219
1308
|
depth = 1
|
|
1220
1309
|
i = index + 1
|
|
1221
1310
|
body = +""
|
|
1311
|
+
quote = nil
|
|
1312
|
+
escaped = false
|
|
1222
1313
|
|
|
1223
1314
|
while i < source.length
|
|
1224
1315
|
char = source[i]
|
|
1225
1316
|
|
|
1226
|
-
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 == "{"
|
|
1227
1365
|
depth += 1
|
|
1228
1366
|
elsif char == "}"
|
|
1229
1367
|
depth -= 1
|
|
1230
|
-
return body, i + 1 if depth.zero?
|
|
1368
|
+
return body, i + 1, true if depth.zero?
|
|
1231
1369
|
end
|
|
1232
1370
|
body << char
|
|
1233
1371
|
i += 1
|
|
1234
1372
|
end
|
|
1235
1373
|
|
|
1236
|
-
[body, i]
|
|
1374
|
+
[body, i, false]
|
|
1237
1375
|
end
|
|
1238
1376
|
|
|
1239
1377
|
def scan_number(source, index)
|
|
1240
|
-
|
|
1241
|
-
return unless rest
|
|
1242
|
-
|
|
1243
|
-
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))
|
|
1244
1379
|
return(
|
|
1245
1380
|
{
|
|
1246
1381
|
raw: {
|
|
@@ -1253,7 +1388,7 @@ class Code
|
|
|
1253
1388
|
)
|
|
1254
1389
|
end
|
|
1255
1390
|
|
|
1256
|
-
if (match = /\
|
|
1391
|
+
if (match = /\G0[oO][0-7](?:_?[0-7])*/.match(source, index))
|
|
1257
1392
|
return(
|
|
1258
1393
|
{
|
|
1259
1394
|
raw: {
|
|
@@ -1266,7 +1401,7 @@ class Code
|
|
|
1266
1401
|
)
|
|
1267
1402
|
end
|
|
1268
1403
|
|
|
1269
|
-
if (match = /\
|
|
1404
|
+
if (match = /\G0[bB][01](?:_?[01])*/.match(source, index))
|
|
1270
1405
|
return(
|
|
1271
1406
|
{
|
|
1272
1407
|
raw: {
|
|
@@ -1281,8 +1416,9 @@ class Code
|
|
|
1281
1416
|
|
|
1282
1417
|
if (
|
|
1283
1418
|
match =
|
|
1284
|
-
/\
|
|
1285
|
-
|
|
1419
|
+
/\G[0-9](?:_?[0-9])*\.[0-9](?:_?[0-9])*(?:[eE][0-9](?:_?[0-9])*(?:\.[0-9](?:_?[0-9])*)?)?/.match(
|
|
1420
|
+
source,
|
|
1421
|
+
index
|
|
1286
1422
|
)
|
|
1287
1423
|
)
|
|
1288
1424
|
decimal, exponent = match[0].split(/[eE]/, 2)
|
|
@@ -1295,8 +1431,9 @@ class Code
|
|
|
1295
1431
|
|
|
1296
1432
|
if (
|
|
1297
1433
|
match =
|
|
1298
|
-
/\
|
|
1299
|
-
|
|
1434
|
+
/\G[0-9](?:_?[0-9])*(?:[eE][0-9](?:_?[0-9])*(?:\.[0-9](?:_?[0-9])*)?)?/.match(
|
|
1435
|
+
source,
|
|
1436
|
+
index
|
|
1300
1437
|
)
|
|
1301
1438
|
)
|
|
1302
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
|