@@ -2,7 +2,7 @@ function echo(value, file, line) {
22 const grey = "\u001b[90m" ;
33 const reset_color = "\u001b[39m" ;
44 const file_line = `${ file } :${ line } ` ;
5- const string_value = echo$ inspect( value ) ;
5+ const string_value = new Echo$Inspector . inspect ( value ) ;
66
77 if ( globalThis . process ?. stderr ?. write ) {
88 // If we're in Node.js, use `stderr`
@@ -22,184 +22,174 @@ function echo(value, file, line) {
2222 return value ;
2323}
2424
25- function echo$inspectString ( str ) {
26- let new_str = '"' ;
27- for ( let i = 0 ; i < str . length ; i ++ ) {
28- let char = str [ i ] ;
29- if ( char == "\n" ) new_str += "\\n" ;
30- else if ( char == "\r" ) new_str += "\\r" ;
31- else if ( char == "\t" ) new_str += "\\t" ;
32- else if ( char == "\f" ) new_str += "\\f" ;
33- else if ( char == "\\" ) new_str += "\\\\" ;
34- else if ( char == '"' ) new_str += '\\"' ;
35- else if ( char < " " || ( char > "~" && char < "\u{00A0}" ) ) {
36- new_str +=
37- "\\u{" +
38- char . charCodeAt ( 0 ) . toString ( 16 ) . toUpperCase ( ) . padStart ( 4 , "0" ) +
39- "}" ;
40- } else {
41- new_str += char ;
25+ class Echo$Inspector {
26+ #references = new Set ( ) ;
27+
28+ inspect ( v ) {
29+ const t = typeof v ;
30+ if ( v === true ) return "True" ;
31+ if ( v === false ) return "False" ;
32+ if ( v === null ) return "//js(null)" ;
33+ if ( v === undefined ) return "Nil" ;
34+ if ( t === "string" ) return this . #string( v ) ;
35+ if ( t === "bigint" || Number . isInteger ( v ) ) return v . toString ( ) ;
36+ if ( t === "number" ) return float_to_string ( v ) ;
37+ if ( v instanceof UtfCodepoint ) return this . #utfCodepoint( v ) ;
38+ if ( v instanceof BitArray ) return this . #bit_array( v ) ;
39+ if ( v instanceof RegExp ) return `//js(${ v } )` ;
40+ if ( v instanceof Date ) return `//js(Date("${ v . toISOString ( ) } "))` ;
41+ if ( v instanceof globalThis . Error ) return `//js(${ v . toString ( ) } )` ;
42+ if ( v instanceof Function ) {
43+ const args = [ ] ;
44+ for ( const i of Array ( v . length ) . keys ( ) )
45+ args . push ( String . fromCharCode ( i + 97 ) ) ;
46+ return `//fn(${ args . join ( ", " ) } ) { ... }` ;
4247 }
43- }
44- new_str += '"' ;
45- return new_str ;
46- }
47-
48- function echo$inspectDict ( map ) {
49- let body = "dict.from_list([" ;
50- let first = true ;
51-
52- let key_value_pairs = [ ] ;
53- map . forEach ( ( value , key ) => {
54- key_value_pairs . push ( [ key , value ] ) ;
55- } ) ;
56- key_value_pairs . sort ( ) ;
57- key_value_pairs . forEach ( ( [ key , value ] ) => {
58- if ( ! first ) body = body + ", " ;
59- body = body + "#(" + echo$inspect ( key ) + ", " + echo$inspect ( value ) + ")" ;
60- first = false ;
61- } ) ;
62- return body + "])" ;
63- }
6448
65- function echo$inspectCustomType ( record ) {
66- const props = globalThis . Object . keys ( record )
67- . map ( ( label ) => {
68- const value = echo$inspect ( record [ label ] ) ;
69- return isNaN ( parseInt ( label ) ) ? `${ label } : ${ value } ` : value ;
70- } )
71- . join ( ", " ) ;
72- return props
73- ? `${ record . constructor . name } (${ props } )`
74- : record . constructor . name ;
75- }
49+ if ( this . #references. size === this . #references. add ( v ) . size ) {
50+ return "//js(circular reference)" ;
51+ }
7652
77- function echo$inspectObject ( v ) {
78- const name = Object . getPrototypeOf ( v ) ?. constructor ?. name || "Object" ;
79- const props = [ ] ;
80- for ( const k of Object . keys ( v ) ) {
81- props . push ( `${ echo$inspect ( k ) } : ${ echo$inspect ( v [ k ] ) } ` ) ;
53+ if ( Array . isArray ( v ) )
54+ return `#(${ v . map ( ( v ) => this . inspect ( v ) ) . join ( ", " ) } )` ;
55+ if ( v instanceof List ) return this . #list( v ) ;
56+ if ( v instanceof CustomType ) return this . #customType( v ) ;
57+ if ( v instanceof Dict ) return this . #dict( v ) ;
58+ if ( v instanceof Set )
59+ return `//js(Set(${ [ ...v ] . map ( ( v ) => this . inspect ( v ) ) . join ( ", " ) } ))` ;
60+ return this . #object( v ) ;
8261 }
83- const body = props . length ? " " + props . join ( ", " ) + " " : "" ;
84- const head = name === "Object" ? "" : name + " " ;
85- return `//js(${ head } {${ body } })` ;
86- }
8762
88- function echo$inspect ( v ) {
89- const t = typeof v ;
90- if ( v === true ) return "True" ;
91- if ( v === false ) return "False" ;
92- if ( v === null ) return "//js(null)" ;
93- if ( v === undefined ) return "Nil" ;
94- if ( t === "string" ) return echo$inspectString ( v ) ;
95- if ( t === "bigint" || t === "number" ) return v . toString ( ) ;
96- if ( v instanceof $UtfCodepoint )
97- return `//utfcodepoint(${ String . fromCodePoint ( v . value ) } )` ;
98- if ( v instanceof $BitArray ) return echo$inspectBitArray ( v ) ;
99- if ( v instanceof RegExp ) return `//js(${ v } )` ;
100- if ( v instanceof Date ) return `//js(Date("${ v . toISOString ( ) } "))` ;
101- if ( v instanceof globalThis . Error ) return `//js(${ v . toString ( ) } )` ;
102- if ( v instanceof Function ) {
103- const args = [ ] ;
104- for ( const i of Array ( v . length ) . keys ( ) )
105- args . push ( String . fromCharCode ( i + 97 ) ) ;
106- return `//fn(${ args . join ( ", " ) } ) { ... }` ;
63+ #object( v ) {
64+ const name = Object . getPrototypeOf ( v ) ?. constructor ?. name || "Object" ;
65+ const props = [ ] ;
66+ for ( const k of Object . keys ( v ) ) {
67+ props . push ( `${ this . inspect ( k ) } : ${ this . inspect ( v [ k ] ) } ` ) ;
68+ }
69+ const body = props . length ? " " + props . join ( ", " ) + " " : "" ;
70+ const head = name === "Object" ? "" : name + " " ;
71+ return `//js(${ head } {${ body } })` ;
10772 }
10873
109- try {
110- if ( globalThis . Array . isArray ( v ) )
111- return `#(${ v . map ( echo$inspect ) . join ( ", " ) } )` ;
112- if ( v instanceof $List ) return echo$inspectList ( v ) ;
113- if ( v instanceof $CustomType ) return echo$inspectCustomType ( v ) ;
114- if ( echo$isDict ( v ) ) return echo$inspectDict ( v ) ;
115- if ( v instanceof Set )
116- return `//js(Set(${ [ ...v ] . map ( echo$inspect ) . join ( ", " ) } ))` ;
117- return echo$inspectObject ( v ) ;
118- } catch ( e ) {
119- if ( e instanceof RangeError ) return "//js(circular)" ;
120- throw e ;
74+ #dict( map ) {
75+ let body = "dict.from_list([" ;
76+ let first = true ;
77+ map . forEach ( ( value , key ) => {
78+ if ( ! first ) body = body + ", " ;
79+ body = body + "#(" + this . inspect ( key ) + ", " + this . inspect ( value ) + ")" ;
80+ first = false ;
81+ } ) ;
82+ return body + "])" ;
12183 }
122- }
12384
124- export function echo$inspectList ( list ) {
125- if ( list instanceof $Empty ) {
126- return "[]" ;
85+ #customType( record ) {
86+ const props = Object . keys ( record )
87+ . map ( ( label ) => {
88+ const value = this . inspect ( record [ label ] ) ;
89+ return isNaN ( parseInt ( label ) ) ? `${ label } : ${ value } ` : value ;
90+ } )
91+ . join ( ", " ) ;
92+ return props
93+ ? `${ record . constructor . name } (${ props } )`
94+ : record . constructor . name ;
12795 }
12896
129- let char_out = 'charlist.from_string("' ;
130- let list_out = "[" ;
97+ #list( list ) {
98+ if ( list instanceof Empty ) {
99+ return "[]" ;
100+ }
101+
102+ let char_out = 'charlist.from_string("' ;
103+ let list_out = "[" ;
131104
132- let current = list ;
133- while ( current instanceof $ NonEmpty) {
134- let element = current . head ;
135- current = current . tail ;
105+ let current = list ;
106+ while ( current instanceof NonEmpty ) {
107+ let element = current . head ;
108+ current = current . tail ;
136109
137- if ( list_out !== "[" ) {
138- list_out += ", " ;
110+ if ( list_out !== "[" ) {
111+ list_out += ", " ;
112+ }
113+ list_out += this . inspect ( element ) ;
114+
115+ if ( char_out ) {
116+ if ( Number . isInteger ( element ) && element >= 32 && element <= 126 ) {
117+ char_out += String . fromCharCode ( element ) ;
118+ } else {
119+ char_out = null ;
120+ }
121+ }
139122 }
140- list_out += echo$inspect ( element ) ;
141123
142124 if ( char_out ) {
143- if ( Number . isInteger ( element ) && element >= 32 && element <= 126 ) {
144- char_out += String . fromCharCode ( element ) ;
145- } else {
146- char_out = null ;
125+ return char_out + '")' ;
126+ } else {
127+ return list_out + "]" ;
128+ }
129+ }
130+
131+ #string( str ) {
132+ let new_str = '"' ;
133+ for ( let i = 0 ; i < str . length ; i ++ ) {
134+ const char = str [ i ] ;
135+ switch ( char ) {
136+ case "\n" :
137+ new_str += "\\n" ;
138+ break ;
139+ case "\r" :
140+ new_str += "\\r" ;
141+ break ;
142+ case "\t" :
143+ new_str += "\\t" ;
144+ break ;
145+ case "\f" :
146+ new_str += "\\f" ;
147+ break ;
148+ case "\\" :
149+ new_str += "\\\\" ;
150+ break ;
151+ case '"' :
152+ new_str += '\\"' ;
153+ break ;
154+ default :
155+ if ( char < " " || ( char > "~" && char < "\u{00A0}" ) ) {
156+ new_str +=
157+ "\\u{" +
158+ char . charCodeAt ( 0 ) . toString ( 16 ) . toUpperCase ( ) . padStart ( 4 , "0" ) +
159+ "}" ;
160+ } else {
161+ new_str += char ;
162+ }
147163 }
148164 }
165+ new_str += '"' ;
166+ return new_str ;
149167 }
150168
151- if ( char_out ) {
152- return char_out + '")' ;
153- } else {
154- return list_out + "]" ;
169+ #utfCodepoint( codepoint ) {
170+ return `//utfcodepoint(${ String . fromCodePoint ( codepoint . value ) } )` ;
155171 }
156- }
157172
158- function echo$inspectBitArray ( bitArray ) {
159- // We take all the aligned bytes of the bit array starting from `bitOffset`
160- // up to the end of the section containing all the aligned bytes.
161- let endOfAlignedBytes =
162- bitArray . bitOffset + 8 * Math . trunc ( bitArray . bitSize / 8 ) ;
163- let alignedBytes = bitArraySlice (
164- bitArray ,
165- bitArray . bitOffset ,
166- endOfAlignedBytes ,
167- ) ;
168-
169- // Now we need to get the remaining unaligned bits at the end of the bit array.
170- // They will start after `endOfAlignedBytes` and end at `bitArray.bitSize`
171- let remainingUnalignedBits = bitArray . bitSize % 8 ;
172- if ( remainingUnalignedBits > 0 ) {
173- let remainingBits = bitArraySliceToInt (
174- bitArray ,
175- endOfAlignedBytes ,
176- bitArray . bitSize ,
177- false ,
178- false ,
179- ) ;
180- let alignedBytesArray = Array . from ( alignedBytes . rawBuffer ) ;
181- let suffix = `${ remainingBits } :size(${ remainingUnalignedBits } )` ;
182- if ( alignedBytesArray . length === 0 ) {
183- return `<<${ suffix } >>` ;
173+ #bit_array( bits ) {
174+ let acc = "<<" ;
175+ if ( bits . bitSize === 0 ) {
176+ return acc ;
177+ }
178+
179+ for ( let i = 0 ; i < bits . byteSize - 1 ; i ++ ) {
180+ acc += bits . byteAt ( i ) . toString ( ) ;
181+ acc += ", " ;
182+ }
183+
184+ if ( bits . byteSize * 8 === bits . bitSize ) {
185+ acc += bits . byteAt ( bits . byteSize - 1 ) . toString ( ) ;
184186 } else {
185- return `<<${ Array . from ( alignedBytes . rawBuffer ) . join ( ", " ) } , ${ suffix } >>` ;
187+ const trailingBitsCount = bits . bitSize % 8 ;
188+ acc += bits . byteAt ( bits . byteSize - 1 ) >> ( 8 - trailingBitsCount ) ;
189+ acc += `:size(${ trailingBitsCount } )` ;
186190 }
187- } else {
188- return `<<${ Array . from ( alignedBytes . rawBuffer ) . join ( ", " ) } >>` ;
189- }
190- }
191191
192- function echo$isDict ( value ) {
193- try {
194- // We can only check if an object is a stdlib Dict if it is one of the
195- // project's dependencies.
196- // The `Dict` class is the default export of `stdlib/dict.mjs`
197- // that we import as `$stdlib$dict`.
198- return value instanceof $stdlib$dict . default ;
199- } catch {
200- // If stdlib is not one of the project's dependencies then `$stdlib$dict`
201- // will not have been imported and the check will throw an exception meaning
202- // we can't check if something is actually a `Dict`.
203- return false ;
192+ acc += ">>" ;
193+ return acc ;
204194 }
205195}
0 commit comments