Skip to content
Snippets Groups Projects
Commit 24f09b77 authored by Jesper's avatar Jesper
Browse files

Build structured AST declarations

The astdecl member of type.doc is now a structured representation of the AST
declaration, using type references to link to component types.

Also removed empty doc comments and empty doc members.
parent bb057b7f
No related branches found
No related tags found
No related merge requests found
/.idea/
/*.iml
/out/
/src/gen/
/src/tmp/
......
plugins {
id 'java'
id 'maven'
id 'org.jastadd' version '1.12.0'
id 'groovy'
id 'org.jastadd' version '1.12.2'
}
if (!file('extendj/jastadd_modules').exists()) {
......@@ -33,8 +34,6 @@ jastadd {
jastadd {
basedir "src/main/jastadd"
include "**/*.ast"
include "**/*.jadd"
include "**/*.jrag"
}
}
......@@ -57,8 +56,10 @@ repositories {
}
dependencies {
compile 'se.llbit:jo-json:1.3.0'
compile 'se.llbit:jo-json:1.3.1'
compile 'org.extendj:trace:0.1'
testCompile 'org.spockframework:spock-core:1.1-groovy-2.4'
}
def mainClassName = 'org.extendj.ragdoc.RagDocBuilder'
......
/* Copyright (c) 2018, Jesper Öqvist <jesper.oqvist@cs.lth.se>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.extendj.ragdoc;
import se.llbit.io.LookaheadReader;
import se.llbit.json.JsonArray;
import se.llbit.json.JsonObject;
import se.llbit.json.JsonParser;
import java.io.Closeable;
import java.io.IOException;
import java.io.StringReader;
public class AstDeclParser implements AutoCloseable, Closeable {
private static final int EOF = -1;
private final LookaheadReader in;
public AstDeclParser(String astdecl) {
in = new LookaheadReader(new StringReader(astdecl), 2);
}
private void accept(char c) throws IOException, JsonParser.SyntaxError {
int next = in.pop();
if (next == EOF) {
throw new JsonParser.SyntaxError(String.format("unexpected end of input (expected '%c')", c));
}
if (next != c) {
throw new JsonParser.SyntaxError(
String.format("unexpected character (was '%c', expected '%c')", (char) next, c));
}
}
JsonObject parse() throws IOException, JsonParser.SyntaxError {
JsonObject decl = new JsonObject();
String name = parseName();
decl.add("n", name);
skipWhitespace();
if (in.peek() == ':' && in.peek(1) != ':') {
accept(':');
skipWhitespace();
String extendsName = parseName();
decl.add("e", extendsName);
skipWhitespace();
}
if (in.peek() != ';') {
accept(':');
accept(':');
accept('=');
skipWhitespace();
JsonArray components = new JsonArray();
while (in.peek() != ';') {
components.add(parseComponent());
skipWhitespace();
}
if (!components.isEmpty()) {
decl.add("c", components);
}
}
accept(';');
return decl;
}
private JsonObject parseComponent() throws IOException, JsonParser.SyntaxError {
JsonObject component = new JsonObject();
if (in.peek() == '[') {
accept('[');
skipWhitespace();
String name = parseName();
skipWhitespace();
if (in.peek() == ':') {
accept(':');
skipWhitespace();
String extendsName = parseName();
component.add("n", name);
component.add("e", extendsName);
skipWhitespace();
} else {
component.add("e", name);
}
component.add("k", "opt");
skipWhitespace();
accept(']');
return component;
} else if (in.peek() == '<') {
accept('<');
skipWhitespace();
String name = parseName();
component.add("n", name);
skipWhitespace();
if (in.peek() == ':') {
accept(':');
skipWhitespace();
String extendsName = parseName();
component.add("e", extendsName);
skipWhitespace();
} else {
component.add("e", "String");
}
component.add("k", "token");
skipWhitespace();
accept('>');
return component;
} else {
String name = parseName();
skipWhitespace();
if (in.peek() == ':') {
accept(':');
skipWhitespace();
String extendsName = parseName();
component.add("n", name);
component.add("e", extendsName);
skipWhitespace();
} else {
component.add("e", name);
}
if (in.peek() == '*') {
accept('*');
component.add("k", "list");
}
return component;
}
}
private void skipWhitespace() throws IOException {
while (isWhitespace(in.peek())) {
in.pop();
}
}
private boolean isWhitespace(int chr) {
return chr == 0x20 || chr == 0x09 || chr == 0x0A || chr == 0x0D;
}
private boolean isNameChar(int chr) {
return chr != EOF && (chr == '.' || Character.isLetterOrDigit(chr));
}
private String chrToStr(int chr) {
return chr != EOF ? String.format("%c", chr) : "EOF";
}
String parseName() throws IOException, JsonParser.SyntaxError {
int next = in.peek();
if (!isNameChar(next)) {
throw new JsonParser.SyntaxError(
String.format("Error: expected name or typename, found %s", chrToStr(next)));
}
StringBuilder name = new StringBuilder();
while (isNameChar(in.peek())) {
name.append((char) in.pop());
}
return name.toString();
}
@Override public void close() throws IOException {
in.close();
}
}
......@@ -233,7 +233,7 @@ public class JavaDocParser extends Object {
private String parseInlineTag() {
if (doc[i] != '{' || doc[i + 1] != '@') {
// no opening bracket
// No opening bracket.
return "";
}
int j = i + 2;
......
......@@ -46,9 +46,11 @@ import org.extendj.util.Sorting;
import se.llbit.json.Json;
import se.llbit.json.JsonArray;
import se.llbit.json.JsonObject;
import se.llbit.json.JsonParser;
import se.llbit.json.JsonValue;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
......@@ -145,6 +147,12 @@ public class JsonBuilder {
}
}
/**
* Build a type reference object.
*
* <p>Type references point to a single type. Disambiguation is handled
* by using unique ID suffixes.
*/
private JsonObject typeRef(TypeDecl type) {
// TODO: handle wildcard types.
JsonObject obj = new JsonObject();
......@@ -307,11 +315,13 @@ public class JsonBuilder {
if (!params.isEmpty()) {
doc.add("params", params);
}
if (!javadoc.isEmpty()) {
doc.add("description", Json.of(javadoc));
}
if (aspectName != null) {
aspects.add(aspectName);
}
return doc;
return doc.isEmpty() ? null : doc;
}
return null;
}
......@@ -377,6 +387,30 @@ public class JsonBuilder {
}
JsonObject doc = type.jsonDocObject();
if (doc != null) {
String astdecl = doc.get("astdecl").stringValue("");
if (!astdecl.isEmpty()) {
// Parse ast declaration.
try (AstDeclParser parser = new AstDeclParser(astdecl)){
// Create declaration structure.
JsonObject decl = parser.parse();
if (!decl.get("e").stringValue("").isEmpty()) {
decl.set("e", typeRef(type.lookupType(decl.get("e").stringValue("")).singletonValue()));
}
doc.set("astdecl", decl);
JsonArray array = decl.get("c").array();
for (JsonValue c : array) {
JsonObject comp = c.object();
if (!comp.get("e").stringValue("").isEmpty()) {
comp.set("e",
typeRef(type.lookupType(comp.get("e").stringValue("")).singletonValue()));
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (JsonParser.SyntaxError syntaxError) {
syntaxError.printStackTrace();
}
}
obj.add("doc", doc);
}
addInheritedMembers(type, obj);
......
/* Copyright (c) 2018, Jesper Öqvist <jesper.oqvist@cs.lth.se>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.extendj.ragdoc
import spock.lang.*
class AstDeclParserSpec extends Specification {
def "No children (no extends)"() {
when:
def res
new AstDeclParser("Decl;").withCloseable { parser ->
res = parser.parse()
}
then:
res.toCompactString() == '{"n":"Decl"}'
}
def "No children (extends)"() {
when:
def res
new AstDeclParser("IdDecl:Decl;").withCloseable { parser ->
res = parser.parse()
}
then:
res.toCompactString() == '{"n":"IdDecl","e":"Decl"}'
}
def "No children (extends) + whitespace"() {
when:
def res
new AstDeclParser("IdDecl : Decl ;").withCloseable { parser ->
res = parser.parse()
}
then:
res.toCompactString() == '{"n":"IdDecl","e":"Decl"}'
}
def "Binary declaration"() {
when:
def res
new AstDeclParser("Binary ::= Left:Expr Right:Expr;").withCloseable { parser ->
res = parser.parse()
}
then:
res.toCompactString() == '{"n":"Binary","c":[{"n":"Left","e":"Expr"},{"n":"Right","e":"Expr"}]}'
}
def "List child"() {
when:
def res
new AstDeclParser("Program ::= CompilationUnit*;").withCloseable { parser ->
res = parser.parse()
}
then:
res.toCompactString() == '{"n":"Program","c":[{"e":"CompilationUnit","k":"list"}]}'
}
def "Named List child"() {
when:
def res
new AstDeclParser("Program ::= Unit:CompilationUnit*;").withCloseable { parser ->
res = parser.parse()
}
then:
res.toCompactString() == '{"n":"Program","c":[{"n":"Unit","e":"CompilationUnit","k":"list"}]}'
}
def "Named List child + whitespace"() {
when:
def res
new AstDeclParser("Program ::= Unit : CompilationUnit *;").withCloseable { parser ->
res = parser.parse()
}
then:
res.toCompactString() == '{"n":"Program","c":[{"n":"Unit","e":"CompilationUnit","k":"list"}]}'
}
def "Opt child"() {
when:
def res
new AstDeclParser("Return ::= [Expr];").withCloseable { parser ->
res = parser.parse()
}
then:
res.toCompactString() == '{"n":"Return","c":[{"e":"Expr","k":"opt"}]}'
}
def "Named Opt child"() {
when:
def res
new AstDeclParser("Return ::= [Result:Expr];").withCloseable { parser ->
res = parser.parse()
}
then:
res.toCompactString() == '{"n":"Return","c":[{"n":"Result","e":"Expr","k":"opt"}]}'
}
def "Named Opt child + whitespace"() {
when:
def res
new AstDeclParser("Return ::= [ Result : Expr ] ;").withCloseable { parser ->
res = parser.parse()
}
then:
res.toCompactString() == '{"n":"Return","c":[{"n":"Result","e":"Expr","k":"opt"}]}'
}
def "Token child"() {
when:
def res
new AstDeclParser("Number ::= <NUM>;").withCloseable { parser ->
res = parser.parse()
}
then:
res.toCompactString() == '{"n":"Number","c":[{"n":"NUM","e":"String","k":"token"}]}'
}
def "Named Token child"() {
when:
def res
new AstDeclParser("Number ::= <NUM:Integer>;").withCloseable { parser ->
res = parser.parse()
}
then:
res.toCompactString() == '{"n":"Number","c":[{"n":"NUM","e":"Integer","k":"token"}]}'
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment