8
8
*/
9
9
10
10
11
+ /**
12
+ * Lexer for JSON built using JSON
13
+ */
14
+ class RegExLexer {
15
+ static TOKEN_EXPRESSIONS = new Map ( [
16
+ // String tokens: " then
17
+ // any number of:
18
+ // not a quote or \
19
+ // \ followed by not a quote
20
+ // even number of \
21
+ // odd number of \ then " (escaped quotation)
22
+ // then even number of \ then " (terminating non-escaped ")
23
+ [ "STRING" , / ^ " ( [ ^ " \\ ] | ( \\ [ ^ " \\ ] ) | ( ( \\ \\ ) * ) | ( \\ ( \\ \\ ) * ) " ) * (? ! \\ ( \\ \\ ) * ) " / ] ,
24
+ // Floating point tokens
25
+ [ "NUMBER" , / ^ - ? \d + ( \. \d + ) ? ( [ e E ] [ + - ] ? \d + ) ? / ] ,
26
+ // Infinity token
27
+ [ "INFINITY" , / ^ - ? I n f i n i t y / ] ,
28
+ // Null token
29
+ [ "NULL" , / ^ n u l l / ] ,
30
+ // NaN token
31
+ [ "NULL" , / ^ n u l l / ] ,
32
+ // boolean token
33
+ [ "BOOLEAN" , / ^ ( t r u e ) | ( f a l s e ) / ] ,
34
+ // Open object token
35
+ [ "OPEN_OBJECT" , / ^ \{ / ] ,
36
+ // Close object token
37
+ [ "CLOSE_OBJECT" , / ^ } / ] ,
38
+ // Field separator token
39
+ [ "FIELD_SEPERATOR" , / ^ , / ] ,
40
+ // Open list token
41
+ [ "OPEN_ARRAY" , / ^ \[ / ] ,
42
+ // Close a list token
43
+ [ "CLOSE_ARRAY" , / ^ ] / ] ,
44
+ // Key Value Seperator
45
+ [ "VALUE_SEPERATOR" , / ^ : / ] ,
46
+ // Any amount of whitespace is an implicit token
47
+ [ "WHITESPACE" , / ^ \s + / ]
48
+ ] ) ;
49
+
50
+ /**
51
+ * Tokenize the input string based on JSON tokens.
52
+ * @param input_string: input string to tokenize
53
+ * @return {*[] }: list of tokens in-order
54
+ */
55
+ static tokenize ( original_string ) {
56
+ let tokens = [ ] ;
57
+ let input_string = original_string ;
58
+ // Consume the whole string
59
+ while ( input_string !== "" ) {
60
+ let matched_something = false ;
61
+ for ( let [ token_type , token_matcher ] of RegExLexer . TOKEN_EXPRESSIONS . entries ( ) ) {
62
+ let match = token_matcher . exec ( input_string )
63
+
64
+ // Token detected
65
+ if ( match != null ) {
66
+ matched_something = true ;
67
+ let matched = match [ 0 ] ;
68
+ tokens . push ( [ token_type , matched ] ) ;
69
+ // Consume the string
70
+ input_string = input_string . substring ( matched . length ) ;
71
+ break ;
72
+ }
73
+ }
74
+ // Check for no token match
75
+ if ( ! matched_something ) {
76
+ throw SyntaxError ( "Failed to match valid token: '" + input_string . substring ( 0 , 20 ) + "'..." ) ;
77
+ }
78
+ }
79
+ return tokens ;
80
+ }
81
+ }
82
+
11
83
/**
12
84
* Helper to determine if value is a string
13
85
* @param value: value to check.
@@ -27,18 +99,22 @@ function isFunction(value) {
27
99
}
28
100
29
101
/**
30
- * Conversion function for converting from string to BigInt or Number depending on size
31
- * @param { * } string_value : value to convert
32
- * @returns : Number for small values and BigInt for large values
102
+ * Convert a string to a number
103
+ * @param value : value to convert
104
+ * @return { bigint|number }: number to return
33
105
*/
34
- function convertInt ( string_value ) {
35
- string_value = string_value . trim ( ) ;
36
- let number_value = Number . parseInt ( string_value ) ;
106
+ function stringToNumber ( value ) {
107
+ value = value . trim ( ) ; // Should be unnecessary
108
+ // Process floats (containing . e or E)
109
+ if ( value . search ( / [ . e E ] / ) !== - 1 ) {
110
+ return Number . parseFloat ( value ) ;
111
+ }
112
+ let number_value = Number . parseInt ( value ) ;
37
113
// When the big and normal numbers match, then return the normal number
38
- if ( string_value == number_value . toString ( ) ) {
39
- return number_value ;
114
+ if ( value ! == number_value . toString ( ) ) {
115
+ return BigInt ( value ) ;
40
116
}
41
- return BigInt ( string_value ) ;
117
+ return number_value ;
42
118
}
43
119
44
120
/**
@@ -56,24 +132,23 @@ function convertInt(string_value) {
56
132
* - BigInt
57
133
*/
58
134
export class SaferParser {
59
- /**
60
- * States representing QUOTED or UNQUOTED text
61
- * @type {{QUOTED: number, UNQUOTED: number} }
62
- */
63
- static STATES = {
64
- UNQUOTED : 0 ,
65
- QUOTED : 1
66
- } ;
67
- /**
68
- * List of mapping tuples for clean parsing: string match, replacement type, and real (post parse) type
69
- */
70
- static MAPPINGS = [
71
- [ / ( - I n f i n i t y ) / , - Infinity ] ,
72
- [ / ( I n f i n i t y ) / , Infinity ] ,
73
- [ / ( N a N ) / , NaN ] ,
74
- [ / ( n u l l ) / , null ] ,
75
- [ / ( - ? \d { 10 , } ) / , "bigint" , convertInt ]
76
- ] ;
135
+ static CONVERSION_KEY = "fprime{replacement" ;
136
+
137
+ static CONVERSION_MAP = new Map ( [
138
+ [ "INFINITY" , ( value ) => ( value [ 0 ] === "-" ) ? - Infinity : Infinity ] ,
139
+ [ "NAN" , NaN ] ,
140
+ [ "NULL" , null ] ,
141
+ [ "NUMBER" , stringToNumber ]
142
+ ] ) ;
143
+
144
+ static STRINGIFY_TOKENS = [
145
+ Infinity ,
146
+ - Infinity ,
147
+ NaN ,
148
+ "number" ,
149
+ "bigint" ,
150
+ null
151
+ ]
77
152
78
153
79
154
// Store the language variants the first time
@@ -101,7 +176,7 @@ export class SaferParser {
101
176
* @return {{} }: Javascript Object representation of data safely represented in JavaScript types
102
177
*/
103
178
static parse ( json_string , reviver ) {
104
- let converted_data = SaferParser . processUnquoted ( json_string , SaferParser . replaceFromString ) ;
179
+ let converted_data = SaferParser . preprocess ( json_string ) ;
105
180
// Set up a composite reviver of the one passed in and ours
106
181
let input_reviver = reviver || ( ( key , value ) => value ) ;
107
182
let full_reviver = ( key , value ) => input_reviver ( key , SaferParser . reviver ( key , value ) ) ;
@@ -170,15 +245,17 @@ export class SaferParser {
170
245
171
246
/**
172
247
* Get replacement object from a JavaScript type
248
+ * @param _: unused
173
249
* @param value: value to replace
174
250
*/
175
251
static replaceFromObject ( _ , value ) {
176
- for ( let i = 0 ; i < SaferParser . MAPPINGS . length ; i ++ ) {
177
- let mapper_type = SaferParser . MAPPINGS [ i ] [ 1 ] ;
178
- let mapper_is_string = isString ( mapper_type ) ;
179
- // Check if the mapping matches the value, if so substitute a replacement object
180
- if ( ( ! mapper_is_string && value == mapper_type ) || ( mapper_is_string && typeof value == mapper_type ) ) {
181
- return { "fprime{replacement" : ( value == null ) ? "null" : value . toString ( ) } ;
252
+ for ( let i = 0 ; i < SaferParser . STRINGIFY_TOKENS . length ; i ++ ) {
253
+ let replacer_type = SaferParser . STRINGIFY_TOKENS [ i ] ;
254
+ let mapper_is_string = isString ( replacer_type ) ;
255
+ if ( ( ! mapper_is_string && value === replacer_type ) || ( mapper_is_string && typeof value === replacer_type ) ) {
256
+ let replace_object = { } ;
257
+ replace_object [ SaferParser . CONVERSION_KEY ] = ( value == null ) ? "null" : value . toString ( ) ;
258
+ return replace_object ;
182
259
}
183
260
}
184
261
return value ;
@@ -194,41 +271,28 @@ export class SaferParser {
194
271
* @return reworked JSON string
195
272
*/
196
273
static postReplacer ( json_string ) {
197
- return json_string . replace ( / \{ \s * " f p r i m e \{ r e p l a c e m e n t " \s * : \s * " ( [ ^ " ] + ) " \s * \} / sg, "$1" ) ;
198
- }
199
-
200
- /**
201
- * Replace string occurrences of our gnarly types with a mapping equivalent
202
- * @param string_value: value to replace
203
- */
204
- static replaceFromString ( string_value ) {
205
- for ( let i = 0 ; i < SaferParser . MAPPINGS . length ; i ++ ) {
206
- let mapper = SaferParser . MAPPINGS [ i ] ;
207
- string_value = string_value . replace ( mapper [ 0 ] , "{\"fprime{replacement\": \"$1\"}" ) ;
208
- }
209
- return string_value ;
274
+ return json_string . replace ( / \{ \s * " f p r i m e \{ r e p l a c e m e n t " \s * : \s * " ( [ ^ " ] + ) " \s * } / sg, "$1" ) ;
210
275
}
211
276
212
277
/**
213
278
* Apply process function to raw json string only for data that is not qu
214
- * @param json_string
215
- * @param process_function
279
+ * @param json_string: JSON string to preprocess
216
280
* @return {string }
217
281
*/
218
- static processUnquoted ( json_string , process_function ) {
219
- // The initial state of any JSON string is unquoted
220
- let state = SaferParser . STATES . UNQUOTED ;
221
- let unprocessed = json_string ;
222
- let transformed_data = "" ;
223
-
224
- while ( unprocessed . length > 0 ) {
225
- let next_quote = unprocessed . indexOf ( "\"" ) ;
226
- let section = ( next_quote !== - 1 ) ? unprocessed . substring ( 0 , next_quote + 1 ) : unprocessed . substring ( 0 ) ;
227
- unprocessed = unprocessed . substring ( section . length ) ;
228
- transformed_data += ( state === SaferParser . STATES . QUOTED ) ? section : process_function ( section ) ;
229
- state = ( state === SaferParser . STATES . QUOTED ) ? SaferParser . STATES . UNQUOTED : SaferParser . STATES . QUOTED ;
230
- }
231
- return transformed_data ;
282
+ static preprocess ( json_string ) {
283
+ const CONVERSION_KEYS = Array . from ( SaferParser . CONVERSION_MAP . keys ( ) ) ;
284
+ let tokens = RegExLexer . tokenize ( json_string ) ;
285
+ let converted_text = tokens . map (
286
+ ( [ token_type , token_text ] ) => {
287
+ if ( CONVERSION_KEYS . indexOf ( token_type ) !== - 1 ) {
288
+ let replacement_object = { } ;
289
+ replacement_object [ SaferParser . CONVERSION_KEY ] = token_type ;
290
+ replacement_object [ "value" ] = token_text ;
291
+ return JSON . stringify ( replacement_object )
292
+ }
293
+ return token_text ;
294
+ } ) ;
295
+ return converted_text . join ( "" ) ;
232
296
}
233
297
234
298
/**
@@ -239,19 +303,13 @@ export class SaferParser {
239
303
*/
240
304
static reviver ( key , value ) {
241
305
// Look for fprime-replacement and quickly abort if not there
242
- let string_value = value [ "fprime{replacement" ] ;
243
- if ( typeof string_value === "undefined" ) {
306
+ let replacement_type = value [ SaferParser . CONVERSION_KEY ] ;
307
+ if ( typeof replacement_type === "undefined" ) {
244
308
return value ;
245
309
}
246
- // Run the mappings looking for a match
247
- for ( let i = 0 ; i < SaferParser . MAPPINGS . length ; i ++ ) {
248
- let mapper = SaferParser . MAPPINGS [ i ] ;
249
- if ( mapper [ 0 ] . test ( string_value ) ) {
250
- // Run the conversion function if it exists, otherwise return the mapped constant value
251
- return ( mapper . length >= 3 ) ? mapper [ 2 ] ( string_value ) : mapper [ 1 ] ;
252
- }
253
- }
254
- return value ;
310
+ let string_value = value [ "value" ] ;
311
+ let replacer = SaferParser . CONVERSION_MAP . get ( replacement_type ) ;
312
+ return isFunction ( replacer ) ? replacer ( string_value ) : replacer ;
255
313
}
256
314
257
315
/**
0 commit comments