blob: 189a4384895704d6d8e506544b0ecd7bf3ce8f1c [file] [log] [blame]
// Copyright (c) 2017, 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.
library code_builder.src.specs.expression;
import 'package:meta/meta.dart';
import '../base.dart';
import '../emitter.dart';
import '../visitors.dart';
import 'code.dart';
import 'reference.dart';
part 'expression/binary.dart';
part 'expression/code.dart';
part 'expression/invoke.dart';
part 'expression/literal.dart';
/// Represents a Dart expression.
///
/// See various concrete implementations for details.
abstract class Expression implements Spec {
const Expression();
@override
R accept<R>(covariant ExpressionVisitor<R> visitor, [R context]);
/// Returns the expression as a valid [Code] block.
///
/// Also see [asStatement].
Code asCode() => new AsCodeExpression(this, false);
/// Returns the expression asa valid [Code] block with a trailing `;`.
Code asStatement() => new AsCodeExpression(this, true);
/// Returns the result of [this] `&&` [other].
Expression and(Expression other) {
return new BinaryExpression._(toExpression(), other, '&&');
}
/// This expression preceding by `await`.
Expression get awaited {
return new BinaryExpression._(
const LiteralExpression._('await'),
this,
'',
);
}
/// Return `{other} = {this}`.
Expression assign(Expression other) {
return new BinaryExpression._(
other,
this,
'=',
);
}
/// Return `{other} ??= {this}`.
Expression assignNullAware(Expression other) {
return new BinaryExpression._(
other,
this,
'??=',
);
}
/// Return `var {name} = {this}`.
Expression assignVar(String name, [Reference type]) {
return new BinaryExpression._(
type == null
? new LiteralExpression._('var $name')
: new BinaryExpression._(
type.toExpression(),
new LiteralExpression._(name),
'',
),
this,
'=',
);
}
/// Return `final {name} = {this}`.
Expression assignFinal(String name, [Reference type]) {
return new BinaryExpression._(
type == null
? const LiteralExpression._('final')
: new BinaryExpression._(
const LiteralExpression._('final'),
type.toExpression(),
'',
),
this,
'$name =',
);
}
/// Return `const {name} = {this}`.
Expression assignConst(String name, [Reference type]) {
return new BinaryExpression._(
type == null
? const LiteralExpression._('const')
: new BinaryExpression._(
const LiteralExpression._('const'),
type.toExpression(),
'',
),
this,
'$name =',
);
}
/// Call this expression as a method.
Expression call(
List<Expression> positionalArguments, [
Map<String, Expression> namedArguments = const {},
List<Reference> typeArguments = const [],
]) {
return new InvokeExpression._(
this,
positionalArguments,
namedArguments,
typeArguments,
);
}
/// Returns an expression accessing `.<name>` on this expression.
Expression property(String name) {
return new BinaryExpression._(
this,
new LiteralExpression._(name),
'.',
false,
);
}
/// Returns a new instance of this expression.
Expression newInstance(
List<Expression> positionalArguments, [
Map<String, Expression> namedArguments = const {},
List<Reference> typeArguments = const [],
]) {
return new InvokeExpression._new(
this,
positionalArguments,
namedArguments,
typeArguments,
null,
);
}
/// Returns a new instance of this expression with a named constructor.
Expression newInstanceNamed(
String name,
List<Expression> positionalArguments, [
Map<String, Expression> namedArguments = const {},
List<Reference> typeArguments = const [],
]) {
return new InvokeExpression._new(
this,
positionalArguments,
namedArguments,
typeArguments,
name,
);
}
/// Returns a const instance of this expression.
Expression constInstance(
List<Expression> positionalArguments, [
Map<String, Expression> namedArguments = const {},
List<Reference> typeArguments = const [],
]) {
return new InvokeExpression._const(
this,
positionalArguments,
namedArguments,
typeArguments,
null,
);
}
/// Returns a const instance of this expression with a named constructor.
Expression constInstanceNamed(
String name,
List<Expression> positionalArguments, [
Map<String, Expression> namedArguments = const {},
List<Reference> typeArguments = const [],
]) {
return new InvokeExpression._const(
this,
positionalArguments,
namedArguments,
typeArguments,
name,
);
}
/// This expression preceding by `return`.
Expression get returned {
return new BinaryExpression._(
const LiteralExpression._('return'),
this,
'',
);
}
/// May be overridden to support other types implementing [Expression].
@visibleForOverriding
Expression toExpression() => this;
}
/// Represents a [code] block that wraps an [Expression].
class AsCodeExpression implements Code {
final Expression code;
/// Whether this code should be considered a _statement_.
final bool isStatement;
@visibleForTesting
const AsCodeExpression(this.code, [this.isStatement = false]);
@override
R accept<R>(CodeVisitor<R> visitor, [R context]) {
return (visitor as ExpressionVisitor<R>)
.visitAsCodeExpression(this, context);
}
@override
String toString() => code.toString();
}
/// Knowledge of different types of expressions in Dart.
///
/// **INTERNAL ONLY**.
abstract class ExpressionVisitor<T> implements SpecVisitor<T> {
T visitAsCodeExpression(AsCodeExpression code, [T context]);
T visitBinaryExpression(BinaryExpression expression, [T context]);
T visitCodeExpression(CodeExpression expression, [T context]);
T visitInvokeExpression(InvokeExpression expression, [T context]);
T visitLiteralExpression(LiteralExpression expression, [T context]);
T visitLiteralListExpression(LiteralListExpression expression, [T context]);
T visitLiteralMapExpression(LiteralMapExpression expression, [T context]);
}
/// Knowledge of how to write valid Dart code from [ExpressionVisitor].
///
/// **INTERNAL ONLY**.
abstract class ExpressionEmitter implements ExpressionVisitor<StringSink> {
@override
visitAsCodeExpression(AsCodeExpression expression, [StringSink output]) {
output ??= new StringBuffer();
expression.code.accept(this, output);
if (expression.isStatement) {
output.write(';');
}
return output;
}
@override
visitBinaryExpression(BinaryExpression expression, [StringSink output]) {
output ??= new StringBuffer();
expression.left.accept(this, output);
if (expression.addSpace) {
output.write(' ');
}
output.write(expression.operator);
if (expression.addSpace) {
output.write(' ');
}
expression.right.accept(this, output);
return output;
}
@override
visitCodeExpression(CodeExpression expression, [StringSink output]) {
output ??= new StringBuffer();
final visitor = this as CodeVisitor<StringSink>;
return expression.code.accept(visitor, output);
}
@override
visitInvokeExpression(InvokeExpression expression, [StringSink output]) {
output ??= new StringBuffer();
switch (expression.type) {
case InvokeExpressionType.newInstance:
output.write('new ');
break;
case InvokeExpressionType.constInstance:
output.write('const ');
break;
}
expression.target.accept(this, output);
if (expression.name != null) {
output..write('.')..write(expression.name);
}
if (expression.typeArguments.isNotEmpty) {
output.write('<');
visitAll<Reference>(expression.typeArguments, output, (type) {
type.accept(this, output);
});
output.write('>');
}
output.write('(');
visitAll<Spec>(expression.positionalArguments, output, (spec) {
spec.accept(this, output);
});
if (expression.positionalArguments.isNotEmpty &&
expression.namedArguments.isNotEmpty) {
output.write(', ');
}
visitAll<String>(expression.namedArguments.keys, output, (name) {
output..write(name)..write(': ');
expression.namedArguments[name].accept(this, output);
});
return output..write(')');
}
@override
visitLiteralExpression(LiteralExpression expression, [StringSink output]) {
output ??= new StringBuffer();
return output..write(expression.literal);
}
void _acceptLiteral(Object literalOrSpec, StringSink output) {
if (literalOrSpec is Spec) {
literalOrSpec.accept(this, output);
return;
}
literal(literalOrSpec).accept(this, output);
}
@override
visitLiteralListExpression(
LiteralListExpression expression, [
StringSink output,
]) {
output ??= new StringBuffer();
if (expression.isConst) {
output.write('const ');
}
if (expression.type != null) {
output.write('<');
expression.type.accept(this, output);
output.write('>');
}
output.write('[');
visitAll<Object>(expression.values, output, (value) {
_acceptLiteral(value, output);
});
return output..write(']');
}
@override
visitLiteralMapExpression(
LiteralMapExpression expression, [
StringSink output,
]) {
output ??= new StringBuffer();
if (expression.isConst) {
output.write('const ');
}
if (expression.keyType != null) {
output.write('<');
expression.keyType.accept(this, output);
output.write(', ');
if (expression.valueType == null) {
const Reference('dynamic', 'dart:core').accept(this, output);
} else {
expression.valueType.accept(this, output);
}
output.write('>');
}
output.write('{');
visitAll<Object>(expression.values.keys, output, (key) {
final value = expression.values[key];
_acceptLiteral(key, output);
output.write(': ');
_acceptLiteral(value, output);
});
return output..write('}');
}
}