| // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| /// Efficient JavaScript code builder. |
| /// |
| /// Used to create wrapper expressions and functions for expression evaluation. |
| class JsBuilder { |
| var _indent = 0; |
| final _buffer = StringBuffer(); |
| |
| String? _built; |
| String build() => _built ??= _buffer.toString(); |
| |
| JsBuilder(); |
| |
| void write(String item) { |
| _buffer.write(item); |
| } |
| |
| void writeLine(String item) { |
| _buffer.writeln(item); |
| } |
| |
| void writeAll(Iterable<String> items, [String separator = '']) { |
| _buffer.writeAll(items, separator); |
| } |
| |
| void _writeIndent() { |
| writeAll([for (var i = 0; i < _indent * 2; i++) ' '], ''); |
| } |
| |
| void writeWithIndent(String item) { |
| _writeIndent(); |
| write(item); |
| } |
| |
| void writeLineWithIndent(String line) { |
| _writeIndent(); |
| writeLine(line); |
| } |
| |
| void writeMultiLineExpression(Iterable<String> lines) { |
| var i = 0; |
| for (final line in lines) { |
| if (i == 0) { |
| writeLine(line); |
| } else if (i < lines.length - 1) { |
| writeLineWithIndent(line); |
| } else { |
| writeWithIndent(line); |
| } |
| i++; |
| } |
| } |
| |
| void increaseIndent() { |
| _indent++; |
| } |
| |
| void decreaseIndent() { |
| if (_indent != 0) _indent--; |
| } |
| |
| /// Call the expression built by [build] with [args]. |
| /// |
| /// $function($args); |
| void writeCallExpression(Iterable<String> args, void Function() build) { |
| build(); |
| write('('); |
| writeAll(args, ', '); |
| write(')'); |
| } |
| |
| /// Wrap the expression built by [build] in try/catch block. |
| /// |
| /// try { |
| /// $expression; |
| /// } catch (error) { |
| /// error.name + ": " + error.message; |
| /// }; |
| void writeTryCatchExpression(void Function() build) { |
| writeLineWithIndent('try {'); |
| |
| increaseIndent(); |
| writeWithIndent(''); |
| build(); |
| writeLine(''); |
| decreaseIndent(); |
| |
| writeLineWithIndent('} catch (error) {'); |
| writeLineWithIndent(' error.name + ": " + error.message;'); |
| writeWithIndent('}'); |
| } |
| |
| /// Wrap the statement built by [build] in try/catch block. |
| /// |
| /// try { |
| /// $statement |
| /// } catch (error) { |
| /// return error.name + ": " + error.message; |
| /// }; |
| void writeTryCatchStatement(void Function() build) { |
| writeLineWithIndent('try {'); |
| |
| increaseIndent(); |
| build(); |
| writeLine(''); |
| decreaseIndent(); |
| |
| writeLineWithIndent('} catch (error) {'); |
| writeLineWithIndent(' return error.name + ": " + error.message;'); |
| writeWithIndent('}'); |
| } |
| |
| /// Return the expression built by [build]. |
| /// |
| /// return $expression; |
| void writeReturnStatement(void Function() build) { |
| writeWithIndent('return '); |
| build(); |
| write(';'); |
| } |
| |
| /// Define a function with [params] and body built by [build]. |
| /// |
| /// function($args) { |
| /// $body |
| /// }; |
| void writeFunctionDefinition(Iterable<String> params, void Function() build) { |
| write('function ('); |
| writeAll(params, ', '); |
| writeLine(') {'); |
| |
| increaseIndent(); |
| build(); |
| writeLine(''); |
| decreaseIndent(); |
| |
| writeWithIndent('}'); |
| } |
| |
| /// Bind the function built by [build] to [to]. |
| /// |
| /// $function.bind($to) |
| void writeBindExpression(String to, void Function() build) { |
| build(); |
| write('.bind('); |
| write(to); |
| write(')'); |
| } |
| |
| /// Create a wrapper expression to evaluate the [body]. |
| /// |
| /// Can be used in `Debugger.evaluateOnCallFrame` Chrome API. |
| /// |
| /// try { |
| /// $expression; |
| /// } catch (error) { |
| /// error.name + ": " + error.message; |
| /// } |
| static String createEvalExpression(Iterable<String> body) => |
| (JsBuilder().._writeEvalExpression(body)).build(); |
| |
| void _writeEvalExpression(Iterable<String> body) { |
| writeTryCatchExpression(() { |
| writeMultiLineExpression(body); |
| write(';'); |
| }); |
| } |
| |
| /// Create a wrapper function with [params] that calls a static [function]. |
| /// |
| /// Can be used in `Runtime.callFunctionOn` Chrome API. |
| /// |
| /// function ($params) { |
| /// try { |
| /// return $function($params); |
| /// } catch (error) { |
| /// return error.name + ": " + error.message; |
| /// } |
| /// } |
| static String createEvalStaticFunction( |
| Iterable<String> function, |
| Iterable<String> params, |
| ) => (JsBuilder().._writeEvalStaticFunction(function, params)).build(); |
| |
| void _writeEvalStaticFunction( |
| Iterable<String> function, |
| Iterable<String> params, |
| ) { |
| writeFunctionDefinition( |
| params, |
| () => writeTryCatchStatement( |
| () => writeReturnStatement( |
| () => writeCallExpression(params, () { |
| writeMultiLineExpression(function); |
| }), |
| ), |
| ), |
| ); |
| } |
| |
| /// Create a wrapper function with [params] that calls a bound [function]. |
| /// |
| /// Can be used in `Runtime.callFunctionOn` Chrome API. |
| /// function ($params, __t$this) { |
| /// try { |
| /// return function ($params) { |
| /// return $function($params); |
| /// }.bind(__t$this)($params); |
| /// } catch (error) { |
| /// return error.name + ": " + error.message; |
| /// } |
| /// } |
| static String createEvalBoundFunction( |
| Iterable<String> function, |
| Iterable<String> params, |
| ) => (JsBuilder().._writeEvalBoundFunction(function, params)).build(); |
| |
| void _writeEvalBoundFunction( |
| Iterable<String> function, |
| Iterable<String> params, |
| ) { |
| final original = 'this'; |
| final substitute = '__t\$this'; |
| |
| final args = params.where((e) => e != original); |
| final substitutedParams = [ |
| ...params.where((e) => e != original), |
| substitute, |
| ]; |
| |
| writeFunctionDefinition( |
| substitutedParams, |
| () => writeTryCatchStatement( |
| () => writeReturnStatement( |
| () => writeCallExpression( |
| args, |
| () => writeBindExpression( |
| substitute, |
| () => writeFunctionDefinition( |
| args, |
| () => writeReturnStatement( |
| () => writeCallExpression(args, () { |
| writeMultiLineExpression(function); |
| }), |
| ), |
| ), |
| ), |
| ), |
| ), |
| ), |
| ); |
| } |
| } |