blob: cd02aee91feff919acf38dc5ae7d2235740e0ed2 [file] [log] [blame]
// 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);
}),
),
),
),
),
),
),
);
}
}