zuzu-js 0.1.0 → 0.1.2

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.
package/lib/cli.js CHANGED
@@ -254,7 +254,7 @@ function stripShebang( source ) {
254
254
  }
255
255
 
256
256
  function printVersion( runtime, verbose ) {
257
- process.stdout.write( 'zuzu-js version 0.1.0\n' );
257
+ process.stdout.write( 'zuzu-js version 0.1.2\n' );
258
258
  if ( verbose ) {
259
259
  process.stdout.write( '\nlib search paths:\n' );
260
260
  for ( const p of runtime.getModuleSearchRoots() ) {
@@ -37,15 +37,18 @@ class ZuzuBinary {
37
37
  }
38
38
 
39
39
  slice( start, end ) {
40
- return new ZuzuBinary( this.bytes.slice( start, end ) );
40
+ return new this.constructor( this.bytes.slice( start, end ) );
41
41
  }
42
42
 
43
43
  at( index ) {
44
- const idx = Number( index );
44
+ let idx = Number( index );
45
+ if ( idx < 0 ) {
46
+ idx = this.bytes.length + idx;
47
+ }
45
48
  if ( idx < 0 || idx >= this.bytes.length ) {
46
49
  return null;
47
50
  }
48
- return new ZuzuBinary( [ this.bytes[idx] ] );
51
+ return new this.constructor( [ this.bytes[idx] ] );
49
52
  }
50
53
 
51
54
  to_String() {
package/lib/runtime.js CHANGED
@@ -1573,10 +1573,11 @@ function zuzuGetIndex( target, index ) {
1573
1573
  if ( typeof target === 'string' || Array.isArray( target ) ) {
1574
1574
  let resolved = Number( index );
1575
1575
  if ( Number.isFinite( resolved ) ) {
1576
+ const indexed = typeof target === 'string' ? [ ...target ] : target;
1576
1577
  if ( resolved < 0 ) {
1577
- resolved = target.length + resolved;
1578
+ resolved = indexed.length + resolved;
1578
1579
  }
1579
- return resolveWeakValue( target[resolved] );
1580
+ return resolveWeakValue( indexed[resolved] ?? null );
1580
1581
  }
1581
1582
  }
1582
1583
  if (
@@ -1592,24 +1593,62 @@ function zuzuGetIndex( target, index ) {
1592
1593
  return resolveWeakValue( target[index] );
1593
1594
  }
1594
1595
 
1596
+ function zuzuSliceBounds( size, from, length ) {
1597
+ let start = Number( from ?? 0 );
1598
+ if ( !Number.isFinite( start ) ) {
1599
+ start = 0;
1600
+ }
1601
+ if ( start < 0 ) {
1602
+ start = size + start;
1603
+ }
1604
+ start = Math.max( 0, Math.min( size, start ) );
1605
+ if ( length == null ) {
1606
+ return [ start, size ];
1607
+ }
1608
+ const span = Number( length );
1609
+ if ( !Number.isFinite( span ) ) {
1610
+ return [ start, start ];
1611
+ }
1612
+ const end = span < 0
1613
+ ? Math.max( 0, Math.min( size, size + span ) )
1614
+ : Math.max( 0, Math.min( size, start + span ) );
1615
+ return [ Math.min( start, end ), Math.max( start, end ) ];
1616
+ }
1617
+
1618
+ function zuzuGetSlice( target, from, length ) {
1619
+ target = resolveWeakValue( target );
1620
+ if ( target == null ) {
1621
+ return null;
1622
+ }
1623
+ if ( typeof target === 'string' ) {
1624
+ const chars = [ ...target ];
1625
+ const [ start, end ] = zuzuSliceBounds( chars.length, from, length );
1626
+ return chars.slice( start, end ).join( '' );
1627
+ }
1628
+ if ( target instanceof ZuzuBinary ) {
1629
+ const [ start, end ] = zuzuSliceBounds( target.length, from, length );
1630
+ return target.slice( start, end );
1631
+ }
1632
+ if ( Array.isArray( target ) ) {
1633
+ const [ start, end ] = zuzuSliceBounds( target.length, from, length );
1634
+ return target.slice( start, end );
1635
+ }
1636
+ return target.slice( from, length == null ? undefined : Number( from ) + Number( length ) );
1637
+ }
1638
+
1595
1639
  function zuzuAssignSlice( target, from, length, value ) {
1596
1640
  const hasLength = length != null;
1597
- const span = hasLength ? Number( length ) : null;
1598
1641
  const replacement = value == null ? '' : value;
1599
1642
  if ( typeof target === 'string' ) {
1600
- let start = Number( from );
1601
- if ( start < 0 ) {
1602
- start = target.length + start;
1603
- }
1604
- start = Math.max( 0, Math.min( target.length, start ) );
1605
- const end = hasLength
1606
- ? Math.max( 0, Math.min( target.length, span >= 0 ? start + span : target.length + span ) )
1607
- : target.length;
1608
- return target.slice( 0, start ) + String( replacement ) + target.slice( end );
1643
+ const chars = [ ...target ];
1644
+ const [ start, end ] = zuzuSliceBounds( chars.length, from, length );
1645
+ chars.splice( start, end - start, ...[ ...String( replacement ) ] );
1646
+ return chars.join( '' );
1609
1647
  }
1610
1648
  const start = Number( from );
1611
1649
  if ( Array.isArray( target ) ) {
1612
1650
  const items = Array.isArray( replacement ) ? replacement : [ replacement ];
1651
+ const span = hasLength ? Number( length ) : null;
1613
1652
  if ( hasLength ) {
1614
1653
  target.splice( start, span, ...items );
1615
1654
  }
@@ -1619,7 +1658,16 @@ function zuzuAssignSlice( target, from, length, value ) {
1619
1658
  return target;
1620
1659
  }
1621
1660
  if ( target instanceof ZuzuBinary ) {
1622
- throw new Error( 'Exception: BinaryString slice assignment is not supported' );
1661
+ if ( !( value instanceof ZuzuBinary ) ) {
1662
+ throw new Error( `TypeException: BinaryString slice assignment expects BinaryString, got ${zuzuTypeof( value )}` );
1663
+ }
1664
+ const [ byteStart, byteEnd ] = zuzuSliceBounds( target.length, from, length );
1665
+ const next = new Uint8Array( target.length - ( byteEnd - byteStart ) + value.bytes.length );
1666
+ next.set( target.bytes.slice( 0, byteStart ), 0 );
1667
+ next.set( value.bytes, byteStart );
1668
+ next.set( target.bytes.slice( byteEnd ), byteStart + value.bytes.length );
1669
+ target.bytes = next;
1670
+ return target;
1623
1671
  }
1624
1672
  throw new Error( `TypeException: slice assignment expects String or Array, got ${zuzuTypeof( target )}` );
1625
1673
  }
@@ -1641,18 +1689,27 @@ function assignIndexedValue( target, index, value, isWeakWrite = false, assignTa
1641
1689
  throw new Error( 'TypeException: cannot assign to null' );
1642
1690
  }
1643
1691
  if ( target instanceof ZuzuBinary ) {
1644
- throw new Error( 'Exception: BinaryString index assignment is not supported' );
1692
+ if ( !( value instanceof ZuzuBinary ) ) {
1693
+ throw new Error( `TypeException: BinaryString index assignment expects BinaryString, got ${zuzuTypeof( value )}` );
1694
+ }
1695
+ zuzuAssignSlice( target, index, 1, value );
1696
+ if ( typeof assignTarget === 'function' ) {
1697
+ assignTarget( target );
1698
+ }
1699
+ return target;
1645
1700
  }
1646
1701
  if ( typeof target === 'string' ) {
1647
1702
  let resolved = Number( index );
1648
1703
  if ( !Number.isFinite( resolved ) ) {
1649
1704
  throw new Error( 'TypeException: String index assignment expects numeric index' );
1650
1705
  }
1706
+ const chars = [ ...target ];
1651
1707
  if ( resolved < 0 ) {
1652
- resolved = target.length + resolved;
1708
+ resolved = chars.length + resolved;
1653
1709
  }
1654
- resolved = Math.max( 0, Math.min( target.length, resolved ) );
1655
- const updated = target.slice( 0, resolved ) + String( value ?? '' ) + target.slice( resolved + 1 );
1710
+ resolved = Math.max( 0, Math.min( chars.length, resolved ) );
1711
+ chars.splice( resolved, resolved < chars.length ? 1 : 0, ...[ ...String( value ?? '' ) ] );
1712
+ const updated = chars.join( '' );
1656
1713
  return typeof assignTarget === 'function' ? assignTarget( updated ) : updated;
1657
1714
  }
1658
1715
  if ( isPairListLike( target ) ) {
@@ -1673,16 +1730,10 @@ function refSlice( target, from, length ) {
1673
1730
  const hasLength = length != null;
1674
1731
  const span = hasLength ? Number( length ) : null;
1675
1732
  if ( arguments.length === 0 ) {
1676
- const end = hasLength
1677
- ? ( span >= 0 ? start + span : span )
1678
- : undefined;
1679
- if ( target instanceof ZuzuBinary ) {
1680
- return target.slice( start, end );
1681
- }
1682
- return target.slice( start, end );
1733
+ return zuzuGetSlice( target, start, hasLength ? span : null );
1683
1734
  }
1684
- if ( target instanceof ZuzuBinary ) {
1685
- throw new Error( 'Exception: BinaryString slice assignment is not supported' );
1735
+ if ( target instanceof ZuzuBinary || typeof target === 'string' ) {
1736
+ return zuzuAssignSlice( target, start, hasLength ? span : null, maybeValue );
1686
1737
  }
1687
1738
  if ( !hasLength ) {
1688
1739
  target.splice( start, target.length - start, ...maybeValue );
@@ -1853,6 +1904,13 @@ function zuzuTypeMatches( value, typeName ) {
1853
1904
  if ( typeName === 'PairList' ) {
1854
1905
  return isPairListLike( value ) ? 1 : 0;
1855
1906
  }
1907
+ if ( typeName === 'Exception' ) {
1908
+ const globalType = globalThis.Exception;
1909
+ if ( globalType != null && zuzuInstanceof( value, globalType ) ) {
1910
+ return 1;
1911
+ }
1912
+ return value instanceof Error ? 1 : 0;
1913
+ }
1856
1914
  return zuzuTypeof( value ) === typeName ? 1 : 0;
1857
1915
  }
1858
1916
  if ( typeName === 'BinaryString' ) {
@@ -2981,6 +3039,7 @@ class ZuzuScript {
2981
3039
  },
2982
3040
  __zuzu_ref_index( target, index ) { return refIndex( target, index ); },
2983
3041
  __zuzu_ref_key( target, key ) { return refKey( target, key ); },
3042
+ __zuzu_get_slice( target, from, length ) { return zuzuGetSlice( target, from, length ); },
2984
3043
  __zuzu_ref_slice( target, from, length ) { return refSlice( target, from, length ); },
2985
3044
  __zuzu_assign_slice( target, from, length, value ) { return zuzuAssignSlice( target, from, length, value ); },
2986
3045
  Pair,
@@ -32,6 +32,7 @@ function emitProgram( ast, options = {} ) {
32
32
  ...options,
33
33
  asyncContext: needsAsyncWrapper && !syncEval,
34
34
  asyncNames: new Set( collectAsyncFunctionNames( ast.body ) ),
35
+ evalNames: new Set( [ 'eval', ...collectEvalImportNames( ast.body ) ] ),
35
36
  weakStorageNames: new Set( collectWeakDeclaredNames( ast.body ) ),
36
37
  scopeNames: new Set( [
37
38
  ...expressionDeclaredNames,
@@ -91,6 +92,7 @@ function marshalCaptureNames( node, options = currentEmitContext() ) {
91
92
  '__file__',
92
93
  '__global__',
93
94
  '__system__',
95
+ ...( options.evalNames || [] ),
94
96
  ].filter( Boolean ) );
95
97
  const localKinds = new Set( [
96
98
  'FunctionDeclaration',
@@ -124,6 +126,21 @@ function marshalCaptureNames( node, options = currentEmitContext() ) {
124
126
  }
125
127
  return;
126
128
  }
129
+ if ( value.type === 'TryStatement' ) {
130
+ visit( value.block, value );
131
+ for ( const handler of value.handlers || [] ) {
132
+ const paramName = handler.paramName;
133
+ const hadParam = paramName ? declared.has( paramName ) : false;
134
+ if ( paramName ) {
135
+ declared.add( paramName );
136
+ }
137
+ visit( handler.body, handler );
138
+ if ( paramName && !hadParam ) {
139
+ declared.delete( paramName );
140
+ }
141
+ }
142
+ return;
143
+ }
127
144
  if ( value.type === 'VariableUnpackDeclaration' ) {
128
145
  visit( value.init, value );
129
146
  for ( const entry of bindingEntriesForPattern( value.pattern ) ) {
@@ -204,6 +221,7 @@ function nestedFunctionOptions( options = {} ) {
204
221
  syncEval: options.syncEval,
205
222
  asyncContext: options.asyncContext,
206
223
  asyncNames: options.asyncNames,
224
+ evalNames: options.evalNames,
207
225
  weakStorageNames: options.weakStorageNames,
208
226
  scopeNames: options.scopeNames,
209
227
  bodylessFunctionNames: options.bodylessFunctionNames,
@@ -898,7 +916,6 @@ function emitImportDeclaration( node, options = {} ) {
898
916
  if (
899
917
  node.source === 'std/eval'
900
918
  && importedName === 'eval'
901
- && specifier.local === 'eval'
902
919
  ) {
903
920
  return '';
904
921
  }
@@ -1101,6 +1118,22 @@ function collectAsyncFunctionNames( statements = [] ) {
1101
1118
  return names;
1102
1119
  }
1103
1120
 
1121
+ function collectEvalImportNames( statements = [] ) {
1122
+ const names = [];
1123
+ for ( const stmt of statements ) {
1124
+ if ( stmt.type !== 'ImportDeclaration' || stmt.source !== 'std/eval' ) {
1125
+ continue;
1126
+ }
1127
+ for ( const specifier of stmt.specifiers || [] ) {
1128
+ const importedName = normalizeImportedName( stmt.source, specifier.imported );
1129
+ if ( importedName === 'eval' && specifier.local ) {
1130
+ names.push( specifier.local );
1131
+ }
1132
+ }
1133
+ }
1134
+ return names;
1135
+ }
1136
+
1104
1137
  function collectDeclaredNames( statements ) {
1105
1138
  const names = [];
1106
1139
  for ( const stmt of statements || [] ) {
@@ -1684,7 +1717,11 @@ function emitCallExpression( node, options = {} ) {
1684
1717
  const argArray = emitCallArgumentArray( node.arguments );
1685
1718
  source = `__zuzu_super_dispatch( __zuzu_super_static__, self, __zuzu_super_class__, __zuzu_super_method__, ${argArray} )`;
1686
1719
  }
1687
- else if ( node.callee.type === 'Identifier' && node.callee.name === 'eval' && node.arguments.length > 0 ) {
1720
+ else if (
1721
+ node.callee.type === 'Identifier'
1722
+ && ( options.evalNames || new Set( [ 'eval' ] ) ).has( node.callee.name )
1723
+ && node.arguments.length > 0
1724
+ ) {
1688
1725
  source = emitEvalCall( node.arguments );
1689
1726
  }
1690
1727
  else if (
@@ -1968,7 +2005,7 @@ function emitBinaryExpression( node ) {
1968
2005
  return `__zuzu_ne( ${left}, ${right} )`;
1969
2006
  case '!=':
1970
2007
  case '≠':
1971
- return `__zuzu_num_ne( ${left}, ${right} )`;
2008
+ return `__zuzu_ne( ${left}, ${right} )`;
1972
2009
  case '~':
1973
2010
  return `__zuzu_match( ${left}, ${right} )`;
1974
2011
  case '_':
@@ -2164,14 +2201,8 @@ function emitAssignmentTarget( node ) {
2164
2201
  function emitSliceExpression( node ) {
2165
2202
  const object = emitExpression( node.object );
2166
2203
  const start = node.start ? emitExpression( node.start ) : '0';
2167
- if ( node.length == null ) {
2168
- return `${object}.slice( ${start} )`;
2169
- }
2170
- const length = emitExpression( node.length );
2171
- const end = isNegativeNumericLiteral( node.length )
2172
- ? length
2173
- : `__zuzu_add( ${start}, ${length} )`;
2174
- return `${object}.slice( ${start}, ${end} )`;
2204
+ const length = node.length == null ? 'null' : emitExpression( node.length );
2205
+ return `__zuzu_get_slice( ${object}, ${start}, ${length} )`;
2175
2206
  }
2176
2207
 
2177
2208
  function unwrapGroupedExpression( node ) {
@@ -2182,16 +2213,6 @@ function unwrapGroupedExpression( node ) {
2182
2213
  return current;
2183
2214
  }
2184
2215
 
2185
- function isNegativeNumericLiteral( node ) {
2186
- return !!(
2187
- node
2188
- && node.type === 'UnaryExpression'
2189
- && node.operator === '-'
2190
- && node.argument
2191
- && node.argument.type === 'NumericLiteral'
2192
- );
2193
- }
2194
-
2195
2216
  function emitRegexReplaceExpression( node ) {
2196
2217
  const leftTarget = unwrapGroupedExpression( node.left );
2197
2218
  if (
package/modules/std/io.js CHANGED
@@ -389,6 +389,14 @@ class Path {
389
389
  slurp_utf8_async() {
390
390
  return new Task( async () => fs.promises.readFile( this.value, 'utf8' ) );
391
391
  }
392
+ append( value ) {
393
+ traceBlockingOperation( 'std/io Path.append' );
394
+ if ( !( value && value.bytes instanceof Uint8Array ) ) {
395
+ throw new Error( `TypeException: Path.append expects BinaryString, got ${typeName( value )}` );
396
+ }
397
+ fs.appendFileSync( this.value, Buffer.from( value.bytes ) );
398
+ return this;
399
+ }
392
400
  append_async( value ) {
393
401
  return new Task( async () => {
394
402
  if ( !( value && value.bytes instanceof Uint8Array ) ) {
@@ -398,6 +406,14 @@ class Path {
398
406
  return this;
399
407
  } );
400
408
  }
409
+ append_utf8( text ) {
410
+ traceBlockingOperation( 'std/io Path.append_utf8' );
411
+ if ( typeof text !== 'string' ) {
412
+ throw new Error( `TypeException: Path.append_utf8 expects String, got ${typeName( text )}` );
413
+ }
414
+ fs.appendFileSync( this.value, text, 'utf8' );
415
+ return this;
416
+ }
401
417
  append_utf8_async( text ) {
402
418
  return new Task( async () => {
403
419
  if ( typeof text !== 'string' ) {
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { spawn, spawnSync } = require( 'node:child_process' );
4
4
  const fs = require( 'node:fs' );
5
+ const os = require( 'node:os' );
5
6
  const path = require( 'node:path' );
6
7
  const { Task, traceBlockingOperation } = require( './task' );
7
8
 
@@ -134,12 +135,15 @@ function runCommand( cmd, options = {} ) {
134
135
  return cwdErrorResult( cmd, options, cwd.error );
135
136
  }
136
137
 
138
+ const stdinDir = fs.mkdtempSync( path.join( os.tmpdir(), 'zuzu-proc-stdin-' ) );
139
+ const stdinPath = path.join( stdinDir, 'stdin' );
140
+ fs.writeFileSync( stdinPath, stdin, 'utf8' );
141
+ const stdinFd = fs.openSync( stdinPath, 'r' );
137
142
  const spawnOptions = {
138
- input: stdin,
139
143
  encoding: 'utf8',
140
144
  maxBuffer: 10 * 1024 * 1024,
141
145
  stdio: [
142
- 'pipe',
146
+ stdinFd,
143
147
  captureStdout ? 'pipe' : 'ignore',
144
148
  mergeStderr ? 'pipe' : ( captureStderr ? 'pipe' : 'ignore' ),
145
149
  ],
@@ -166,7 +170,19 @@ function runCommand( cmd, options = {} ) {
166
170
  }
167
171
  }
168
172
 
169
- const spawned = spawnSync( cmd[0], cmd.slice( 1 ), spawnOptions );
173
+ let spawned;
174
+ try {
175
+ spawned = spawnSync( cmd[0], cmd.slice( 1 ), spawnOptions );
176
+ }
177
+ finally {
178
+ fs.closeSync( stdinFd );
179
+ try {
180
+ fs.unlinkSync( stdinPath );
181
+ fs.rmdirSync( stdinDir );
182
+ }
183
+ catch ( _err ) {
184
+ }
185
+ }
170
186
  const timedOut = spawned.error && spawned.error.code === 'ETIMEDOUT';
171
187
  const signal = timedOut
172
188
  ? 14
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zuzu-js",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "JavaScript runtime, compiler, and browser bundle for ZuzuScript.",
5
5
  "main": "lib/zuzu.js",
6
6
  "bin": {