2021-12-19 16:28:51 +00:00
|
|
|
import 'package:jael3/jael3.dart';
|
2019-07-29 23:02:49 +00:00
|
|
|
import 'package:logging/logging.dart';
|
2021-12-19 16:28:51 +00:00
|
|
|
import 'package:belatuk_symbol_table/belatuk_symbol_table.dart';
|
2019-07-29 23:02:49 +00:00
|
|
|
import 'object.dart';
|
|
|
|
|
|
|
|
class Analyzer extends Parser {
|
|
|
|
final Logger logger;
|
|
|
|
Analyzer(Scanner scanner, this.logger) : super(scanner);
|
|
|
|
|
2021-06-20 12:37:20 +00:00
|
|
|
@override
|
2019-07-29 23:02:49 +00:00
|
|
|
final errors = <JaelError>[];
|
2021-06-20 12:37:20 +00:00
|
|
|
SymbolTable<JaelObject>? _scope = SymbolTable<JaelObject>();
|
2019-07-29 23:02:49 +00:00
|
|
|
var allDefinitions = <Variable<JaelObject>>[];
|
|
|
|
|
2021-06-20 12:37:20 +00:00
|
|
|
SymbolTable<JaelObject>? get parentScope =>
|
|
|
|
_scope!.isRoot ? _scope : _scope!.parent;
|
2019-07-29 23:02:49 +00:00
|
|
|
|
2021-06-20 12:37:20 +00:00
|
|
|
SymbolTable<JaelObject>? get scope => _scope;
|
2019-07-29 23:02:49 +00:00
|
|
|
|
|
|
|
bool ensureAttributeIsPresent(Element element, String name) {
|
|
|
|
if (element.getAttribute(name)?.value == null) {
|
2021-06-20 12:37:20 +00:00
|
|
|
addError(JaelError(JaelErrorSeverity.error,
|
2019-07-29 23:02:49 +00:00
|
|
|
'Missing required attribute `$name`.', element.span));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void addError(JaelError e) {
|
|
|
|
errors.add(e);
|
|
|
|
logger.severe(e.message, e.span.highlight());
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ensureAttributeIsConstantString(Element element, String name) {
|
|
|
|
var a = element.getAttribute(name);
|
|
|
|
if (a?.value is! StringLiteral || a?.value == null) {
|
2021-06-20 12:37:20 +00:00
|
|
|
var e = JaelError(
|
2019-07-29 23:02:49 +00:00
|
|
|
JaelErrorSeverity.warning,
|
2021-06-20 12:37:20 +00:00
|
|
|
'`$name` attribute should be a constant string literal.',
|
2019-07-29 23:02:49 +00:00
|
|
|
a?.span ?? element.tagName.span);
|
|
|
|
addError(e);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2021-06-20 12:37:20 +00:00
|
|
|
Element? parseElement() {
|
2019-07-29 23:02:49 +00:00
|
|
|
try {
|
2021-06-20 12:37:20 +00:00
|
|
|
_scope = _scope!.createChild();
|
2019-07-29 23:02:49 +00:00
|
|
|
var element = super.parseElement();
|
|
|
|
if (element == null) return null;
|
|
|
|
|
|
|
|
// Check if any custom element exists.
|
2021-06-20 12:37:20 +00:00
|
|
|
_scope!
|
2019-07-29 23:02:49 +00:00
|
|
|
.resolve(element.tagName.name)
|
|
|
|
?.value
|
|
|
|
?.usages
|
2021-06-20 12:37:20 +00:00
|
|
|
.add(SymbolUsage(SymbolUsageType.read, element.span));
|
2019-07-29 23:02:49 +00:00
|
|
|
|
|
|
|
// Validate attrs
|
|
|
|
var forEach = element.getAttribute('for-each');
|
|
|
|
if (forEach != null) {
|
|
|
|
var asAttr = element.getAttribute('as');
|
|
|
|
if (asAttr != null) {
|
|
|
|
if (ensureAttributeIsConstantString(element, 'as')) {
|
2021-06-20 12:37:20 +00:00
|
|
|
var asName = asAttr.string!.value;
|
|
|
|
_scope!.create(asName,
|
|
|
|
value: JaelVariable(asName, asAttr.span), constant: true);
|
2019-07-29 23:02:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (forEach.value != null) {
|
2021-06-20 12:37:20 +00:00
|
|
|
addError(JaelError(JaelErrorSeverity.error,
|
2019-07-29 23:02:49 +00:00
|
|
|
'Missing value for `for-each` directive.', forEach.span));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var iff = element.getAttribute('if');
|
|
|
|
if (iff != null) {
|
|
|
|
if (iff.value != null) {
|
2021-06-20 12:37:20 +00:00
|
|
|
addError(JaelError(JaelErrorSeverity.error,
|
2019-07-29 23:02:49 +00:00
|
|
|
'Missing value for `iff` directive.', iff.span));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate the tag itself
|
|
|
|
if (element is RegularElement) {
|
|
|
|
if (element.tagName.name == 'block') {
|
|
|
|
ensureAttributeIsConstantString(element, 'name');
|
|
|
|
//logger.info('Found <block> at ${element.span.start.toolString}');
|
|
|
|
} else if (element.tagName.name == 'case') {
|
|
|
|
ensureAttributeIsPresent(element, 'value');
|
|
|
|
//logger.info('Found <case> at ${element.span.start.toolString}');
|
|
|
|
} else if (element.tagName.name == 'declare') {
|
|
|
|
if (element.attributes.isEmpty) {
|
2021-06-20 12:37:20 +00:00
|
|
|
addError(JaelError(
|
2019-07-29 23:02:49 +00:00
|
|
|
JaelErrorSeverity.warning,
|
|
|
|
'`declare` directive does not define any new symbols.',
|
|
|
|
element.tagName.span));
|
|
|
|
} else {
|
|
|
|
for (var attr in element.attributes) {
|
2021-06-20 12:37:20 +00:00
|
|
|
_scope!
|
|
|
|
.create(attr.name, value: JaelVariable(attr.name, attr.span));
|
2019-07-29 23:02:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (element.tagName.name == 'element') {
|
|
|
|
if (ensureAttributeIsConstantString(element, 'name')) {
|
2021-06-20 12:37:20 +00:00
|
|
|
var nameCtx = element.getAttribute('name')!.value as StringLiteral;
|
2019-07-29 23:02:49 +00:00
|
|
|
var name = nameCtx.value;
|
|
|
|
//logger.info(
|
|
|
|
// 'Found custom element $name at ${element.span.start.toolString}');
|
|
|
|
try {
|
2021-06-20 12:37:20 +00:00
|
|
|
var symbol = parentScope!.create(name,
|
|
|
|
value: JaelCustomElement(name, element.tagName.span),
|
2019-07-29 23:02:49 +00:00
|
|
|
constant: true);
|
|
|
|
allDefinitions.add(symbol);
|
|
|
|
} on StateError catch (e) {
|
2021-06-20 12:37:20 +00:00
|
|
|
addError(JaelError(
|
2019-07-29 23:02:49 +00:00
|
|
|
JaelErrorSeverity.error, e.message, element.tagName.span));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (element.tagName.name == 'extend') {
|
|
|
|
ensureAttributeIsConstantString(element, 'src');
|
|
|
|
//logger.info('Found <extend> at ${element.span.start.toolString}');
|
|
|
|
}
|
|
|
|
} else if (element is SelfClosingElement) {
|
|
|
|
if (element.tagName.name == 'include') {
|
|
|
|
//logger.info('Found <include> at ${element.span.start.toolString}');
|
|
|
|
ensureAttributeIsConstantString(element, 'src');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return element;
|
|
|
|
} finally {
|
2021-06-20 12:37:20 +00:00
|
|
|
_scope = _scope!.parent;
|
2019-07-29 23:02:49 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2021-06-20 12:37:20 +00:00
|
|
|
Expression? parseExpression(int precedence) {
|
2019-07-29 23:02:49 +00:00
|
|
|
var expr = super.parseExpression(precedence);
|
|
|
|
if (expr == null) return null;
|
|
|
|
|
|
|
|
if (expr is Identifier) {
|
2021-06-20 12:37:20 +00:00
|
|
|
var ref = _scope!.resolve(expr.name);
|
|
|
|
ref?.value?.usages.add(SymbolUsage(SymbolUsageType.read, expr.span));
|
2019-07-29 23:02:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return expr;
|
|
|
|
}
|
|
|
|
}
|