platform/packages/jael/jael_language_server/lib/src/analyzer.dart

155 lines
5 KiB
Dart
Raw Normal View History

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;
}
}