Still needs work
This commit is contained in:
parent
2056d1eacc
commit
cec21c0e7d
16 changed files with 205 additions and 14 deletions
7
.idea/runConfigurations/main_dart.xml
Normal file
7
.idea/runConfigurations/main_dart.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="main.dart" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true" nameIsGenerated="true">
|
||||
<option name="filePath" value="$PROJECT_DIR$/angel_jael/example/main.dart" />
|
||||
<option name="workingDirectory" value="$PROJECT_DIR$/angel_jael/example" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
27
angel_jael/example/main.dart
Normal file
27
angel_jael/example/main.dart
Normal file
|
@ -0,0 +1,27 @@
|
|||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_jael/angel_jael.dart';
|
||||
import 'package:file/local.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
main() async {
|
||||
var app = new Angel();
|
||||
var fileSystem = const LocalFileSystem();
|
||||
|
||||
await app.configure(
|
||||
jael(fileSystem.directory('views')),
|
||||
);
|
||||
|
||||
app.get('/', (res) => res.render('index', {'title': 'ESKETTIT'}));
|
||||
|
||||
app.use(() => throw new AngelHttpException.notFound());
|
||||
|
||||
app.logger = new Logger('angel')
|
||||
..onRecord.listen((rec) {
|
||||
print(rec);
|
||||
if (rec.error != null) print(rec.error);
|
||||
if (rec.stackTrace != null) print(rec.stackTrace);
|
||||
});
|
||||
|
||||
var server = await app.startServer(null, 3000);
|
||||
print('Listening at http://${server.address.address}:${server.port}');
|
||||
}
|
5
angel_jael/example/views/index.jl
Normal file
5
angel_jael/example/views/index.jl
Normal file
|
@ -0,0 +1,5 @@
|
|||
<extend src="layout.jl">
|
||||
<block name="content">
|
||||
Hello, world!!!
|
||||
</block>
|
||||
</extend>
|
19
angel_jael/example/views/layout.jl
Normal file
19
angel_jael/example/views/layout.jl
Normal file
|
@ -0,0 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<title>{{title}}</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>
|
||||
{{title}}
|
||||
</h1>
|
||||
<block name="content">
|
||||
<i>Content goes here.</i>
|
||||
</block>
|
||||
<script>
|
||||
window.alert("a");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
87
angel_jael/lib/angel_jael.dart
Normal file
87
angel_jael/lib/angel_jael.dart
Normal file
|
@ -0,0 +1,87 @@
|
|||
import 'dart:convert';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:code_buffer/code_buffer.dart';
|
||||
import 'package:file/file.dart';
|
||||
import 'package:jael/jael.dart';
|
||||
import 'package:jael_preprocessor/jael_preprocessor.dart';
|
||||
import 'package:symbol_table/symbol_table.dart';
|
||||
|
||||
AngelConfigurer jael(Directory viewsDirectory,
|
||||
{String fileExtension, CodeBuffer createBuffer()}) {
|
||||
fileExtension ??= '.jl';
|
||||
createBuffer ??= () => new CodeBuffer();
|
||||
|
||||
return (Angel app) async {
|
||||
app.viewGenerator = (String name, [Map locals]) async {
|
||||
var file = viewsDirectory.childFile(name + fileExtension);
|
||||
var contents = await file.readAsString();
|
||||
var errors = <JaelError>[];
|
||||
var doc =
|
||||
parseDocument(contents, sourceUrl: file.uri, onError: errors.add);
|
||||
var processed = doc;
|
||||
|
||||
try {
|
||||
processed = await resolve(doc, viewsDirectory, onError: errors.add);
|
||||
} catch (_) {
|
||||
// Ignore these errors, so that we can show syntax errors.
|
||||
}
|
||||
|
||||
var buf = createBuffer();
|
||||
var scope = new SymbolTable(values: locals ?? {});
|
||||
|
||||
if (errors.isEmpty) {
|
||||
try {
|
||||
const Renderer().render(processed, buf, scope);
|
||||
return buf.toString();
|
||||
} on JaelError catch (e) {
|
||||
errors.add(e);
|
||||
}
|
||||
}
|
||||
|
||||
buf
|
||||
..writeln('<!DOCTYPE html>')
|
||||
..writeln('<html lang="en">')
|
||||
..indent()
|
||||
..writeln('<head>')
|
||||
..indent()
|
||||
..writeln(
|
||||
'<meta name="viewport" content="width=device-width, initial-scale=1">',
|
||||
)
|
||||
..writeln('<title>${errors.length} Error(s)</title>')
|
||||
..outdent()
|
||||
..writeln('</head>')
|
||||
..writeln('<body>')
|
||||
..writeln('<h1>${errors.length} Error(s)</h1>')
|
||||
..writeln('<ul>')
|
||||
..indent();
|
||||
|
||||
for (var error in errors) {
|
||||
var type =
|
||||
error.severity == JaelErrorSeverity.warning ? 'warning' : 'error';
|
||||
buf
|
||||
..writeln('<li>')
|
||||
..indent()
|
||||
..writeln(
|
||||
'<b>$type:</b> ${error.span.start.toolString}: ${error.message}')
|
||||
..writeln('<br>')
|
||||
..writeln(
|
||||
'<span style="color: red;">' +
|
||||
HTML_ESCAPE
|
||||
.convert(error.span.highlight(color: false))
|
||||
.replaceAll('\n', '<br>') +
|
||||
'</span>',
|
||||
)
|
||||
..outdent()
|
||||
..writeln('</li>');
|
||||
}
|
||||
|
||||
buf
|
||||
..outdent()
|
||||
..writeln('</ul>')
|
||||
..writeln('</body>')
|
||||
..writeln('</html>');
|
||||
|
||||
return buf.toString();
|
||||
};
|
||||
};
|
||||
}
|
16
angel_jael/pubspec.yaml
Normal file
16
angel_jael/pubspec.yaml
Normal file
|
@ -0,0 +1,16 @@
|
|||
name: angel_jael
|
||||
dependencies:
|
||||
angel_framework: ^1.0.0-dev
|
||||
code_buffer: ^1.0.0
|
||||
file: ^2.0.0
|
||||
jael: ^1.0.0-alpha
|
||||
jael_preprocessor: ^1.0.0-alpha
|
||||
symbol_table: ^1.0.0
|
||||
dev_dependencies:
|
||||
angel_test: ^1.1.0-alpha
|
||||
test: ^0.12.0
|
||||
dependency_overrides:
|
||||
jael:
|
||||
path: ../jael
|
||||
jael_preprocessor:
|
||||
path: ../jael_preprocessor
|
5
jael.iml
5
jael.iml
|
@ -2,7 +2,10 @@
|
|||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$/angel_jael" />
|
||||
<content url="file://$MODULE_DIR$/angel_jael">
|
||||
<excludeFolder url="file://$MODULE_DIR$/angel_jael/.pub" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/angel_jael/build" />
|
||||
</content>
|
||||
<content url="file://$MODULE_DIR$/jael">
|
||||
<excludeFolder url="file://$MODULE_DIR$/jael/.pub" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/jael/build" />
|
||||
|
|
|
@ -38,7 +38,7 @@ class BinaryExpression extends Expression {
|
|||
return l ?? r;
|
||||
default:
|
||||
throw new UnsupportedError(
|
||||
'Unsupported binary operator: "${operator?.span ?? "<null>"}".');
|
||||
'Unsupported binary operator: "${operator?.span?.text ?? "<null>"}".');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,10 +10,22 @@ class Identifier extends Expression {
|
|||
|
||||
@override
|
||||
compute(SymbolTable scope) {
|
||||
var symbol = scope.resolve(name);
|
||||
if (symbol == null)
|
||||
throw new ArgumentError('The name "$name" does not exist in this scope.');
|
||||
return scope.resolve(name).value;
|
||||
switch(name) {
|
||||
case 'null':
|
||||
return null;
|
||||
case 'true':
|
||||
return true;
|
||||
case 'false':
|
||||
return false;
|
||||
default:
|
||||
var symbol = scope.resolve(name);
|
||||
if (symbol == null)
|
||||
throw new ArgumentError(
|
||||
'The name "$name" does not exist in this scope.');
|
||||
return scope
|
||||
.resolve(name)
|
||||
.value;
|
||||
}
|
||||
}
|
||||
|
||||
String get name => id.span.text;
|
||||
|
|
|
@ -7,17 +7,18 @@ import 'token.dart';
|
|||
|
||||
class MemberExpression extends Expression {
|
||||
final Expression expression;
|
||||
final Token dot;
|
||||
final Token op;
|
||||
final Identifier name;
|
||||
|
||||
MemberExpression(this.expression, this.dot, this.name);
|
||||
MemberExpression(this.expression, this.op, this.name);
|
||||
|
||||
@override
|
||||
compute(SymbolTable scope) {
|
||||
var target = expression.compute(scope);
|
||||
if (op.span.text == '?.' && target == null) return null;
|
||||
return reflect(target).getField(new Symbol(name.name)).reflectee;
|
||||
}
|
||||
|
||||
@override
|
||||
FileSpan get span => expression.span.expand(dot.span).expand(name.span);
|
||||
FileSpan get span => expression.span.expand(op.span).expand(name.span);
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ enum TokenType {
|
|||
plus,
|
||||
minus,
|
||||
elvis,
|
||||
elvis_dot,
|
||||
lte,
|
||||
gte,
|
||||
equ,
|
||||
|
@ -54,4 +55,5 @@ enum TokenType {
|
|||
number,
|
||||
hex,
|
||||
string,
|
||||
question,
|
||||
}
|
|
@ -65,7 +65,7 @@ class Renderer {
|
|||
msg = value.keys.fold<StringBuffer>(new StringBuffer(), (buf, k) {
|
||||
var v = value[k];
|
||||
if (v == null) return buf;
|
||||
return buf..write('$k=$v;');
|
||||
return buf..write('$k: $v;');
|
||||
}).toString();
|
||||
} else {
|
||||
msg = value.toString();
|
||||
|
|
|
@ -2,6 +2,7 @@ part of jael.src.text.parselet;
|
|||
|
||||
const Map<TokenType, InfixParselet> infixParselets = const {
|
||||
TokenType.lParen: const CallParselet(),
|
||||
TokenType.elvis_dot: const MemberParselet(),
|
||||
TokenType.dot: const MemberParselet(),
|
||||
TokenType.lBracket: const IndexerParselet(),
|
||||
TokenType.asterisk: const BinaryParselet(14),
|
||||
|
|
|
@ -300,6 +300,16 @@ class Parser {
|
|||
|
||||
while (precedence < _nextPrecedence()) {
|
||||
_current = scanner.tokens[++_index];
|
||||
|
||||
if (_current.type == TokenType.slash && peek()?.type == TokenType.gt) {
|
||||
// Handle `/>`
|
||||
//
|
||||
// Don't register this as an infix expression.
|
||||
// Instead, backtrack, and return the current expression.
|
||||
_index--;
|
||||
return left;
|
||||
}
|
||||
|
||||
var infix = infixParselets[_current.type];
|
||||
var newLeft = infix.parse(this, left, _current);
|
||||
|
||||
|
|
|
@ -146,9 +146,9 @@ class _Scanner implements Scanner {
|
|||
// Not sure how, but the following logic seems to occur
|
||||
// automatically:
|
||||
//
|
||||
// var textToken = tokens.removeLast();
|
||||
// var newSpan = textToken.span.expand(lastToken.span);
|
||||
// tokens.add(new Token(TokenType.text, newSpan));
|
||||
//var textToken = tokens.removeLast();
|
||||
//var newSpan = textToken.span.expand(lastToken.span);
|
||||
//tokens.add(new Token(TokenType.text, newSpan));
|
||||
} else if (lastToken != null) {
|
||||
textStart = null;
|
||||
} else if (!_scanner.isDone ?? lastToken == null) {
|
||||
|
|
|
@ -9,7 +9,7 @@ main() {
|
|||
<html>
|
||||
<body>
|
||||
<h1>Hello</h1>
|
||||
<img src=profile['avatar']>
|
||||
<img src=profile['avatar'] />
|
||||
</body>
|
||||
</html>
|
||||
''';
|
||||
|
@ -53,6 +53,7 @@ main() {
|
|||
''';
|
||||
|
||||
var buf = new CodeBuffer();
|
||||
//jael.scan(template, sourceUrl: 'test.jl').tokens.forEach(print);
|
||||
var document = jael.parseDocument(template, sourceUrl: 'test.jl');
|
||||
var scope = new SymbolTable(values: {
|
||||
'pokemon': const _Pokemon('Darkrai', 'Dark'),
|
||||
|
|
Loading…
Reference in a new issue