Added DSX syntax to Jael

This commit is contained in:
Tobe O 2018-06-26 11:29:03 -04:00
parent 0e600a1e59
commit 3402e5a0db
10 changed files with 109 additions and 28 deletions

View file

@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="tests in dsx_test.dart" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true" nameIsGenerated="true">
<option name="filePath" value="$PROJECT_DIR$/jael/test/render/dsx_test.dart" />
<option name="testRunnerOptions" value="-j4" />
<method />
</configuration>
</component>

View file

@ -13,6 +13,7 @@
<content url="file://$MODULE_DIR$/dsx" />
<content url="file://$MODULE_DIR$/dsx_generator" />
<content url="file://$MODULE_DIR$/jael">
<excludeFolder url="file://$MODULE_DIR$/jael/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/jael/.pub" />
<excludeFolder url="file://$MODULE_DIR$/jael/build" />
</content>

View file

@ -34,7 +34,7 @@ void myFunction() {
''';
var buf = new CodeBuffer();
var document = jael.parseDocument(template, sourceUrl: 'test.jl');
var document = jael.parseDocument(template, sourceUrl: 'test.jl', asDSX: false);
var scope = new SymbolTable(values: {
'profile': {
'avatar': 'thosakwe.png',

View file

@ -33,8 +33,13 @@ abstract class Element extends ElementChild {
];
Identifier get tagName;
Iterable<Attribute> get attributes;
Iterable<ElementChild> get children;
Attribute getAttribute(String name) =>
attributes.firstWhere((a) => a.name == name, orElse: () => null);
}
class SelfClosingElement extends Element {

View file

@ -34,8 +34,8 @@ enum TokenType {
*/
lBracket,
rBracket,
doubleCurlyL,
doubleCurlyR,
lDoubleCurly,
rDoubleCurly,
lCurly,
rCurly,
lParen,

View file

@ -7,8 +7,8 @@ import 'text/scanner.dart';
/// Parses a Jael document.
Document parseDocument(String text,
{sourceUrl, void onError(JaelError error)}) {
var scanner = scan(text, sourceUrl: sourceUrl);
{sourceUrl, bool asDSX: false, void onError(JaelError error)}) {
var scanner = scan(text, sourceUrl: sourceUrl, asDSX: asDSX);
//scanner.tokens.forEach(print);
@ -16,7 +16,7 @@ Document parseDocument(String text,
scanner.errors.forEach(onError);
else if (scanner.errors.isNotEmpty) throw scanner.errors.first;
var parser = new Parser(scanner);
var parser = new Parser(scanner, asDSX: asDSX);
var doc = parser.parseDocument();
if (parser.errors.isNotEmpty && onError != null)

View file

@ -5,11 +5,12 @@ import 'scanner.dart';
class Parser {
final List<JaelError> errors = [];
final Scanner scanner;
final bool asDSX;
Token _current;
int _index = -1;
Parser(this.scanner);
Parser(this.scanner, {this.asDSX: false});
Token get current => _current;
@ -148,7 +149,7 @@ class Parser {
Text parseText() => next(TokenType.text) ? new Text(_current) : null;
Interpolation parseInterpolation() {
if (!next(TokenType.doubleCurlyL)) return null;
if (!next(asDSX ? TokenType.lCurly : TokenType.lDoubleCurly)) return null;
var doubleCurlyL = _current;
var expression = parseExpression(0);
@ -159,9 +160,10 @@ class Parser {
return null;
}
if (!next(TokenType.doubleCurlyR)) {
if (!next(asDSX ? TokenType.rCurly : TokenType.rDoubleCurly)) {
var expected = asDSX ? '}' : '}}';
errors.add(new JaelError(JaelErrorSeverity.error,
'Missing closing "}}" in interpolation.', expression.span));
'Missing closing "$expected" in interpolation.', expression.span));
return null;
}
@ -260,7 +262,8 @@ class Parser {
if (tagName2.name != tagName.name) {
errors.add(new JaelError(
JaelErrorSeverity.error,
'Mismatched closing tags. Expected "${tagName.span.text}"; got "${tagName2.name}" instead.',
'Mismatched closing tags. Expected "${tagName.span
.text}"; got "${tagName2.name}" instead.',
lt2.span));
return null;
}
@ -291,12 +294,13 @@ class Parser {
if (next(TokenType.equals)) {
equals = _current;
} else if (next(TokenType.nequ)) {
} else if (!asDSX && next(TokenType.nequ)) {
nequ = _current;
} else {
return new Attribute(id, string, null, null, null);
}
if (!asDSX) {
var value = parseExpression(0);
if (value == null) {
@ -306,6 +310,25 @@ class Parser {
}
return new Attribute(id, string, equals, nequ, value);
} else {
// Find either a string, or an interpolation.
var value = implicitString();
if (value != null) {
return new Attribute(id, string, equals, nequ, value);
}
var interpolation = parseInterpolation();
if (interpolation != null) {
return new Attribute(
id, string, equals, nequ, interpolation.expression);
}
errors.add(new JaelError(JaelErrorSeverity.error,
'Missing expression in attribute.', equals?.span ?? nequ.span));
return null;
}
}
Expression parseExpression(int precedence) {

View file

@ -12,7 +12,8 @@ final RegExp _string1 = new RegExp(
final RegExp _string2 = new RegExp(
r'"((\\(["\\/bfnrt]|(u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])))|([^"\\]))*"');
Scanner scan(String text, {sourceUrl}) => new _Scanner(text, sourceUrl)..scan();
Scanner scan(String text, {sourceUrl, bool asDSX: false}) =>
new _Scanner(text, sourceUrl)..scan(asDSX: asDSX);
abstract class Scanner {
List<JaelError> get errors;
@ -24,8 +25,8 @@ final RegExp _htmlComment = new RegExp(r'<!--[^$]*-->');
final Map<Pattern, TokenType> _expressionPatterns = {
//final Map<Pattern, TokenType> _htmlPatterns = {
'{{': TokenType.doubleCurlyL,
'{{-': TokenType.doubleCurlyL,
'{{': TokenType.lDoubleCurly,
'{{-': TokenType.lDoubleCurly,
//
_htmlComment: TokenType.htmlComment,
@ -42,7 +43,7 @@ final Map<Pattern, TokenType> _expressionPatterns = {
//};
//final Map<Pattern, TokenType> _expressionPatterns = {
'}}': TokenType.doubleCurlyR,
'}}': TokenType.rDoubleCurly,
// Keywords
'new': TokenType.$new,
@ -92,10 +93,10 @@ class _Scanner implements Scanner {
_scanner = new SpanScanner(text, sourceUrl: sourceUrl);
}
void scan() {
void scan({bool asDSX: false}) {
while (!_scanner.isDone) {
if (state == _ScannerState.html) {
scanHtml();
scanHtml(asDSX);
} else if (state == _ScannerState.freeText) {
// Just keep parsing until we hit "</"
var start = _scanner.state, end = start;
@ -104,8 +105,8 @@ class _Scanner implements Scanner {
// Skip through comments
if (_scanner.scan(_htmlComment)) continue;
// Break on {{
if (_scanner.matches('{{')) {
// Break on {{ or {
if (_scanner.matches(asDSX ? '{' : '{{')) {
state = _ScannerState.html;
//_scanner.position--;
break;
@ -169,7 +170,7 @@ class _Scanner implements Scanner {
}
}
void scanHtml() {
void scanHtml(bool asDSX) {
var brackets = new Queue<Token>();
do {
@ -247,7 +248,8 @@ class _Scanner implements Scanner {
break;
}
}
} else if (token.type == TokenType.doubleCurlyR) {
} else if (token.type ==
(asDSX ? TokenType.rCurly : TokenType.rDoubleCurly)) {
state = _ScannerState.freeText;
break;
}

View file

@ -0,0 +1,43 @@
import 'package:jael/jael.dart';
import 'package:symbol_table/symbol_table.dart';
import 'package:test/test.dart';
void main() {
test('attributes', () {
var doc = parseDSX('''
<foo bar="baz" yes={no} />
''');
var foo = doc.root as SelfClosingElement;
expect(foo.tagName.name, 'foo');
expect(foo.attributes, hasLength(2));
expect(foo.getAttribute('bar'), isNotNull);
expect(foo.getAttribute('yes'), isNotNull);
expect(foo.getAttribute('bar').value.compute(null), 'baz');
expect(
foo
.getAttribute('yes')
.value
.compute(new SymbolTable(values: {'no': 'maybe'})),
'maybe');
});
test('children', () {
var doc = parseDSX('''
<foo bar="baz" yes={no}>
<bar>{24 * 3}</bar>
</foo>
''');
var bar = doc.root.children.first as RegularElement;
expect(bar.tagName.name, 'bar');
var interpolation = bar.children.first as Interpolation;
expect(interpolation.expression.compute(null), 24 * 3);
});
}
Document parseDSX(String text) {
return parseDocument(text,
sourceUrl: 'test.dsx', asDSX: true, onError: (e) => throw e);
}

View file

@ -64,13 +64,13 @@ main() {
expect(tokens[6], isToken(TokenType.number, '2'));
expect(tokens[7], isToken(TokenType.gt));
expect(tokens[8], isToken(TokenType.text, 'three'));
expect(tokens[9], isToken(TokenType.doubleCurlyL));
expect(tokens[9], isToken(TokenType.lDoubleCurly));
expect(tokens[10], isToken(TokenType.id, 'four'));
expect(tokens[11], isToken(TokenType.gt));
expect(tokens[12], isToken(TokenType.id, 'five'));
expect(tokens[13], isToken(TokenType.dot));
expect(tokens[14], isToken(TokenType.id, 'six'));
expect(tokens[15], isToken(TokenType.doubleCurlyR));
expect(tokens[15], isToken(TokenType.rDoubleCurly));
expect(tokens[16], isToken(TokenType.lt));
expect(tokens[17], isToken(TokenType.slash));
expect(tokens[18], isToken(TokenType.id, 'ul'));