Added DSX syntax to Jael
This commit is contained in:
parent
0e600a1e59
commit
3402e5a0db
10 changed files with 109 additions and 28 deletions
7
.idea/runConfigurations/tests_in_dsx_test_dart.xml
Normal file
7
.idea/runConfigurations/tests_in_dsx_test_dart.xml
Normal 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>
|
1
jael.iml
1
jael.iml
|
@ -13,6 +13,7 @@
|
||||||
<content url="file://$MODULE_DIR$/dsx" />
|
<content url="file://$MODULE_DIR$/dsx" />
|
||||||
<content url="file://$MODULE_DIR$/dsx_generator" />
|
<content url="file://$MODULE_DIR$/dsx_generator" />
|
||||||
<content url="file://$MODULE_DIR$/jael">
|
<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/.pub" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/jael/build" />
|
<excludeFolder url="file://$MODULE_DIR$/jael/build" />
|
||||||
</content>
|
</content>
|
||||||
|
|
|
@ -34,7 +34,7 @@ void myFunction() {
|
||||||
''';
|
''';
|
||||||
|
|
||||||
var buf = new CodeBuffer();
|
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: {
|
var scope = new SymbolTable(values: {
|
||||||
'profile': {
|
'profile': {
|
||||||
'avatar': 'thosakwe.png',
|
'avatar': 'thosakwe.png',
|
||||||
|
|
|
@ -33,8 +33,13 @@ abstract class Element extends ElementChild {
|
||||||
];
|
];
|
||||||
|
|
||||||
Identifier get tagName;
|
Identifier get tagName;
|
||||||
|
|
||||||
Iterable<Attribute> get attributes;
|
Iterable<Attribute> get attributes;
|
||||||
|
|
||||||
Iterable<ElementChild> get children;
|
Iterable<ElementChild> get children;
|
||||||
|
|
||||||
|
Attribute getAttribute(String name) =>
|
||||||
|
attributes.firstWhere((a) => a.name == name, orElse: () => null);
|
||||||
}
|
}
|
||||||
|
|
||||||
class SelfClosingElement extends Element {
|
class SelfClosingElement extends Element {
|
||||||
|
|
|
@ -34,8 +34,8 @@ enum TokenType {
|
||||||
*/
|
*/
|
||||||
lBracket,
|
lBracket,
|
||||||
rBracket,
|
rBracket,
|
||||||
doubleCurlyL,
|
lDoubleCurly,
|
||||||
doubleCurlyR,
|
rDoubleCurly,
|
||||||
lCurly,
|
lCurly,
|
||||||
rCurly,
|
rCurly,
|
||||||
lParen,
|
lParen,
|
||||||
|
|
|
@ -7,8 +7,8 @@ import 'text/scanner.dart';
|
||||||
|
|
||||||
/// Parses a Jael document.
|
/// Parses a Jael document.
|
||||||
Document parseDocument(String text,
|
Document parseDocument(String text,
|
||||||
{sourceUrl, void onError(JaelError error)}) {
|
{sourceUrl, bool asDSX: false, void onError(JaelError error)}) {
|
||||||
var scanner = scan(text, sourceUrl: sourceUrl);
|
var scanner = scan(text, sourceUrl: sourceUrl, asDSX: asDSX);
|
||||||
|
|
||||||
//scanner.tokens.forEach(print);
|
//scanner.tokens.forEach(print);
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ Document parseDocument(String text,
|
||||||
scanner.errors.forEach(onError);
|
scanner.errors.forEach(onError);
|
||||||
else if (scanner.errors.isNotEmpty) throw scanner.errors.first;
|
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();
|
var doc = parser.parseDocument();
|
||||||
|
|
||||||
if (parser.errors.isNotEmpty && onError != null)
|
if (parser.errors.isNotEmpty && onError != null)
|
||||||
|
|
|
@ -5,11 +5,12 @@ import 'scanner.dart';
|
||||||
class Parser {
|
class Parser {
|
||||||
final List<JaelError> errors = [];
|
final List<JaelError> errors = [];
|
||||||
final Scanner scanner;
|
final Scanner scanner;
|
||||||
|
final bool asDSX;
|
||||||
|
|
||||||
Token _current;
|
Token _current;
|
||||||
int _index = -1;
|
int _index = -1;
|
||||||
|
|
||||||
Parser(this.scanner);
|
Parser(this.scanner, {this.asDSX: false});
|
||||||
|
|
||||||
Token get current => _current;
|
Token get current => _current;
|
||||||
|
|
||||||
|
@ -148,7 +149,7 @@ class Parser {
|
||||||
Text parseText() => next(TokenType.text) ? new Text(_current) : null;
|
Text parseText() => next(TokenType.text) ? new Text(_current) : null;
|
||||||
|
|
||||||
Interpolation parseInterpolation() {
|
Interpolation parseInterpolation() {
|
||||||
if (!next(TokenType.doubleCurlyL)) return null;
|
if (!next(asDSX ? TokenType.lCurly : TokenType.lDoubleCurly)) return null;
|
||||||
var doubleCurlyL = _current;
|
var doubleCurlyL = _current;
|
||||||
|
|
||||||
var expression = parseExpression(0);
|
var expression = parseExpression(0);
|
||||||
|
@ -159,9 +160,10 @@ class Parser {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!next(TokenType.doubleCurlyR)) {
|
if (!next(asDSX ? TokenType.rCurly : TokenType.rDoubleCurly)) {
|
||||||
|
var expected = asDSX ? '}' : '}}';
|
||||||
errors.add(new JaelError(JaelErrorSeverity.error,
|
errors.add(new JaelError(JaelErrorSeverity.error,
|
||||||
'Missing closing "}}" in interpolation.', expression.span));
|
'Missing closing "$expected" in interpolation.', expression.span));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,7 +262,8 @@ class Parser {
|
||||||
if (tagName2.name != tagName.name) {
|
if (tagName2.name != tagName.name) {
|
||||||
errors.add(new JaelError(
|
errors.add(new JaelError(
|
||||||
JaelErrorSeverity.error,
|
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));
|
lt2.span));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -291,12 +294,13 @@ class Parser {
|
||||||
|
|
||||||
if (next(TokenType.equals)) {
|
if (next(TokenType.equals)) {
|
||||||
equals = _current;
|
equals = _current;
|
||||||
} else if (next(TokenType.nequ)) {
|
} else if (!asDSX && next(TokenType.nequ)) {
|
||||||
nequ = _current;
|
nequ = _current;
|
||||||
} else {
|
} else {
|
||||||
return new Attribute(id, string, null, null, null);
|
return new Attribute(id, string, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!asDSX) {
|
||||||
var value = parseExpression(0);
|
var value = parseExpression(0);
|
||||||
|
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
|
@ -306,6 +310,25 @@ class Parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Attribute(id, string, equals, nequ, value);
|
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) {
|
Expression parseExpression(int precedence) {
|
||||||
|
|
|
@ -12,7 +12,8 @@ final RegExp _string1 = new RegExp(
|
||||||
final RegExp _string2 = 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])))|([^"\\]))*"');
|
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 {
|
abstract class Scanner {
|
||||||
List<JaelError> get errors;
|
List<JaelError> get errors;
|
||||||
|
@ -24,8 +25,8 @@ final RegExp _htmlComment = new RegExp(r'<!--[^$]*-->');
|
||||||
|
|
||||||
final Map<Pattern, TokenType> _expressionPatterns = {
|
final Map<Pattern, TokenType> _expressionPatterns = {
|
||||||
//final Map<Pattern, TokenType> _htmlPatterns = {
|
//final Map<Pattern, TokenType> _htmlPatterns = {
|
||||||
'{{': TokenType.doubleCurlyL,
|
'{{': TokenType.lDoubleCurly,
|
||||||
'{{-': TokenType.doubleCurlyL,
|
'{{-': TokenType.lDoubleCurly,
|
||||||
|
|
||||||
//
|
//
|
||||||
_htmlComment: TokenType.htmlComment,
|
_htmlComment: TokenType.htmlComment,
|
||||||
|
@ -42,7 +43,7 @@ final Map<Pattern, TokenType> _expressionPatterns = {
|
||||||
//};
|
//};
|
||||||
|
|
||||||
//final Map<Pattern, TokenType> _expressionPatterns = {
|
//final Map<Pattern, TokenType> _expressionPatterns = {
|
||||||
'}}': TokenType.doubleCurlyR,
|
'}}': TokenType.rDoubleCurly,
|
||||||
|
|
||||||
// Keywords
|
// Keywords
|
||||||
'new': TokenType.$new,
|
'new': TokenType.$new,
|
||||||
|
@ -92,10 +93,10 @@ class _Scanner implements Scanner {
|
||||||
_scanner = new SpanScanner(text, sourceUrl: sourceUrl);
|
_scanner = new SpanScanner(text, sourceUrl: sourceUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
void scan() {
|
void scan({bool asDSX: false}) {
|
||||||
while (!_scanner.isDone) {
|
while (!_scanner.isDone) {
|
||||||
if (state == _ScannerState.html) {
|
if (state == _ScannerState.html) {
|
||||||
scanHtml();
|
scanHtml(asDSX);
|
||||||
} else if (state == _ScannerState.freeText) {
|
} else if (state == _ScannerState.freeText) {
|
||||||
// Just keep parsing until we hit "</"
|
// Just keep parsing until we hit "</"
|
||||||
var start = _scanner.state, end = start;
|
var start = _scanner.state, end = start;
|
||||||
|
@ -104,8 +105,8 @@ class _Scanner implements Scanner {
|
||||||
// Skip through comments
|
// Skip through comments
|
||||||
if (_scanner.scan(_htmlComment)) continue;
|
if (_scanner.scan(_htmlComment)) continue;
|
||||||
|
|
||||||
// Break on {{
|
// Break on {{ or {
|
||||||
if (_scanner.matches('{{')) {
|
if (_scanner.matches(asDSX ? '{' : '{{')) {
|
||||||
state = _ScannerState.html;
|
state = _ScannerState.html;
|
||||||
//_scanner.position--;
|
//_scanner.position--;
|
||||||
break;
|
break;
|
||||||
|
@ -169,7 +170,7 @@ class _Scanner implements Scanner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void scanHtml() {
|
void scanHtml(bool asDSX) {
|
||||||
var brackets = new Queue<Token>();
|
var brackets = new Queue<Token>();
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
@ -247,7 +248,8 @@ class _Scanner implements Scanner {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (token.type == TokenType.doubleCurlyR) {
|
} else if (token.type ==
|
||||||
|
(asDSX ? TokenType.rCurly : TokenType.rDoubleCurly)) {
|
||||||
state = _ScannerState.freeText;
|
state = _ScannerState.freeText;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
43
jael/test/render/dsx_test.dart
Normal file
43
jael/test/render/dsx_test.dart
Normal 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);
|
||||||
|
}
|
|
@ -64,13 +64,13 @@ main() {
|
||||||
expect(tokens[6], isToken(TokenType.number, '2'));
|
expect(tokens[6], isToken(TokenType.number, '2'));
|
||||||
expect(tokens[7], isToken(TokenType.gt));
|
expect(tokens[7], isToken(TokenType.gt));
|
||||||
expect(tokens[8], isToken(TokenType.text, 'three'));
|
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[10], isToken(TokenType.id, 'four'));
|
||||||
expect(tokens[11], isToken(TokenType.gt));
|
expect(tokens[11], isToken(TokenType.gt));
|
||||||
expect(tokens[12], isToken(TokenType.id, 'five'));
|
expect(tokens[12], isToken(TokenType.id, 'five'));
|
||||||
expect(tokens[13], isToken(TokenType.dot));
|
expect(tokens[13], isToken(TokenType.dot));
|
||||||
expect(tokens[14], isToken(TokenType.id, 'six'));
|
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[16], isToken(TokenType.lt));
|
||||||
expect(tokens[17], isToken(TokenType.slash));
|
expect(tokens[17], isToken(TokenType.slash));
|
||||||
expect(tokens[18], isToken(TokenType.id, 'ul'));
|
expect(tokens[18], isToken(TokenType.id, 'ul'));
|
||||||
|
|
Loading…
Reference in a new issue