88 */
99
1010
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+
1183/**
1284 * Helper to determine if value is a string
1385 * @param value: value to check.
@@ -27,18 +99,22 @@ function isFunction(value) {
2799}
28100
29101/**
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
33105 */
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 ) ;
37113 // 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 ) ;
40116 }
41- return BigInt ( string_value ) ;
117+ return number_value ;
42118}
43119
44120/**
@@ -56,24 +132,23 @@ function convertInt(string_value) {
56132 * - BigInt
57133 */
58134export 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+ ]
77152
78153
79154 // Store the language variants the first time
@@ -101,7 +176,7 @@ export class SaferParser {
101176 * @return {{} }: Javascript Object representation of data safely represented in JavaScript types
102177 */
103178 static parse ( json_string , reviver ) {
104- let converted_data = SaferParser . processUnquoted ( json_string , SaferParser . replaceFromString ) ;
179+ let converted_data = SaferParser . preprocess ( json_string ) ;
105180 // Set up a composite reviver of the one passed in and ours
106181 let input_reviver = reviver || ( ( key , value ) => value ) ;
107182 let full_reviver = ( key , value ) => input_reviver ( key , SaferParser . reviver ( key , value ) ) ;
@@ -170,15 +245,17 @@ export class SaferParser {
170245
171246 /**
172247 * Get replacement object from a JavaScript type
248+ * @param _: unused
173249 * @param value: value to replace
174250 */
175251 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 ;
182259 }
183260 }
184261 return value ;
@@ -194,41 +271,28 @@ export class SaferParser {
194271 * @return reworked JSON string
195272 */
196273 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" ) ;
210275 }
211276
212277 /**
213278 * 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
216280 * @return {string }
217281 */
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 ( "" ) ;
232296 }
233297
234298 /**
@@ -239,19 +303,13 @@ export class SaferParser {
239303 */
240304 static reviver ( key , value ) {
241305 // 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" ) {
244308 return value ;
245309 }
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 ;
255313 }
256314
257315 /**
0 commit comments