Skip to content

Commit 8d80cee

Browse files
committed
feat: add draft logic of eager evaluation
1 parent f03f6b7 commit 8d80cee

File tree

4 files changed

+293
-2
lines changed

4 files changed

+293
-2
lines changed

lib/node_modules/@stdlib/repl/lib/defaults.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,10 @@ function defaults() {
106106
'syntaxHighlighting': void 0,
107107

108108
// Theme for syntax highlighting:
109-
'theme': 'stdlib-ansi-basic'
109+
'theme': 'stdlib-ansi-basic',
110+
111+
// Flag indicating whether to enable eager Evaluation (note: default depends on whether TTY):
112+
'eagerEvaluation': void 0
110113
}
111114
};
112115
}
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
/**
2+
* @license Apache-2.0
3+
*
4+
* Copyright (c) 2024 The Stdlib Authors.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
/* eslint-disable no-underscore-dangle, no-restricted-syntax, no-invalid-this */
20+
21+
'use strict';
22+
23+
// MODULES //
24+
25+
var readline = require( 'readline' );
26+
var inspect = require( 'util' ).inspect;
27+
var logger = require( 'debug' );
28+
var parse = require( 'acorn' ).parse;
29+
var replace = require( '@stdlib/string/replace' );
30+
var setNonEnumerableReadOnly = require( '@stdlib/utils/define-nonenumerable-read-only-property' );
31+
var processCommand = require( './process_command.js' );
32+
var compileCommand = require( './compile_command.js' );
33+
34+
35+
// VARIABLES //
36+
37+
var debug = logger( 'repl:EE' );
38+
var AOPTS = {
39+
'ecmaVersion': 'latest'
40+
};
41+
var tempDB = {
42+
'base_sin': {
43+
'isPure': true
44+
}
45+
};
46+
47+
48+
// MAIN //
49+
50+
/**
51+
* Constructor for creating a multi-line handler.
52+
*
53+
* @private
54+
* @param {REPL} repl - repl instance
55+
* @param {Object} rli - readline instance
56+
* @param {Boolean} enabled - boolean indicating whether the syntax-highlighter should be initially enabled
57+
* @returns {EagerEvaluator} eager-evaluator instance
58+
*/
59+
function EagerEvaluator( repl, rli, enabled ) {
60+
this._repl = repl;
61+
this._rli = rli;
62+
this._cmd = repl._cmd;
63+
this._enabled = enabled;
64+
this._isPreview = false;
65+
66+
return this;
67+
}
68+
69+
/**
70+
* Callback for handling a "keypress" event.
71+
*
72+
* @name onKeyPress
73+
* @memberof EagerEvaluator.prototype
74+
* @type {Function}
75+
* @param {string} data - input data
76+
* @param {(Object|void)} key - key object
77+
* @returns {void}
78+
*
79+
*/
80+
setNonEnumerableReadOnly(EagerEvaluator.prototype, 'onKeypress', function onKeyPress(data, key) {
81+
var cursorPosition;
82+
var executable;
83+
var nlInd;
84+
var code;
85+
var opts;
86+
var pre;
87+
var res;
88+
var tmp;
89+
90+
if (!this._enabled || this._rli.line === '' || key.name === 'paste-start' || (key.name === 'o' && key.ctrl) || key.name === 'return') {
91+
return;
92+
}
93+
94+
code = this._cmd.join('\n') + this._rli.line;
95+
opts = {
96+
'timeout': this._repl._timeout,
97+
'displayErrors': false,
98+
'breakOnSigint': true // Node.js >=6.3.0
99+
};
100+
if (!this.isSideEffectFree(code)) {
101+
debug('code have side effect');
102+
return;
103+
}
104+
debug('try to process command');
105+
tmp = processCommand(code);
106+
if (tmp instanceof Error) {
107+
debug( 'getting error %s', tmp.message );
108+
return;
109+
}
110+
debug('try to compile command');
111+
executable = compileCommand(tmp);
112+
if (executable instanceof Error) {
113+
debug( 'getting error %s', executable.message );
114+
return;
115+
}
116+
try {
117+
if ( this._repl._sandbox ) {
118+
res = executable.compiled.runInContext( this._repl._context, opts );
119+
} else {
120+
res = executable.compiled.runInThisContext( opts );
121+
}
122+
} catch (err) {
123+
debug( 'getting error when executing the command %s', err.message );
124+
return;
125+
}
126+
127+
res = inspect(res);
128+
nlInd = res.indexOf('\n');
129+
if ( nlInd !== -1 ) {
130+
res = res.slice(0, nlInd ) + '...';
131+
}
132+
cursorPosition = this._rli.cursor;
133+
pre = replace( this._repl._outputPrompt, '%d', (this._repl._count+1).toString() );
134+
this._repl._ostream.write( '\n\u001b[90m' + pre + res + '\u001b[0m' );
135+
readline.moveCursor(this._repl._ostream, 0, -1);
136+
readline.cursorTo(this._repl._ostream, cursorPosition + this._repl.promptLength() );
137+
this._isPreview = true;
138+
executable.raw = code;
139+
this._repl._isEagerEvaluated = true;
140+
this._repl._eagerEvaluatedExecutable = executable;
141+
debug( 'sucess' );
142+
});
143+
144+
/**
145+
* Callback which should be invoked**before** a "keypress" event.
146+
*
147+
* @name beforeKeyPress
148+
* @memberof EagerEvaluator.prototype
149+
* @type {Function}
150+
* @param {string} data - input data
151+
* @param {(Object|void)} key - key object
152+
* @returns {void}
153+
*
154+
*/
155+
setNonEnumerableReadOnly(EagerEvaluator.prototype, 'beforeKeypress', function beforeKeyPress(data, key) {
156+
var cursorPosition;
157+
158+
if (!this._isPreview || key.name === 'return' || (key.name === 'o' && key.ctrl) ) {
159+
return;
160+
}
161+
if ( this._isPreview ) {
162+
cursorPosition = this._rli.cursor;
163+
readline.moveCursor( this._repl._ostream, 0, 1 );
164+
readline.clearLine( this._repl._ostream, 0 );
165+
readline.moveCursor( this._repl._ostream, 0, -1 );
166+
readline.cursorTo( this._repl._ostream, cursorPosition + this._repl.promptLength() );
167+
this._isPreview = false;
168+
}
169+
this._repl._isEagerEvaluated = false;
170+
this._repl._eagerEvaluatedExecutable = void 0;
171+
});
172+
173+
/**
174+
* Tells weather code is side effect free or not.
175+
*
176+
* @name isSideEffectFree
177+
* @memberof EagerEvaluator.prototype
178+
* @type {Function}
179+
* @param {string} code - input code
180+
* @returns {boolean} - boolean indicate weather code is side effect free or not.
181+
*
182+
*/
183+
setNonEnumerableReadOnly( EagerEvaluator.prototype, 'isSideEffectFree', function isSideEffectFree( code ) {
184+
var ast;
185+
var i;
186+
187+
try {
188+
ast = parse( code, AOPTS );
189+
}
190+
catch (err) {
191+
debug( 'getting error when generating AST %S ', err );
192+
return false;
193+
}
194+
// Iterate from each node in body
195+
for ( i = 0; i < ast.body.length; i++ ) {
196+
if ( !traverse( ast.body[ i ] ) ) {
197+
return false;
198+
}
199+
}
200+
return true;
201+
202+
/**
203+
* Function which recursivly traverse from the node and tells weather node is side effect free or not.
204+
*
205+
* @private
206+
* @param {JSON} node - ast node.
207+
* @returns {Boolean} - Boolean indicate weather node is side effect free or not.
208+
*/
209+
function traverse(node) {
210+
var fname;
211+
var i;
212+
if ( !node ) {
213+
return false;
214+
}
215+
if ( node.type === 'Literal' || node.type === 'Identifier' || node.type === 'MemberExpression' ) {
216+
return true;
217+
}
218+
if ( node.type === 'BinaryExpression' ) {
219+
if ( traverse( node.left ) && traverse( node.right ) ) {
220+
return true;
221+
}
222+
}
223+
else if ( node.type === 'ExpressionStatement' ) {
224+
if ( traverse( node.expression ) ) {
225+
return true;
226+
}
227+
}
228+
else if ( node.type === 'CallExpression' ) {
229+
fname = getFunctionName( node.callee );
230+
if ( tempDB[fname] && tempDB[fname].isPure ) {
231+
// Iterating through arguments
232+
for ( i = 0; i < node.arguments.length; i++ ) {
233+
if ( !traverse( node.arguments[ i ] ) ) {
234+
return false;
235+
}
236+
}
237+
return true;
238+
}
239+
}
240+
return false;
241+
}
242+
243+
/**
244+
* Get the underscore seprate function name for the member function call.
245+
*
246+
* @private
247+
* @param {JSON} node - ast node
248+
* @returns {string} - underscore seprated function name for the member function call
249+
*/
250+
function getFunctionName( node ) {
251+
if ( !node ) {
252+
return '';
253+
}
254+
if ( node.type === 'MemberExpression' ) {
255+
return getFunctionName(node.object) + '_' + node.property.name;
256+
}
257+
if ( node.type === 'Identifier' ) {
258+
return node.name;
259+
}
260+
return '';
261+
}
262+
} );
263+
264+
265+
// EXPORTS //
266+
267+
module.exports = EagerEvaluator;

lib/node_modules/@stdlib/repl/lib/main.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ var CompleterEngine = require( './completer_engine.js' );
6868
var PreviewCompleter = require( './completer_preview.js' );
6969
var AutoCloser = require( './auto_close_pairs.js' );
7070
var SyntaxHighlighter = require( './syntax_highlighter.js' );
71+
var EagerEvaluator = require( './eager_evaluation.js' );
7172
var ALIAS_OVERRIDES = require( './alias_overrides.js' );
7273
var SETTINGS = require( './settings.js' );
7374
var SETTINGS_VALIDATORS = require( './settings_validators.js' );
@@ -159,6 +160,7 @@ function REPL( options ) {
159160
opts.settings.completionPreviews = ( opts.settings.completionPreviews === void 0 ) ? opts.isTTY : opts.settings.completionPreviews; // eslint-disable-line max-len
160161
opts.settings.autoDisableBracketedPasteOnExit = ( opts.settings.autoDisableBracketedPasteOnExit === void 0 ) ? opts.isTTY : opts.settings.autoDisableBracketedPasteOnExit; // eslint-disable-line max-len
161162
opts.settings.syntaxHighlighting = ( opts.settings.syntaxHighlighting === void 0 ) ? opts.isTTY : opts.settings.syntaxHighlighting; // eslint-disable-line max-len
163+
opts.settings.eagerEvaluation = ( opts.settings.eagerEvaluation === void 0 ) ? opts.isTTY : opts.settings.eagerEvaluation; // eslint-disable-line max-len
162164

163165
debug( 'Options: %s', JSON.stringify({
164166
'input': '<readable_stream>',
@@ -254,6 +256,12 @@ function REPL( options ) {
254256
// Initialize an internal flag indicating whether we've received a `SIGINT` signal:
255257
setNonEnumerable( this, '_SIGINT', false );
256258

259+
// Initialize an internal flag indicating whether command is eagerlyEvaluated:
260+
setNonEnumerable( this, '_isEagerEvaluated', false );
261+
262+
// Initialize as internal variable for caching the compiled object from eagerEvaluation
263+
setNonEnumerable( this, '_eagerEvaluatedExecutable', void 0 );
264+
257265
// Initialize an internal variable for caching the result of the last successfully evaluated command:
258266
setNonEnumerable( this, '_ans', void 0 );
259267

@@ -293,6 +301,9 @@ function REPL( options ) {
293301
// Initialize a syntax-highlighter:
294302
setNonEnumerableReadOnly( this, '_syntaxHighlighter', new SyntaxHighlighter( this, this._ostream, this._settings.syntaxHighlighting ) );
295303

304+
// Initialize a eagerEvaluator:
305+
setNonEnumerableReadOnly( this, '_eagerEvaluator', new EagerEvaluator( this, this._rli, this._settings.eagerEvaluation ) );
306+
296307
// Cache a reference to the private readline interface `ttyWrite` to allow calling the method when wanting default behavior:
297308
setNonEnumerableReadOnly( this, '_ttyWrite', this._rli._ttyWrite );
298309

@@ -363,6 +374,7 @@ function REPL( options ) {
363374
completed = self._previewCompleter.beforeKeypress( data, key );
364375
FLG = self._editorActions.beforeKeypress( data, key );
365376

377+
self._eagerEvaluator.beforeKeypress( data, key );
366378
// If ENTER keypress is encountered or if a preview was completed while navigating, gracefully close the completer...
367379
if ( completed || ( key && key.name === 'return' ) ) {
368380
self._completerEngine.closeCompleter();
@@ -398,6 +410,7 @@ function REPL( options ) {
398410
self._previewCompleter.clear();
399411
}
400412
self._completerEngine.onKeypress( data, key );
413+
self._eagerEvaluator.onKeypress( data, key );
401414
self._multilineHandler.onKeypress( data, key );
402415
self._syntaxHighlighter.onKeypress();
403416
self._previewCompleter.onKeypress( data, key );
@@ -1449,7 +1462,7 @@ setNonEnumerableReadOnly( REPL.prototype, 'settings', function settings() {
14491462
}
14501463
nargs = arguments.length;
14511464
if ( nargs === 0 ) {
1452-
return assign( {}, this._settings );
1465+
return assign({}, this._settings );
14531466
}
14541467
name = arguments[ 0 ];
14551468
if ( !isString( name ) ) {

lib/node_modules/@stdlib/repl/lib/multiline_handler.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,14 @@ setNonEnumerableReadOnly( MultilineHandler.prototype, 'processLine', function pr
621621
return;
622622
}
623623
this._multiline.active = false; // false until proven otherwise
624+
if ( this._repl._isEagerEvaluated ) {
625+
this.resetInput();
626+
this._queue.push( this._repl._eagerEvaluatedExecutable );
627+
drain( this._repl );
628+
this._repl_isEagerEvaluated = false;
629+
this._repl._eagerEvaluatedExecutable = void 0;
630+
return;
631+
}
624632
cmd = this._cmd.join( '\n' );
625633
if ( RE_WHITESPACE.test( cmd ) ) {
626634
this.resetInput();

0 commit comments

Comments
 (0)