Skip to content
This repository has been archived by the owner on Feb 17, 2020. It is now read-only.

Commit

Permalink
Formatting + init server
Browse files Browse the repository at this point in the history
  • Loading branch information
thosakwe committed Jul 29, 2019
1 parent 3d5ac69 commit af16828
Show file tree
Hide file tree
Showing 9 changed files with 854 additions and 0 deletions.
21 changes: 21 additions & 0 deletions jael_language_server/.gitignore
@@ -0,0 +1,21 @@
# See https://www.dartlang.org/guides/libraries/private-files

# Files and directories created by pub
.dart_tool/
.packages
build/
# If you're building an application, you may want to check-in your pubspec.lock
pubspec.lock

# Directory created by dartdoc
# If you don't generate documentation locally you can remove this line.
doc/api/

# Avoid committing generated Javascript files:
*.dart.js
*.info.json # Produced by the --dump-info flag.
*.js # When generated by dart2js. Don't specify *.js if your
# project includes source files written in JavaScript.
*.js_
*.js.deps
*.js.map
3 changes: 3 additions & 0 deletions jael_language_server/analysis_options.yaml
@@ -0,0 +1,3 @@
analyzer:
strong-mode:
implicit-casts: false
68 changes: 68 additions & 0 deletions jael_language_server/bin/jael_language_server.dart
@@ -0,0 +1,68 @@
import 'dart:async';
import 'dart:io';
import 'package:args/args.dart';
import 'package:io/ansi.dart';
import 'package:io/io.dart';
import 'package:dart_language_server/dart_language_server.dart';
import 'package:jael_language_server/jael_language_server.dart';

main(List<String> args) async {
var argParser = new ArgParser()
..addFlag('help',
abbr: 'h', negatable: false, help: 'Print this help information.')
..addOption('log-file', help: 'A path to which to write a log file.');

void printUsage() {
print('usage: jael_language_server [options...]\n\nOptions:');
print(argParser.usage);
}

try {
var argResults = argParser.parse(args);

if (argResults['help'] as bool) {
printUsage();
return;
} else {
var jaelServer = new JaelLanguageServer();

if (argResults.wasParsed('log-file')) {
var f = new File(argResults['log-file'] as String);
await f.create(recursive: true);

jaelServer.logger.onRecord.listen((rec) async {
var sink = await f.openWrite(mode: FileMode.append);
sink.writeln(rec);
if (rec.error != null) sink.writeln(rec.error);
if (rec.stackTrace != null) sink.writeln(rec.stackTrace);
await sink.close();
});
} else {
jaelServer.logger.onRecord.listen((rec) async {
var sink = stderr;
sink.writeln(rec);
if (rec.error != null) sink.writeln(rec.error);
if (rec.stackTrace != null) sink.writeln(rec.stackTrace);
});
}

var spec = new ZoneSpecification(
handleUncaughtError: (self, parent, zone, error, stackTrace) {
jaelServer.logger.severe('Uncaught', error, stackTrace);
},
print: (self, parent, zone, line) {
jaelServer.logger.info(line);
},
);
var zone = Zone.current.fork(specification: spec);
await zone.run(() async {
var stdio = new StdIOLanguageServer.start(jaelServer);
await stdio.onDone;
});
}
} on ArgParserException catch (e) {
print('${red.wrap('error')}: ${e.message}\n');
printUsage();
exitCode = ExitCode.usage.code;
}
}
1 change: 1 addition & 0 deletions jael_language_server/lib/jael_language_server.dart
@@ -0,0 +1 @@
export 'src/server.dart';
153 changes: 153 additions & 0 deletions jael_language_server/lib/src/analyzer.dart
@@ -0,0 +1,153 @@
import 'package:jael/jael.dart';
import 'package:logging/logging.dart';
import 'package:symbol_table/symbol_table.dart';
import 'object.dart';

class Analyzer extends Parser {
final Logger logger;
Analyzer(Scanner scanner, this.logger) : super(scanner);

final errors = <JaelError>[];
var _scope = new SymbolTable<JaelObject>();
var allDefinitions = <Variable<JaelObject>>[];

SymbolTable<JaelObject> get parentScope =>
_scope.isRoot ? _scope : _scope.parent;

SymbolTable<JaelObject> get scope => _scope;

bool ensureAttributeIsPresent(Element element, String name) {
if (element.getAttribute(name)?.value == null) {
addError(new JaelError(JaelErrorSeverity.error,
'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) {
var e = new JaelError(
JaelErrorSeverity.warning,
"`$name` attribute should be a constant string literal.",
a?.span ?? element.tagName.span);
addError(e);
return false;
}

return true;
}

@override
Element parseElement() {
try {
_scope = _scope.createChild();
var element = super.parseElement();
if (element == null) return null;

// Check if any custom element exists.
_scope
.resolve(element.tagName.name)
?.value
?.usages
?.add(new SymbolUsage(SymbolUsageType.read, element.span));

// Validate attrs
var forEach = element.getAttribute('for-each');
if (forEach != null) {
var asAttr = element.getAttribute('as');
if (asAttr != null) {
if (ensureAttributeIsConstantString(element, 'as')) {
var asName = asAttr.string.value;
_scope.create(asName,
value: new JaelVariable(asName, asAttr.span), constant: true);
}
}

if (forEach.value != null) {
addError(new JaelError(JaelErrorSeverity.error,
'Missing value for `for-each` directive.', forEach.span));
}
}

var iff = element.getAttribute('if');
if (iff != null) {
if (iff.value != null) {
addError(new JaelError(JaelErrorSeverity.error,
'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) {
addError(new JaelError(
JaelErrorSeverity.warning,
'`declare` directive does not define any new symbols.',
element.tagName.span));
} else {
for (var attr in element.attributes) {
_scope.create(attr.name,
value: new JaelVariable(attr.name, attr.span));
}
}
} else if (element.tagName.name == 'element') {
if (ensureAttributeIsConstantString(element, 'name')) {
var nameCtx = element.getAttribute('name').value as StringLiteral;
var name = nameCtx.value;
//logger.info(
// 'Found custom element $name at ${element.span.start.toolString}');
try {
var symbol = parentScope.create(name,
value: new JaelCustomElement(name, element.tagName.span),
constant: true);
allDefinitions.add(symbol);
} on StateError catch (e) {
addError(new JaelError(
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 {
_scope = _scope.parent;
return null;
}
}

@override
Expression parseExpression(int precedence) {
var expr = super.parseExpression(precedence);
if (expr == null) return null;

if (expr is Identifier) {
var ref = _scope.resolve(expr.name);
ref?.value?.usages?.add(new SymbolUsage(SymbolUsageType.read, expr.span));
}

return expr;
}
}
32 changes: 32 additions & 0 deletions jael_language_server/lib/src/object.dart
@@ -0,0 +1,32 @@
import 'dart:collection';

import 'package:source_span/source_span.dart';

abstract class JaelObject {
final FileSpan span;
final usages = <SymbolUsage>[];
String get name;

JaelObject(this.span);
}

class JaelCustomElement extends JaelObject {
final String name;
final attributes = new SplayTreeSet<String>();

JaelCustomElement(this.name, FileSpan span) : super(span);
}

class JaelVariable extends JaelObject {
final String name;
JaelVariable(this.name, FileSpan span) : super(span);
}

class SymbolUsage {
final SymbolUsageType type;
final FileSpan span;

SymbolUsage(this.type, this.span);
}

enum SymbolUsageType { definition, read }

0 comments on commit af16828

Please sign in to comment.