tsjson 1.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 +7 -0
- data/lib/errors/cant_distinguish_type_error.rb +17 -0
- data/lib/errors/index.rb +12 -0
- data/lib/errors/list_validation_error.rb +34 -0
- data/lib/errors/literal_union_validation_error.rb +18 -0
- data/lib/errors/literal_validation_error.rb +16 -0
- data/lib/errors/not_enough_discriminators.rb +7 -0
- data/lib/errors/object_validation_error.rb +56 -0
- data/lib/errors/required_field_error.rb +7 -0
- data/lib/errors/scalar_union_validation_error.rb +18 -0
- data/lib/errors/scalar_validation_error.rb +16 -0
- data/lib/errors/unexpected_field_error.rb +7 -0
- data/lib/errors/unexpected_value_error.rb +16 -0
- data/lib/errors/validation_error.rb +16 -0
- data/lib/language/ast/kind.rb +25 -0
- data/lib/language/lexer/lexer.rb +452 -0
- data/lib/language/lexer/location.rb +20 -0
- data/lib/language/lexer/syntax_error.rb +89 -0
- data/lib/language/lexer/token.rb +34 -0
- data/lib/language/lexer/token_kind.rb +37 -0
- data/lib/language/lexer/utils.rb +32 -0
- data/lib/language/parser/parser.rb +437 -0
- data/lib/language/source.rb +109 -0
- data/lib/schema/schema.rb +48 -0
- data/lib/schema/schema_builder.rb +148 -0
- data/lib/tsjson.rb +1 -0
- data/lib/types/any.rb +15 -0
- data/lib/types/base.rb +19 -0
- data/lib/types/boolean.rb +17 -0
- data/lib/types/discriminator_map.rb +116 -0
- data/lib/types/enum.rb +47 -0
- data/lib/types/float.rb +17 -0
- data/lib/types/index.rb +27 -0
- data/lib/types/int.rb +17 -0
- data/lib/types/intersection.rb +72 -0
- data/lib/types/list.rb +33 -0
- data/lib/types/literal.rb +25 -0
- data/lib/types/literal_union.rb +48 -0
- data/lib/types/merge.rb +21 -0
- data/lib/types/null.rb +17 -0
- data/lib/types/object.rb +87 -0
- data/lib/types/scalar.rb +24 -0
- data/lib/types/scalar_union.rb +25 -0
- data/lib/types/string.rb +17 -0
- data/lib/types/union.rb +61 -0
- metadata +85 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 78840d40b3eb87c1d769fc9fbf0abe0ed430aec3d133f08853dadbdec8320bc1
|
4
|
+
data.tar.gz: 79ec134234bd8258785c415dd0d7399eff8bf4570feaa3ba66f606d5e10544fa
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7746982f586ee257f27776394568361e179c141b8c4bad6f1b49bf578c0a3672f3deea1307646170c5c314e11107a9dd3ecbdb3b9a3542f2beb62aa872f5da10
|
7
|
+
data.tar.gz: f41b5427d03b4f66c0d0a3d08b8d2f4dab4c3dfbc1be99bcf2d3e7eeac6206d4db1ce61422bf999c3b9e65f1bd4c41868f2837b54260569decdeb0ea0cf16409
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module TSJSON
|
2
|
+
class CantDistinguishTypeError < ValidationError
|
3
|
+
def initialize(discriminators:)
|
4
|
+
super
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_human_json
|
8
|
+
{
|
9
|
+
message: "Can't distinguish type. Need more discriminators",
|
10
|
+
details:
|
11
|
+
@details[:discriminators].map do |d|
|
12
|
+
{ message: "#{d[:field]}: #{d[:values].join(', ')}" }
|
13
|
+
end
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/errors/index.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require_relative './validation_error.rb'
|
2
|
+
require_relative './unexpected_value_error.rb'
|
3
|
+
require_relative './cant_distinguish_type_error.rb'
|
4
|
+
require_relative './scalar_validation_error.rb'
|
5
|
+
require_relative './scalar_union_validation_error.rb'
|
6
|
+
require_relative './literal_validation_error.rb'
|
7
|
+
require_relative './list_validation_error.rb'
|
8
|
+
require_relative './object_validation_error.rb'
|
9
|
+
require_relative './unexpected_field_error.rb'
|
10
|
+
require_relative './not_enough_discriminators.rb'
|
11
|
+
require_relative './required_field_error.rb'
|
12
|
+
require_relative './literal_union_validation_error.rb'
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module TSJSON
|
2
|
+
class ListValidationError < ValidationError
|
3
|
+
def initialize(errors:)
|
4
|
+
super
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_json
|
8
|
+
{
|
9
|
+
code: self.class.name.split('::').last,
|
10
|
+
details: {
|
11
|
+
errors:
|
12
|
+
@details[:errors].map do |e|
|
13
|
+
{ index: e[:index], error: e[:error].to_json }
|
14
|
+
end
|
15
|
+
}
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_human_json
|
20
|
+
details =
|
21
|
+
@details[:errors].map do |err|
|
22
|
+
index = err[:index]
|
23
|
+
err_human_json = err[:error].to_human_json
|
24
|
+
|
25
|
+
{
|
26
|
+
message: "#{index}: #{err_human_json[:message]}",
|
27
|
+
details: err_human_json[:details]
|
28
|
+
}.compact
|
29
|
+
end
|
30
|
+
|
31
|
+
return { message: 'One or more items are invalid', details: details }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module TSJSON
|
2
|
+
class LiteralUnionValidationError < ValidationError
|
3
|
+
def initialize(expected_values:, received_value:)
|
4
|
+
super
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_human_json
|
8
|
+
{
|
9
|
+
message:
|
10
|
+
"Expected values: `#{
|
11
|
+
@details[:expected_values].join(', ')
|
12
|
+
}`. Received value `#{@details[:received_value]}` of type `#{
|
13
|
+
@details[:received_type].class
|
14
|
+
}`"
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module TSJSON
|
2
|
+
class LiteralValidationError < ValidationError
|
3
|
+
def initialize(expected_value:, received_value:)
|
4
|
+
super
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_human_json
|
8
|
+
{
|
9
|
+
message:
|
10
|
+
"Expected value `#{@details[:expected_value]}`. Received value `#{
|
11
|
+
@details[:received_value]
|
12
|
+
}`"
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module TSJSON
|
2
|
+
class ObjectValidationError < ValidationError
|
3
|
+
def initialize(errors:)
|
4
|
+
super
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_json
|
8
|
+
{
|
9
|
+
code: self.class.name.split('::').last,
|
10
|
+
details: {
|
11
|
+
errors:
|
12
|
+
@details[:errors].map do |e|
|
13
|
+
{ field: e[:field], error: e[:error].to_json }
|
14
|
+
end
|
15
|
+
}
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_human_json
|
20
|
+
required_fields = []
|
21
|
+
unexpected_fields = []
|
22
|
+
other_errors = []
|
23
|
+
|
24
|
+
@details[:errors].each do |err|
|
25
|
+
if (err[:error].is_a?(RequiredFieldError))
|
26
|
+
required_fields.push(err[:field])
|
27
|
+
elsif (err[:error].is_a?(UnexpectedFieldError))
|
28
|
+
unexpected_fields.push(err[:field])
|
29
|
+
else
|
30
|
+
err_human_json = err[:error].to_human_json
|
31
|
+
other_errors.push(
|
32
|
+
{
|
33
|
+
message: "#{err[:field]}: #{err_human_json[:message]}",
|
34
|
+
details: err_human_json[:details]
|
35
|
+
}.compact
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
details = []
|
41
|
+
if required_fields.length > 0
|
42
|
+
details.push(
|
43
|
+
{ message: "Required fields: #{required_fields.join(', ')}" }
|
44
|
+
)
|
45
|
+
end
|
46
|
+
if unexpected_fields.length > 0
|
47
|
+
details.push(
|
48
|
+
{ message: "Unexpected fields: #{unexpected_fields.join(', ')}" }
|
49
|
+
)
|
50
|
+
end
|
51
|
+
details.concat(other_errors)
|
52
|
+
|
53
|
+
{ message: 'Invalid object', details: details }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module TSJSON
|
2
|
+
class ScalarUnionValidationError < ValidationError
|
3
|
+
def initialize(expected_types:, received_type:, received_value:)
|
4
|
+
super
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_human_json
|
8
|
+
{
|
9
|
+
message:
|
10
|
+
"Expected types: `#{
|
11
|
+
@details[:expected_types].join(', ')
|
12
|
+
}`. Received value `#{@details[:received_value]}` of type `#{
|
13
|
+
@details[:received_type]
|
14
|
+
}`"
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module TSJSON
|
2
|
+
class ScalarValidationError < ValidationError
|
3
|
+
def initialize(expected_type:, received_type:, received_value:)
|
4
|
+
super
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_human_json
|
8
|
+
{
|
9
|
+
message:
|
10
|
+
"Expected type `#{@details[:expected_type]}`. Received value `#{
|
11
|
+
@details[:received_value]
|
12
|
+
}` of type `#{@details[:received_type]}`"
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module TSJSON
|
2
|
+
class UnexpectedValueError < ValidationError
|
3
|
+
def initialize(field:, expected_values:, received:)
|
4
|
+
super
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_human_json
|
8
|
+
{
|
9
|
+
message:
|
10
|
+
"Field '#{@details[:field]}' received unexpected value '#{
|
11
|
+
@details[:received]
|
12
|
+
}'. Expected values are: #{@details[:expected_values].join(', ')}"
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module TSJSON
|
2
|
+
class ValidationError < StandardError
|
3
|
+
def initialize(**details)
|
4
|
+
@details = details
|
5
|
+
super(to_json.to_s)
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_json
|
9
|
+
{ code: self.class.name.split('::').last, details: @details }
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_human_json
|
13
|
+
{ message: self.class.name.split('::').last }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module TSJSON
|
2
|
+
module AST
|
3
|
+
module Kind
|
4
|
+
Name = 'Name'
|
5
|
+
StringLiteral = 'StringLiteral'
|
6
|
+
Int = 'Int'
|
7
|
+
Float = 'Float'
|
8
|
+
Document = 'Document'
|
9
|
+
TypeAlias = 'TypeAlias'
|
10
|
+
TypeParameter = 'TypeParameter'
|
11
|
+
TypeLiteral = 'TypeLiteral'
|
12
|
+
PropertySignature = 'PropertySignature'
|
13
|
+
UnionType = 'UnionType'
|
14
|
+
IntersectionType = 'IntersectionType'
|
15
|
+
ParenthesizedType = 'ParenthesizedType'
|
16
|
+
ArrayType = 'ArrayType'
|
17
|
+
Tuple = 'Tuple'
|
18
|
+
TypeReference = 'TypeReference'
|
19
|
+
Enum = 'Enum'
|
20
|
+
EnumMember = 'EnumMember'
|
21
|
+
IndexAccess = 'IndexAccess'
|
22
|
+
PropertyAccess = 'PropertyAccess'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,452 @@
|
|
1
|
+
require 'json'
|
2
|
+
require_relative './token.rb'
|
3
|
+
require_relative './token_kind.rb'
|
4
|
+
require_relative './syntax_error.rb'
|
5
|
+
require_relative './utils.rb'
|
6
|
+
|
7
|
+
module TSJSON
|
8
|
+
class Lexer
|
9
|
+
attr_accessor :source, :last_token, :token, :line, :line_start
|
10
|
+
|
11
|
+
def initialize(source)
|
12
|
+
startOfFileToken = Token.new(TokenKind::SOF, 0, 0, 0, 0, nil)
|
13
|
+
|
14
|
+
self.source = source
|
15
|
+
self.last_token = startOfFileToken
|
16
|
+
self.token = startOfFileToken
|
17
|
+
self.line = 1
|
18
|
+
self.line_start = 0
|
19
|
+
end
|
20
|
+
|
21
|
+
def advance
|
22
|
+
self.last_token = self.token
|
23
|
+
self.token = self.lookahead
|
24
|
+
end
|
25
|
+
|
26
|
+
def lookahead
|
27
|
+
token = self.token
|
28
|
+
|
29
|
+
if token.kind != TokenKind::EOF
|
30
|
+
loop do
|
31
|
+
# Note: next is only mutable during parsing, so we cast to allow this.
|
32
|
+
token = token.next || (token.next = readToken(token))
|
33
|
+
break if (token.kind != TokenKind::COMMENT)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
return token
|
37
|
+
end
|
38
|
+
|
39
|
+
def readToken(prev)
|
40
|
+
lexer = self
|
41
|
+
source = lexer.source
|
42
|
+
body = source.body
|
43
|
+
body_length = body.length
|
44
|
+
|
45
|
+
pos = prev.end_pos
|
46
|
+
while (pos < body_length)
|
47
|
+
code = char_code_at(body, pos)
|
48
|
+
|
49
|
+
line = lexer.line
|
50
|
+
col = 1 + pos - lexer.line_start
|
51
|
+
|
52
|
+
#SourceCharacter
|
53
|
+
case (code)
|
54
|
+
when 0xfeff, 9, 32
|
55
|
+
pos += 1
|
56
|
+
next
|
57
|
+
when 10
|
58
|
+
pos += 1
|
59
|
+
lexer.line += 1
|
60
|
+
lexer.line_start = pos
|
61
|
+
next
|
62
|
+
when 13
|
63
|
+
if (char_code_at(body, pos + 1) == 10)
|
64
|
+
pos += 2
|
65
|
+
else
|
66
|
+
pos += 1
|
67
|
+
end
|
68
|
+
lexer.line += 1
|
69
|
+
lexer.line_start = pos
|
70
|
+
next
|
71
|
+
when char_code('/')
|
72
|
+
if (char_code_at(body, pos + 1) == char_code('/'))
|
73
|
+
return read_comment(source, pos, line, col, prev)
|
74
|
+
end
|
75
|
+
break
|
76
|
+
when char_code(',')
|
77
|
+
return Token.new(TokenKind::COMMA, pos, pos + 1, line, col, prev)
|
78
|
+
when char_code('&')
|
79
|
+
return Token.new(TokenKind::AMP, pos, pos + 1, line, col, prev)
|
80
|
+
when char_code('(')
|
81
|
+
return Token.new(TokenKind::PAREN_L, pos, pos + 1, line, col, prev)
|
82
|
+
when char_code(')')
|
83
|
+
return Token.new(TokenKind::PAREN_R, pos, pos + 1, line, col, prev)
|
84
|
+
when char_code(':')
|
85
|
+
return Token.new(TokenKind::COLON, pos, pos + 1, line, col, prev)
|
86
|
+
when char_code(';')
|
87
|
+
return Token.new(TokenKind::SEMICOLON, pos, pos + 1, line, col, prev)
|
88
|
+
when char_code('=')
|
89
|
+
return Token.new(TokenKind::EQUALS, pos, pos + 1, line, col, prev)
|
90
|
+
when char_code('<')
|
91
|
+
return Token.new(TokenKind::CHEVRON_L, pos, pos + 1, line, col, prev)
|
92
|
+
when char_code('>')
|
93
|
+
return Token.new(TokenKind::CHEVRON_R, pos, pos + 1, line, col, prev)
|
94
|
+
when char_code('[')
|
95
|
+
return Token.new(TokenKind::BRACKET_L, pos, pos + 1, line, col, prev)
|
96
|
+
when char_code(']')
|
97
|
+
return Token.new(TokenKind::BRACKET_R, pos, pos + 1, line, col, prev)
|
98
|
+
when char_code('{')
|
99
|
+
return Token.new(TokenKind::BRACE_L, pos, pos + 1, line, col, prev)
|
100
|
+
when char_code('|')
|
101
|
+
return Token.new(TokenKind::PIPE, pos, pos + 1, line, col, prev)
|
102
|
+
when char_code('}')
|
103
|
+
return Token.new(TokenKind::BRACE_R, pos, pos + 1, line, col, prev)
|
104
|
+
when char_code('.')
|
105
|
+
return Token.new(TokenKind::DOT, pos, pos + 1, line, col, prev)
|
106
|
+
when char_code('?')
|
107
|
+
return(
|
108
|
+
Token.new(TokenKind::QUESTION_MARK, pos, pos + 1, line, col, prev)
|
109
|
+
)
|
110
|
+
when char_code('"')
|
111
|
+
return read_string(source, pos, line, col, prev)
|
112
|
+
when char_code('-'), char_code('0'), char_code('1'), char_code('2'),
|
113
|
+
char_code('3'), char_code('4'), char_code('5'), char_code('6'),
|
114
|
+
char_code('7'), char_code('8'), char_code('9')
|
115
|
+
return read_number(source, pos, code, line, col, prev)
|
116
|
+
when char_code('A'), char_code('B'), char_code('C'), char_code('D'),
|
117
|
+
char_code('E'), char_code('F'), char_code('G'), char_code('H'),
|
118
|
+
char_code('I'), char_code('J'), char_code('K'), char_code('L'),
|
119
|
+
char_code('M'), char_code('N'), char_code('O'), char_code('P'),
|
120
|
+
char_code('Q'), char_code('R'), char_code('S'), char_code('T'),
|
121
|
+
char_code('U'), char_code('V'), char_code('W'), char_code('X'),
|
122
|
+
char_code('Y'), char_code('Z'), char_code('_'), char_code('a'),
|
123
|
+
char_code('b'), char_code('c'), char_code('d'), char_code('e'),
|
124
|
+
char_code('f'), char_code('g'), char_code('h'), char_code('i'),
|
125
|
+
char_code('j'), char_code('k'), char_code('l'), char_code('m'),
|
126
|
+
char_code('n'), char_code('o'), char_code('p'), char_code('q'),
|
127
|
+
char_code('r'), char_code('s'), char_code('t'), char_code('u'),
|
128
|
+
char_code('v'), char_code('w'), char_code('x'), char_code('y'),
|
129
|
+
char_code('z')
|
130
|
+
return read_name(source, pos, line, col, prev)
|
131
|
+
end
|
132
|
+
|
133
|
+
raise TSJSONSyntaxError.syntax_error(
|
134
|
+
source,
|
135
|
+
pos,
|
136
|
+
unexpectedCharacterMessage(code)
|
137
|
+
)
|
138
|
+
end
|
139
|
+
|
140
|
+
line = lexer.line
|
141
|
+
col = 1 + pos - lexer.line_start
|
142
|
+
return(
|
143
|
+
Token.new(TokenKind::EOF, body_length, body_length, line, col, prev)
|
144
|
+
)
|
145
|
+
end
|
146
|
+
|
147
|
+
def char_code_at(str, pos)
|
148
|
+
str[pos || 0].ord
|
149
|
+
rescue StandardError
|
150
|
+
Float::NAN
|
151
|
+
end
|
152
|
+
|
153
|
+
def char_code(str)
|
154
|
+
char_code_at(str, 0)
|
155
|
+
end
|
156
|
+
|
157
|
+
def unexpectedCharacterMessage(code)
|
158
|
+
if (code < 0x0020 && code != 0x0009 && code != 0x000a && code != 0x000d)
|
159
|
+
return "Cannot contain the invalid character #{print_char_code(code)}."
|
160
|
+
end
|
161
|
+
|
162
|
+
if (code == 39)
|
163
|
+
return(
|
164
|
+
'Unexpected single quote character (\'), did you mean to use a double quote (")?'
|
165
|
+
)
|
166
|
+
end
|
167
|
+
|
168
|
+
return "Cannot parse the unexpected character #{print_char_code(code)}."
|
169
|
+
end
|
170
|
+
|
171
|
+
def print_char_code(code)
|
172
|
+
return(
|
173
|
+
if is_nan?(code)
|
174
|
+
TokenKind::EOF
|
175
|
+
else
|
176
|
+
if code < 0x007f
|
177
|
+
code.chr.to_json
|
178
|
+
else
|
179
|
+
utf_str = '00' + code.to_s(16).upcase
|
180
|
+
"\"\\u#{utf_str.slice(utf_str.length - 4, 4)}\""
|
181
|
+
end
|
182
|
+
end
|
183
|
+
)
|
184
|
+
end
|
185
|
+
|
186
|
+
def read_name(source, start, line, col, prev)
|
187
|
+
body = source.body
|
188
|
+
bodyLength = body.length
|
189
|
+
position = start + 1
|
190
|
+
code = 0
|
191
|
+
while (
|
192
|
+
position != bodyLength &&
|
193
|
+
(!is_nan?(code = char_code_at(body, position))) &&
|
194
|
+
(
|
195
|
+
code == 95 || (code >= 48 && code <= 57) ||
|
196
|
+
(code >= 65 && code <= 90) || (code >= 97 && code <= 122)
|
197
|
+
)
|
198
|
+
)
|
199
|
+
position += 1
|
200
|
+
end
|
201
|
+
return(
|
202
|
+
Token.new(
|
203
|
+
TokenKind::NAME,
|
204
|
+
start,
|
205
|
+
position,
|
206
|
+
line,
|
207
|
+
col,
|
208
|
+
prev,
|
209
|
+
body[start..position - 1]
|
210
|
+
)
|
211
|
+
)
|
212
|
+
end
|
213
|
+
|
214
|
+
def read_comment(source, start, line, col, prev)
|
215
|
+
body = source.body
|
216
|
+
|
217
|
+
position = start
|
218
|
+
|
219
|
+
loop do
|
220
|
+
code = char_code_at(body, position += 1)
|
221
|
+
break unless !is_nan?(code) && (code > 0x001f || code == 0x0009)
|
222
|
+
end
|
223
|
+
|
224
|
+
return(
|
225
|
+
Token.new(
|
226
|
+
TokenKind::COMMENT,
|
227
|
+
start,
|
228
|
+
position,
|
229
|
+
line,
|
230
|
+
col,
|
231
|
+
prev,
|
232
|
+
body[start + 2..position - 1]
|
233
|
+
)
|
234
|
+
)
|
235
|
+
end
|
236
|
+
|
237
|
+
def read_string(source, start, line, col, prev)
|
238
|
+
body = source.body
|
239
|
+
position = start + 1
|
240
|
+
chunkStart = position
|
241
|
+
code = 0
|
242
|
+
value = ''
|
243
|
+
|
244
|
+
while (
|
245
|
+
position < body.length && (code = char_code_at(body, position)) &&
|
246
|
+
!is_nan?(code) && code != 0x000a && code != 0x000d
|
247
|
+
)
|
248
|
+
# Closing Quote (")
|
249
|
+
if (code == 34)
|
250
|
+
value += body[chunkStart..position - 1]
|
251
|
+
return(
|
252
|
+
Token.new(
|
253
|
+
TokenKind::STRING,
|
254
|
+
start,
|
255
|
+
position + 1,
|
256
|
+
line,
|
257
|
+
col,
|
258
|
+
prev,
|
259
|
+
value
|
260
|
+
)
|
261
|
+
)
|
262
|
+
end
|
263
|
+
|
264
|
+
# SourceCharacter
|
265
|
+
if (code < 0x0020 && code != 0x0009)
|
266
|
+
raise TSJSONSyntaxError.syntax_error(
|
267
|
+
source,
|
268
|
+
position,
|
269
|
+
"Invalid character within String: #{print_char_code(code)}."
|
270
|
+
)
|
271
|
+
end
|
272
|
+
|
273
|
+
position += 1
|
274
|
+
if (code == 92)
|
275
|
+
# \
|
276
|
+
value += body[chunkStart..position - 2]
|
277
|
+
code = char_code_at(body, position)
|
278
|
+
case (code)
|
279
|
+
when 34
|
280
|
+
value += '"'
|
281
|
+
when 47
|
282
|
+
value += '/'
|
283
|
+
when 92
|
284
|
+
value += '\\'
|
285
|
+
when 98
|
286
|
+
value += '\b'
|
287
|
+
when 102
|
288
|
+
value += '\f'
|
289
|
+
when 110
|
290
|
+
value += '\n'
|
291
|
+
when 114
|
292
|
+
value += '\r'
|
293
|
+
when 116
|
294
|
+
value += '\t'
|
295
|
+
when 117
|
296
|
+
charCode =
|
297
|
+
uniCharCode(
|
298
|
+
char_code_at(body, position + 1),
|
299
|
+
char_code_at(body, position + 2),
|
300
|
+
char_code_at(body, position + 3),
|
301
|
+
char_code_at(body, position + 4)
|
302
|
+
)
|
303
|
+
if (charCode < 0)
|
304
|
+
invalid_sequence = body[position + 1..position + 4]
|
305
|
+
raise TSJSONSyntaxError.syntax_error(
|
306
|
+
source,
|
307
|
+
position,
|
308
|
+
"Invalid character escape sequence: \\u#{
|
309
|
+
invalid_sequence
|
310
|
+
}."
|
311
|
+
)
|
312
|
+
end
|
313
|
+
value += charCode.chr(Encoding::UTF_8)
|
314
|
+
position += 4
|
315
|
+
else
|
316
|
+
raise TSJSONSyntaxError.syntax_error(
|
317
|
+
source,
|
318
|
+
position,
|
319
|
+
"Invalid character escape sequence: \\#{code.chr}."
|
320
|
+
)
|
321
|
+
end
|
322
|
+
position += 1
|
323
|
+
chunkStart = position
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
raise TSJSONSyntaxError.syntax_error(
|
328
|
+
source,
|
329
|
+
position,
|
330
|
+
'Unterminated string.'
|
331
|
+
)
|
332
|
+
end
|
333
|
+
|
334
|
+
def read_number(source, start, firstCode, line, col, prev)
|
335
|
+
body = source.body
|
336
|
+
code = firstCode
|
337
|
+
position = start
|
338
|
+
isFloat = false
|
339
|
+
|
340
|
+
code = char_code_at(body, position += 1) if (code === 45) # -
|
341
|
+
|
342
|
+
if (code === 48)
|
343
|
+
# 0
|
344
|
+
code = char_code_at(body, position += 1)
|
345
|
+
if (code >= 48 && code <= 57)
|
346
|
+
raise TSJSONSyntaxError.syntax_error(
|
347
|
+
source,
|
348
|
+
position,
|
349
|
+
"Invalid number, unexpected digit after 0: #{
|
350
|
+
print_char_code(code)
|
351
|
+
}."
|
352
|
+
)
|
353
|
+
end
|
354
|
+
else
|
355
|
+
position = read_digits(source, position, code)
|
356
|
+
code = char_code_at(body, position)
|
357
|
+
end
|
358
|
+
|
359
|
+
if (code === 46)
|
360
|
+
# .
|
361
|
+
isFloat = true
|
362
|
+
|
363
|
+
code = char_code_at(body, position += 1)
|
364
|
+
position = read_digits(source, position, code)
|
365
|
+
code = char_code_at(body, position)
|
366
|
+
end
|
367
|
+
|
368
|
+
if (code === 69 || code === 101)
|
369
|
+
# E e
|
370
|
+
isFloat = true
|
371
|
+
|
372
|
+
code = char_code_at(body, position += 1)
|
373
|
+
code = char_code_at(body, position += 1) if (code === 43 || code === 45) # + -
|
374
|
+
position = read_digits(source, position, code)
|
375
|
+
code = char_code_at(body, position)
|
376
|
+
end
|
377
|
+
|
378
|
+
# Numbers cannot be followed by . or NameStart
|
379
|
+
if (code === 46 || is_name_start(code))
|
380
|
+
raise TSJSONSyntaxError.syntax_error(
|
381
|
+
source,
|
382
|
+
position,
|
383
|
+
"Invalid number, expected digit but got: #{
|
384
|
+
print_char_code(code)
|
385
|
+
}."
|
386
|
+
)
|
387
|
+
end
|
388
|
+
|
389
|
+
return(
|
390
|
+
Token.new(
|
391
|
+
isFloat ? TokenKind::FLOAT : TokenKind::INT,
|
392
|
+
start,
|
393
|
+
position,
|
394
|
+
line,
|
395
|
+
col,
|
396
|
+
prev,
|
397
|
+
body[start..position - 1]
|
398
|
+
)
|
399
|
+
)
|
400
|
+
end
|
401
|
+
|
402
|
+
def read_digits(source, start, firstCode)
|
403
|
+
body = source.body
|
404
|
+
position = start
|
405
|
+
code = firstCode
|
406
|
+
if (code >= 48 && code <= 57)
|
407
|
+
# 0 - 9
|
408
|
+
loop do
|
409
|
+
code = char_code_at(body, position += 1)
|
410
|
+
break unless (code >= 48 && code <= 57) # 0 - 9
|
411
|
+
end
|
412
|
+
return position
|
413
|
+
end
|
414
|
+
raise TSJSONSyntaxError.syntax_error(
|
415
|
+
source,
|
416
|
+
position,
|
417
|
+
"Invalid number, expected digit but got: #{
|
418
|
+
print_char_code(code)
|
419
|
+
}."
|
420
|
+
)
|
421
|
+
end
|
422
|
+
|
423
|
+
def is_name_start(code)
|
424
|
+
return(
|
425
|
+
code === 95 || (code >= 65 && code <= 90) || (code >= 97 && code <= 122)
|
426
|
+
)
|
427
|
+
end
|
428
|
+
|
429
|
+
def uniCharCode(a, b, c, d)
|
430
|
+
return(
|
431
|
+
(char2hex(a) << 12) | (char2hex(b) << 8) | (char2hex(c) << 4) |
|
432
|
+
char2hex(d)
|
433
|
+
)
|
434
|
+
end
|
435
|
+
|
436
|
+
def char2hex(a)
|
437
|
+
if a >= 48 && a <= 57
|
438
|
+
a - 48 # 0-9
|
439
|
+
elsif a >= 65 && a <= 70
|
440
|
+
a - 55 # A-F
|
441
|
+
elsif a >= 97 && a <= 102
|
442
|
+
a - 87 # a-f
|
443
|
+
else
|
444
|
+
-1
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
def is_nan?(val)
|
449
|
+
val.is_a?(Float) && val.nan?
|
450
|
+
end
|
451
|
+
end
|
452
|
+
end
|