Skip to content

Commit

Permalink
Add Block + BlockBuilder, some utilities. (#134)
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.

* Add blocks of code (statements).
  • Loading branch information
matanlurey committed Oct 13, 2017
1 parent 1aacdfa commit a9d0ce5
Show file tree
Hide file tree
Showing 9 changed files with 228 additions and 8 deletions.
4 changes: 3 additions & 1 deletion lib/code_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ export 'src/emitter.dart' show DartEmitter;
export 'src/matchers.dart' show equalsDart;
export 'src/specs/annotation.dart' show Annotation, AnnotationBuilder;
export 'src/specs/class.dart' show Class, ClassBuilder;
export 'src/specs/code.dart' show Code, StaticCode, ScopedCode;
export 'src/specs/code.dart'
show Block, BlockBuilder, Code, StaticCode, ScopedCode;
export 'src/specs/constructor.dart' show Constructor, ConstructorBuilder;
export 'src/specs/directive.dart'
show Directive, DirectiveType, DirectiveBuilder;
export 'src/specs/expression.dart'
show
AsCodeExpression,
BinaryExpression,
CodeExpression,
Expression,
Expand Down
5 changes: 3 additions & 2 deletions lib/src/emitter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import 'visitors.dart';
///
/// If [elements] is at least 2 elements, inserts [separator] delimiting them.
@visibleForTesting
void visitAll<T>(
StringSink visitAll<T>(
Iterable<T> elements,
StringSink output,
void visit(T element), [
Expand All @@ -36,14 +36,15 @@ void visitAll<T>(
//
// ... which would allocate more StringBuffer(s) for a one-time use.
if (elements.isEmpty) {
return;
return output;
}
final iterator = elements.iterator..moveNext();
visit(iterator.current);
while (iterator.moveNext()) {
output.write(separator);
visit(iterator.current);
}
return output;
}

class DartEmitter extends Object
Expand Down
2 changes: 1 addition & 1 deletion lib/src/matchers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ String _dartfmt(String source) {
} catch (_) {
// Ignored on purpose, probably not exactly valid Dart code.
} finally {
source = source.replaceAll(' ', ' ').replaceAll('\n', '').trim();
source = collapseWhitespace(source);
}
return source;
}
Expand Down
50 changes: 50 additions & 0 deletions lib/src/specs/code.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@

library code_builder.src.specs.code;

import 'package:built_value/built_value.dart';
import 'package:built_collection/built_collection.dart';
import 'package:meta/meta.dart';

import '../allocator.dart';
import '../base.dart';
import '../emitter.dart';
import '../visitors.dart';

import 'expression.dart';
import 'reference.dart';

part 'code.g.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 Expand Up @@ -41,10 +47,40 @@ abstract class Code implements Spec {
R accept<R>(covariant CodeVisitor<R> visitor, [R context]);
}

/// Represents blocks of statements of Dart code.
abstract class Block implements Built<Block, BlockBuilder>, Spec {
factory Block([void updates(BlockBuilder b)]) = _$Block;

Block._();

@override
R accept<R>(covariant CodeVisitor<R> visitor, [R context]) {
return visitor.visitBlock(this, context);
}

BuiltList<Code> get statements;
}

abstract class BlockBuilder implements Builder<Block, BlockBuilder> {
factory BlockBuilder() = _$BlockBuilder;

BlockBuilder._();

/// Adds an [expression] to [statements].
///
/// **NOTE**: Not all expressions are _useful_ statements.
void addExpression(Expression expression) {
statements.add(expression.asStatement());
}

ListBuilder<Code> statements = new ListBuilder<Code>();
}

/// Knowledge of different types of blocks of code in Dart.
///
/// **INTERNAL ONLY**.
abstract class CodeVisitor<T> implements SpecVisitor<T> {
T visitBlock(Block code, [T context]);
T visitStaticCode(StaticCode code, [T context]);
T visitScopedCode(ScopedCode code, [T context]);
}
Expand All @@ -54,6 +90,14 @@ abstract class CodeEmitter implements CodeVisitor<StringSink> {
@protected
Allocator get allocator;

@override
visitBlock(Block block, [StringSink output]) {
output ??= new StringBuffer();
return visitAll<Code>(block.statements, output, (statement) {
statement.accept(this, output);
}, '\n');
}

@override
visitStaticCode(StaticCode code, [StringSink output]) {
output ??= new StringBuffer();
Expand All @@ -77,6 +121,9 @@ class StaticCode implements Code {
R accept<R>(CodeVisitor<R> visitor, [R context]) {
return visitor.visitStaticCode(this, context);
}

@override
String toString() => code;
}

/// Represents a [code] block that may require scoping.
Expand All @@ -89,4 +136,7 @@ class ScopedCode implements Code {
R accept<R>(CodeVisitor<R> visitor, [R context]) {
return visitor.visitScopedCode(this, context);
}

@override
String toString() => code((ref) => ref.symbol);
}
92 changes: 92 additions & 0 deletions lib/src/specs/code.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 28 additions & 4 deletions lib/src/specs/expression.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ abstract class Expression implements Spec {
R accept<R>(covariant ExpressionVisitor<R> visitor, [R context]);

/// Returns the expression as a valid [Code] block.
Code asCode() => new _AsExpressionCode(this);
///
/// 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) {
Expand Down Expand Up @@ -86,21 +91,30 @@ abstract class Expression implements Spec {
}

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

const _AsExpressionCode(this.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 code.accept(visitor as ExpressionVisitor<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]);
Expand All @@ -113,6 +127,16 @@ abstract class ExpressionVisitor<T> implements SpecVisitor<T> {
///
/// **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();
Expand Down
4 changes: 4 additions & 0 deletions lib/src/specs/expression/invoke.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ class InvokeExpression extends Expression {
R accept<R>(ExpressionVisitor<R> visitor, [R context]) {
return visitor.visitInvokeExpression(this, context);
}

@override
String toString() =>
'${type ?? ''} $target($positionalArguments, $namedArguments)';
}

enum InvokeExpressionType {
Expand Down
9 changes: 9 additions & 0 deletions lib/src/specs/expression/literal.dart
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ class LiteralExpression extends Expression {
R accept<R>(ExpressionVisitor<R> visitor, [R context]) {
return visitor.visitLiteralExpression(this, context);
}

@override
String toString() => literal;
}

class LiteralListExpression extends Expression {
Expand All @@ -108,6 +111,9 @@ class LiteralListExpression extends Expression {
R accept<R>(ExpressionVisitor<R> visitor, [R context]) {
return visitor.visitLiteralListExpression(this, context);
}

@override
String toString() => '[${values.map(literal).join(', ')}]';
}

class LiteralMapExpression extends Expression {
Expand All @@ -127,4 +133,7 @@ class LiteralMapExpression extends Expression {
R accept<R>(ExpressionVisitor<R> visitor, [R context]) {
return visitor.visitLiteralMapExpression(this, context);
}

@override
String toString() => '{$values}';
}
38 changes: 38 additions & 0 deletions test/specs/code/statement_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// 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.

import 'package:code_builder/code_builder.dart';
import 'package:test/test.dart';

void main() {
test('should emit a block of code', () {
expect(
new Block((b) => b.statements.addAll([
const Code('if (foo) {'),
const Code(' print(true);'),
const Code('}'),
])),
equalsDart(r'''
if (foo) {
print(true);
}
'''),
);
});

test('should emit a block of code including expressions', () {
expect(
new Block((b) => b.statements.addAll([
const Code('if (foo) {'),
refer('print')([literalTrue]).asStatement(),
const Code('}'),
])),
equalsDart(r'''
if (foo) {
print(true);
}
'''),
);
});
}

0 comments on commit a9d0ce5

Please sign in to comment.