diff --git a/.idea/runConfigurations/tests_in_jael.xml b/.idea/runConfigurations/tests_in_jael.xml new file mode 100644 index 00000000..b905e806 --- /dev/null +++ b/.idea/runConfigurations/tests_in_jael.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/jael/analysis_options.yaml b/jael/analysis_options.yaml index 518eb901..eae1e42a 100644 --- a/jael/analysis_options.yaml +++ b/jael/analysis_options.yaml @@ -1,2 +1,3 @@ analyzer: - strong-mode: true \ No newline at end of file + strong-mode: + implicit-casts: false \ No newline at end of file diff --git a/jael/lib/src/ast/call.dart b/jael/lib/src/ast/call.dart index 9d932727..196d7877 100644 --- a/jael/lib/src/ast/call.dart +++ b/jael/lib/src/ast/call.dart @@ -38,7 +38,7 @@ class Call extends Expression { var args = computePositional(scope); var named = computeNamed(scope); - return Function.apply(callee, args, named); + return Function.apply(callee as Function, args, named); } } diff --git a/jael/lib/src/ast/conditional.dart b/jael/lib/src/ast/conditional.dart index 871eb4f8..c3697d84 100644 --- a/jael/lib/src/ast/conditional.dart +++ b/jael/lib/src/ast/conditional.dart @@ -20,7 +20,7 @@ class Conditional extends Expression { @override compute(scope) { - return condition.compute(scope) + return (condition.compute(scope) == true) ? ifTrue.compute(scope) : ifFalse.compute(scope); } diff --git a/jael/lib/src/ast/expression.dart b/jael/lib/src/ast/expression.dart index 2eecb1f6..24b0e7b4 100644 --- a/jael/lib/src/ast/expression.dart +++ b/jael/lib/src/ast/expression.dart @@ -22,6 +22,6 @@ class Negation extends Expression { @override compute(SymbolTable scope) { - return !(expression.compute(scope)); + return !(expression.compute(scope) == true); } } diff --git a/jael/lib/src/ast/identifier.dart b/jael/lib/src/ast/identifier.dart index 923a7ca5..47e2f76d 100644 --- a/jael/lib/src/ast/identifier.dart +++ b/jael/lib/src/ast/identifier.dart @@ -33,3 +33,16 @@ class Identifier extends Expression { @override FileSpan get span => id.span; } + +class SyntheticIdentifier extends Identifier { + @override + final String name; + + SyntheticIdentifier(this.name, [Token token]) : super(token); + + @override + FileSpan get span { + if (id != null) return id.span; + throw new UnsupportedError('Cannot get the span of a SyntheticIdentifier.'); + } +} diff --git a/jael/lib/src/ast/new.dart b/jael/lib/src/ast/new.dart index 6b801f17..d29b1e45 100644 --- a/jael/lib/src/ast/new.dart +++ b/jael/lib/src/ast/new.dart @@ -24,7 +24,7 @@ class NewExpression extends Expression { if (call.target is MemberExpression) name = (call.target as MemberExpression).name.name; - return reflectClass(targetType) + return reflectClass(targetType as Type) .newInstance(new Symbol(name), positional, named) .reflectee; } diff --git a/jael/lib/src/renderer.dart b/jael/lib/src/renderer.dart index a563fa37..8218372e 100644 --- a/jael/lib/src/renderer.dart +++ b/jael/lib/src/renderer.dart @@ -107,6 +107,10 @@ class Renderer { } else if (element.tagName.name == 'element') { registerCustomElement(element, buffer, childScope, html5); return; + } else if (scope.resolve(customElementName(element.tagName.name))?.value + is Element) { + renderCustomElement(element, buffer, childScope, html5); + return; } buffer..write('<')..write(element.tagName.name); @@ -171,7 +175,7 @@ class Renderer { .firstWhere((a) => a.name == 'as', orElse: () => null); var indexAsAttribute = element.attributes .firstWhere((a) => a.name == 'index-as', orElse: () => null); - var alias = asAttribute?.value?.compute(scope) ?? 'item'; + var alias = asAttribute?.value?.compute(scope)?.toString() ?? 'item'; var indexAs = indexAsAttribute?.value?.compute(scope)?.toString() ?? 'item'; var otherAttributes = element.attributes.where( (a) => a.name != 'for-each' && a.name != 'as' && a.name != 'index-as'); @@ -302,12 +306,67 @@ class Renderer { } } + static String customElementName(String name) => 'elements@$name'; + void registerCustomElement( Element element, CodeBuffer buffer, SymbolTable scope, bool html5) { + if (element is! RegularElement) { + throw new JaelError(JaelErrorSeverity.error, + "Custom elements cannot be self-closing.", element.span); + } + var name = element.getAttribute('name')?.value?.compute(scope)?.toString(); - if (name) { + if (name == null) { + throw new JaelError( + JaelErrorSeverity.error, + "Attribute 'name' is required when registering a custom element.", + element.tagName.span); + } + try { + scope.create(customElementName(name), value: element, constant: true); + } on StateError { + throw new JaelError( + JaelErrorSeverity.error, + "Cannot re-define element '$name' in this scope.", + element.getAttribute('name').span); + } + } + + void renderCustomElement( + Element element, CodeBuffer buffer, SymbolTable scope, bool html5) { + var template = scope.resolve(customElementName(element.tagName.name)).value + as RegularElement; + var renderAs = element.getAttribute('as')?.value?.compute(scope); + var attrs = element.attributes.where((a) => a.name != 'as'); + + for (var attribute in attrs) { + scope.create(attribute.name, + value: attribute.value?.compute(scope), constant: true); + } + + if (renderAs == false) { + for (int i = 0; i < template.children.length; i++) { + var child = template.children.elementAt(i); + renderElementChild( + element, child, buffer, scope, html5, i, element.children.length); + } + } else { + var tagName = renderAs?.toString() ?? 'div'; + + var syntheticElement = new RegularElement( + template.lt, + new SyntheticIdentifier(tagName), + [], + template.gt, + template.children, + template.lt2, + template.slash, + new SyntheticIdentifier(tagName), + template.gt2); + + renderElement(syntheticElement, buffer, scope, html5); } } } diff --git a/jael/lib/src/text/parselet/prefix.dart b/jael/lib/src/text/parselet/prefix.dart index 4b3f3a50..0dfb0bc8 100644 --- a/jael/lib/src/text/parselet/prefix.dart +++ b/jael/lib/src/text/parselet/prefix.dart @@ -41,14 +41,14 @@ class NewParselet implements PrefixParselet { '"new" must precede a call expression. Nothing was found.', call.span)); return null; - } else if (call is! Call) { + } else if (call is Call) { + return new NewExpression(token, call); + } else { parser.errors.add(new JaelError( JaelErrorSeverity.error, '"new" must precede a call expression, not a(n) ${call.runtimeType}.', call.span)); return null; - } else { - return new NewExpression(token, call); } } } diff --git a/jael/lib/src/text/parser.dart b/jael/lib/src/text/parser.dart index 3a162072..5f8de467 100644 --- a/jael/lib/src/text/parser.dart +++ b/jael/lib/src/text/parser.dart @@ -68,8 +68,10 @@ class Parser { StringLiteral implicitString() { if (next(TokenType.string)) { - return prefixParselets[TokenType.string].parse(this, _current); - } else if (next(TokenType.text)) {} + return prefixParselets[TokenType.string].parse(this, _current) as StringLiteral; + } /*else if (next(TokenType.text)) { + + }*/ return null; } @@ -118,7 +120,7 @@ class Parser { return null; } - var name = stringParser.parse(this, _current); + var name = stringParser.parse(this, _current) as StringLiteral; if (!next(TokenType.string)) { errors.add(new JaelError(JaelErrorSeverity.error, @@ -126,7 +128,7 @@ class Parser { return null; } - var url = stringParser.parse(this, _current); + var url = stringParser.parse(this, _current) as StringLiteral; if (!next(TokenType.gt)) { errors.add(new JaelError(JaelErrorSeverity.error,