Skip to content

Commit

Permalink
Boom expression args (#133)
Browse files Browse the repository at this point in the history
* Initial support for Expression(s).

* More in changelog.

* Add context everywhere.

* All tests passing.

* More cleanup.

* More expression work.

* Improve calls, add property access.

* Update README.

* Actually update it.

* Be nice to users.

* Add literalString, literalMap.
  • Loading branch information
matanlurey committed Oct 13, 2017
1 parent de8f4e9 commit 1aacdfa
Show file tree
Hide file tree
Showing 19 changed files with 380 additions and 88 deletions.
22 changes: 14 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,31 @@
* Added `show|hide` to `Directive`.
* Added `Directive.importDeferredAs`.
* Added a new line character after emitting some types (class, method, etc).
* Added `refer` as a short-hand for `new Reference(...)`.
* `Reference` now implements `Expression`.

* `SpecVisitor<T>`'s methods all have an optional `[T context]` parameter now.
* This makes it much easier to avoid allocating extra `StringBuffer`s.
* Removed `SimpleSpecVisitor` (it was unused).
* Removed `implements Reference` from `Method` and `Field`; not a lot of value.
* `equalsDart` removes insignificant white space before comparing results.

* In process of adding classes/methods for writing bodies of `Code` fluently:
* Added many classes/methods for writing bodies of `Code` fluently:
* `Expression`
* `LiteralExpression`
* `literal`
* `literalNull`
* `literalBool`
* `literalTrue`
* `literalFalse`
* `literalList`
* `literalNum`
* `literalString`
* `literalList` and `literalConstList`
* `literalMap` and `literalConstMap`
* `const Code(staticString)`
* `const Code.scope((allocate) => '')`

* Removed `SimpleSpecVisitor` (it was unused).
* Removed `implements Reference` from `Method` and `Field`; not a lot of value.

* `SpecVisitor<T>`'s methods all have an optional `[T context]` parameter now.
* This makes it much easier to avoid allocating extra `StringBuffer`s.
* `equalsDart` removes insignificant white space before comparing results.

## 2.0.0-alpha+1

* Removed `Reference.localScope`. Just use `Reference(symbol)` now.
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ import 'package:dart_style/dart_style.dart';
void main() {
final animal = new Class((b) => b
..name = 'Animal'
..extend = const Reference('Organism').toType()
..extend = refer('Organism').toType()
..methods.add(new Method.returnsVoid((b) => b
..name = 'eat'
..lambda = true
..body = new Code((b) => b..code = 'print(\'Yum\')'))));
..body = const Code('print(\'Yum\')'))));
final emitter = const DartEmitter();
print(new DartFormatter().format('${animal.accept(emitter)}'));
}
Expand All @@ -70,11 +70,11 @@ void main() {
new Method((b) => b
..body = new Code((b) => b.code = '')
..name = 'doThing'
..returns = const Reference('Thing', 'package:a/a.dart')),
..returns = refer('Thing', 'package:a/a.dart')),
new Method((b) => b
..body = new Code((b) => b..code = '')
..name = 'doOther'
..returns = const Reference('Other', 'package:b/b.dart')),
..returns = refer('Other', 'package:b/b.dart')),
]));
final emitter = new DartEmitter(new Allocator.simplePrefixing());
print(new DartFormatter().format('${library.accept(emitter)}'));
Expand Down
2 changes: 1 addition & 1 deletion example/animal.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import 'package:dart_style/dart_style.dart';
void main() {
final animal = new Class((b) => b
..name = 'Animal'
..extend = const Reference('Organism')
..extend = refer('Organism')
..methods.add(new Method.returnsVoid((b) => b
..name = 'eat'
..lambda = true
Expand Down
4 changes: 2 additions & 2 deletions example/scope.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ void main() {
new Method((b) => b
..body = const Code('')
..name = 'doThing'
..returns = const Reference('Thing', 'package:a/a.dart')),
..returns = refer('Thing', 'package:a/a.dart')),
new Method((b) => b
..body = const Code('')
..name = 'doOther'
..returns = const Reference('Other', 'package:b/b.dart')),
..returns = refer('Other', 'package:b/b.dart')),
]));
final emitter = new DartEmitter(new Allocator.simplePrefixing());
print(new DartFormatter().format('${library.accept(emitter)}'));
Expand Down
5 changes: 4 additions & 1 deletion lib/code_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export 'src/specs/expression.dart'
literalBool,
literalList,
literalConstList,
literalMap,
literalConstMap,
literalString,
literalTrue,
literalFalse;
export 'src/specs/field.dart' show Field, FieldBuilder, FieldModifier;
Expand All @@ -40,5 +43,5 @@ export 'src/specs/method.dart'
MethodType,
Parameter,
ParameterBuilder;
export 'src/specs/reference.dart' show Reference;
export 'src/specs/reference.dart' show refer, Reference;
export 'src/specs/type_reference.dart' show TypeReference, TypeReferenceBuilder;
29 changes: 29 additions & 0 deletions lib/src/emitter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// 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.

import 'package:meta/meta.dart';

import 'allocator.dart';
import 'base.dart';
import 'specs/annotation.dart';
Expand All @@ -17,6 +19,33 @@ import 'specs/reference.dart';
import 'specs/type_reference.dart';
import 'visitors.dart';

/// Helper method improving on [StringSink.writeAll].
///
/// For every `Spec` in [elements], executing [visit].
///
/// If [elements] is at least 2 elements, inserts [separator] delimiting them.
@visibleForTesting
void visitAll<T>(
Iterable<T> elements,
StringSink output,
void visit(T element), [
String separator = ', ',
]) {
// Basically, this whole method is an improvement on
// output.writeAll(specs.map((s) => s.accept(visitor));
//
// ... which would allocate more StringBuffer(s) for a one-time use.
if (elements.isEmpty) {
return;
}
final iterator = elements.iterator..moveNext();
visit(iterator.current);
while (iterator.moveNext()) {
output.write(separator);
visit(iterator.current);
}
}

class DartEmitter extends Object
with CodeEmitter, ExpressionEmitter
implements SpecVisitor<StringSink> {
Expand Down
3 changes: 2 additions & 1 deletion lib/src/specs/code.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import 'package:meta/meta.dart';

import '../allocator.dart';
import '../base.dart';
import '../specs/reference.dart';
import '../visitors.dart';

import 'reference.dart';

/// Returns a scoped symbol to [Reference], with an import prefix if needed.
///
/// This is short-hand for [Allocator.allocate] in most implementations.
Expand Down
139 changes: 116 additions & 23 deletions lib/src/specs/expression.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ 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';
Expand All @@ -25,31 +26,77 @@ abstract class Expression implements Spec {
@override
R accept<R>(covariant ExpressionVisitor<R> visitor, [R context]);

/// Returns the expression as a valid [Code] block.
Code asCode() => new _AsExpressionCode(this);

/// Returns the result of [this] `&&` [other].
Expression and(Expression other) {
return new BinaryExpression._(toExpression(), other, '&&');
}

/// Call this expression as a method.
Expression call() {
return new InvokeExpression._(this);
Expression call(
List<Expression> positionalArguments, [
Map<String, Expression> namedArguments = const {},
]) {
return new InvokeExpression._(
this,
positionalArguments,
namedArguments,
);
}

/// 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() {
return new InvokeExpression._new(this);
Expression newInstance(
List<Expression> positionalArguments, [
Map<String, Expression> namedArguments = const {},
]) {
return new InvokeExpression._new(
this,
positionalArguments,
namedArguments,
);
}

/// Returns a const instance of this expression.
Expression constInstance() {
return new InvokeExpression._const(this);
Expression constInstance(
List<Expression> positionalArguments, [
Map<String, Expression> namedArguments = const {},
]) {
return new InvokeExpression._const(
this,
positionalArguments,
namedArguments,
);
}

/// May be overriden to support other types implementing [Expression].
@visibleForOverriding
Expression toExpression() => this;
}

/// Represents a [code] block that wraps an [Expression].
class _AsExpressionCode implements Code {
final Expression code;

const _AsExpressionCode(this.code);

@override
R accept<R>(CodeVisitor<R> visitor, [R context]) {
return code.accept(visitor as ExpressionVisitor<R>, context);
}
}

/// Knowledge of different types of expressions in Dart.
///
/// **INTERNAL ONLY**.
Expand All @@ -59,6 +106,7 @@ abstract class ExpressionVisitor<T> implements SpecVisitor<T> {
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].
Expand All @@ -68,12 +116,16 @@ abstract class ExpressionEmitter implements ExpressionVisitor<StringSink> {
@override
visitBinaryExpression(BinaryExpression expression, [StringSink output]) {
output ??= new StringBuffer();
return output
..write(expression.left.accept(this))
..write(' ')
..write(expression.operator)
..write(' ')
..write(expression.right.accept(this));
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
Expand All @@ -95,7 +147,19 @@ abstract class ExpressionEmitter implements ExpressionVisitor<StringSink> {
break;
}
expression.target.accept(this, output);
return 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
Expand All @@ -104,6 +168,14 @@ abstract class ExpressionEmitter implements ExpressionVisitor<StringSink> {
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, [
Expand All @@ -119,18 +191,39 @@ abstract class ExpressionEmitter implements ExpressionVisitor<StringSink> {
output.write('>');
}
output.write('[');
// ignore: prefer_final_locals
for (var i = 0, l = expression.values.length; i < l; i++) {
final value = expression.values[i];
if (value is Spec) {
value.accept(this, output);
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 {
literal(value).accept(this, output);
}
if (i < l - 1) {
output.write(', ');
expression.valueType.accept(this, output);
}
output.write('>');
}
return 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('}');
}
}
8 changes: 7 additions & 1 deletion lib/src/specs/expression/binary.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@ class BinaryExpression extends Expression {
final Expression left;
final Expression right;
final String operator;
final bool addSpace;

const BinaryExpression._(this.left, this.right, this.operator);
const BinaryExpression._(
this.left,
this.right,
this.operator, [
this.addSpace = true,
]);

@override
R accept<R>(ExpressionVisitor<R> visitor, [R context]) {
Expand Down
Loading

0 comments on commit 1aacdfa

Please sign in to comment.