diff --git a/.gitignore b/.gitignore index 2d18d32a..e92e2846 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,6 @@ Java/ .mtj.tmp/ # Package Files # -*.jar *.war *.nar *.ear @@ -43,7 +42,6 @@ dist/ downloads/ eggs/ .eggs/ -lib/ lib64/ parts/ sdist/ diff --git a/.gitpod.yml b/.gitpod.yml index 977c8f11..4fed3d19 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -8,3 +8,5 @@ tasks: export PATH="$LIBPLCC:$PATH" ' >> "$HOME/.bashrc" source "$HOME/.bashrc" + - name: Update Java + command: sdk install java < /dev/null \ No newline at end of file diff --git a/README.md b/README.md index 1e2f96bb..8dc77dda 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ for installing PLCC into a Bash Environment. ### Bash Environment -maxOS and Linux come with Bash. Windows users should +macOS and Linux come with Bash. Windows users should follow the instructions above to first get a Bash environment, then return and follow these directions. @@ -257,19 +257,21 @@ $ plcc file runs plcc.py on 'file', which generates code in a directory named 'Java/'. -plccmk [-c] [file] +plccmk [-c] [--json_ast] [file] runs plcc.py on file and compiles its results. - '-c' Remove 'Java/' before regenerating it. + '-c' Removes 'Java/' before regenerating it. + '--json_ast' add support to print JSON ASTs. 'file' defaults to 'grammar' scan [file...] Run Java/Scan on each file and then stdin. Scans input printing recognized token. -parse [-t] [-n] [file...] +parse [-t] [-n] [--json_ast] [file...] Run Java/Parser on each file and then stdin. Scans and parses input, printing OK for recognized programs and error otherwise. '-t' Print trace (i.e., parse tree). '-n' Suppress prompt. + '--json_ast' print JSON AST to stdout. rep [-t] [-n] [file...] Run Java/Rep on each file and then stdin. REP = Read, Execute, and Print loop. @@ -279,6 +281,14 @@ rep [-t] [-n] [file...] '-n' Suppress prompt. ``` +To print a JSON AST for a program, pass `--json_ast` to both `plccmk` +and `parse`, like so: + +```bash +plccmk --json_ast -c YOUR_GRAMMAR_FILE +parse --json_ast < YOUR_PROGRAM_FILE +``` + ## Grammar Files A grammar file consist of three sections separated by a line containing diff --git a/src/Std/ParseJsonAst.java b/src/Std/ParseJsonAst.java new file mode 100644 index 00000000..ed1fe7b7 --- /dev/null +++ b/src/Std/ParseJsonAst.java @@ -0,0 +1,46 @@ +import java.util.*; +import java.io.*; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator; +import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator; +import com.fasterxml.jackson.core.JsonProcessingException; + + + +public class ParseJsonAst extends ProcessFiles { + + // Parse the program and call $ok() on the resulting parse tree + public void action(Scan scn, Trace trace) { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); + objectMapper.enable(SerializationFeature.WRAP_ROOT_VALUE); + objectMapper.enable(SerializationFeature.INDENT_OUTPUT); + + PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator + .builder() + .allowIfBaseType(Object.class) // Can change "Object" to any type, we can look back at this later to be more specific + .build(); + + objectMapper.activateDefaultTypingAsProperty(ptv, ObjectMapper.DefaultTyping.NON_FINAL, "$type"); + + _Start parseTree = _Start.parse(scn, trace); + parseTree.$ok(); + try + { + String json = objectMapper.writeValueAsString(parseTree); + System.out.println(json); + } + catch (JsonProcessingException e) + { + e.printStackTrace(); + } + + } + + // Read programs from command-line files + // and then read programs from standard input. + public static void main(String [] args) { + new ParseJsonAst().processFiles(args); + } +} diff --git a/src/lib/jackson-annotations-2.15.2.jar b/src/lib/jackson-annotations-2.15.2.jar new file mode 100644 index 00000000..f8799c3f Binary files /dev/null and b/src/lib/jackson-annotations-2.15.2.jar differ diff --git a/src/lib/jackson-core-2.15.2.jar b/src/lib/jackson-core-2.15.2.jar new file mode 100644 index 00000000..a0e74865 Binary files /dev/null and b/src/lib/jackson-core-2.15.2.jar differ diff --git a/src/lib/jackson-databind-2.15.2.jar b/src/lib/jackson-databind-2.15.2.jar new file mode 100644 index 00000000..86f9a866 Binary files /dev/null and b/src/lib/jackson-databind-2.15.2.jar differ diff --git a/src/parse b/src/parse index fd0fcf48..91beeaf3 100755 --- a/src/parse +++ b/src/parse @@ -1,5 +1,11 @@ #!/bin/bash +JACKSON_VERSION="2.15.2" +JACKSON_ANNOTATIONS="${LIBPLCC}/lib/jackson-annotations-${JACKSON_VERSION}.jar" +JACKSON_CORE="${LIBPLCC}/lib/jackson-core-${JACKSON_VERSION}.jar" +JACKSON_DATABIND="${LIBPLCC}/lib/jackson-databind-${JACKSON_VERSION}.jar" +CP="${JACKSON_ANNOTATIONS}:${JACKSON_CORE}:${JACKSON_DATABIND}" + [ -d Java ] || { echo "Java directory missing" >&2 exit 1 @@ -10,4 +16,26 @@ exit 2 } -java -cp ./Java Parse $* +if echo "$*" | grep -- "--json_ast" &> /dev/null +then + + ARGS=() + while [ $# -gt 0 ] ; do + case "$1" in + --json_ast) + shift + ;; + *) + ARGS+=("$1") + shift + ;; + esac + done + + if ! java -cp "./Java:${CP}" ParseJsonAst "${ARGS[@]}" ; then + >&2 echo "Did you forget to pass --json_ast to plccmk?" + exit 1 + fi +else + java -cp ./Java Parse $* +fi diff --git a/src/plcc.py b/src/plcc.py index 8ce4c594..99a659e5 100644 --- a/src/plcc.py +++ b/src/plcc.py @@ -24,7 +24,6 @@ import io import shutil import tempfile - argv = sys.argv[1:] # skip over the command-line argument # current file information @@ -107,13 +106,15 @@ def main(): print(version.get_version()) sys.exit(0) + jsonAstInit() + nxt = nextLine() # nxt is the next line generator lex(nxt) # lexical analyzer generation par(nxt) # LL(1) check and parser generation sem(nxt) # semantic actions def plccInit(): - global flags, STD, STDT, STDP + global flags, argv, STD, STDT, STDP STDT = ['ILazy','IMatch','IScan','ITrace', 'Trace', 'PLCCException', 'Scan'] STDP = ['ProcessFiles','Parse','Rep'] STD = STDT + STDP @@ -134,6 +135,14 @@ def plccInit(): flags['semantics'] = True # create semantics routines flags['nowrite'] = False # when True, produce *no* file output +def jsonAstInit(): + global flags, STD, STDP + if 'json_ast' in flags and flags['json_ast']: + if 'ParseJsonAst' not in STDP: + if 'ParseJsonAst' not in STD: + STDP.append('ParseJsonAst') + flags['ParseJsonAst'] = 'ParseJsonAst' + def lex(nxt): # print('=== lexical specification') # Handle any flags appearing at beginning of lexical spec section; diff --git a/src/plccmk b/src/plccmk index 51e98594..5f494ab8 100755 --- a/src/plccmk +++ b/src/plccmk @@ -4,6 +4,12 @@ LIB="${LIBPLCC:-/usr/local/pub/plcc/PLCC}" PYTHON3=python3 PLCC="$LIB/plcc.py" +JACKSON_VERSION="2.15.2" +JACKSON_ANNOTATIONS="${LIBPLCC}/lib/jackson-annotations-${JACKSON_VERSION}.jar" +JACKSON_CORE="${LIBPLCC}/lib/jackson-core-${JACKSON_VERSION}.jar" +JACKSON_DATABIND="${LIBPLCC}/lib/jackson-databind-${JACKSON_VERSION}.jar" +CP="${JACKSON_ANNOTATIONS}:${JACKSON_CORE}:${JACKSON_DATABIND}" + if [ "$1" = "-c" ]; then rm Java/*.java Java/*.class &>/dev/null @@ -33,5 +39,24 @@ $PYTHON3 "$PLCC" $FILES || { echo "Java directory missing" >&2 exit 5 } +if echo "$*" | grep -- "--json_ast" &> /dev/null +then + + ARGS=() + while [ $# -gt 0 ] ; do + case "$1" in + --json_ast) + shift + ;; + *) + ARGS+=("$1") + shift + ;; + esac + done + + (cd ./Java ; javac -cp ".:${CP}" *.java) +else + (cd ./Java ; javac *.java) +fi -(cd ./Java ; javac *.java) diff --git a/tests/plcc/can-print-json-ast/expected.json b/tests/plcc/can-print-json-ast/expected.json new file mode 100644 index 00000000..6af5076e --- /dev/null +++ b/tests/plcc/can-print-json-ast/expected.json @@ -0,0 +1,36 @@ +OK +{ + "Program" : { + "$type" : "Program", + "exp" : { + "$type" : "PrimappExp", + "prim" : { + "$type" : "AddPrim" + }, + "rands" : { + "$type" : "Rands", + "expList" : [ "java.util.ArrayList", [ { + "$type" : "LitExp", + "lit" : { + "$type" : "Token", + "match" : "LIT", + "str" : "3", + "lno" : 1, + "line" : "+(3, 2)\n", + "eof" : false + } + }, { + "$type" : "LitExp", + "lit" : { + "$type" : "Token", + "match" : "LIT", + "str" : "2", + "lno" : 1, + "line" : "+(3, 2)\n", + "eof" : false + } + } ] ] + } + } + } +} diff --git a/tests/plcc/can-print-json-ast/given-grammar.lang b/tests/plcc/can-print-json-ast/given-grammar.lang new file mode 100644 index 00000000..948767ed --- /dev/null +++ b/tests/plcc/can-print-json-ast/given-grammar.lang @@ -0,0 +1,21 @@ +skip WHITESPACE '\s+' +skip COMMENT '%.*' +LIT '\d+' +LPAREN '\(' +RPAREN '\)' +COMMA ',' +ADDOP '\+' +SUBOP '\-' +ADD1OP 'add1' +SUB1OP 'sub1' +VAR '[A-Za-z]\w*' +% + ::= +:LitExp ::= +:VarExp ::= +:PrimappExp ::= LPAREN RPAREN + **= +COMMA +:AddPrim ::= ADDOP +:SubPrim ::= SUBOP +:Add1Prim ::= ADD1OP +:Sub1Prim ::= SUB1OP diff --git a/tests/plcc/can-print-json-ast/given-program.lang b/tests/plcc/can-print-json-ast/given-program.lang new file mode 100644 index 00000000..159235f2 --- /dev/null +++ b/tests/plcc/can-print-json-ast/given-program.lang @@ -0,0 +1 @@ ++(3, 2) \ No newline at end of file diff --git a/tests/plcc/can-print-json-ast/prints-json-ast.bats b/tests/plcc/can-print-json-ast/prints-json-ast.bats new file mode 100644 index 00000000..7f1e0d31 --- /dev/null +++ b/tests/plcc/can-print-json-ast/prints-json-ast.bats @@ -0,0 +1,19 @@ +#!/usr/bin/env bats + +@test "PLCC can print JSON AST." { + FILES="expected.json given-grammar.lang given-program.lang" + for f in $FILES ; do + cp "${BATS_TEST_DIRNAME}/${f}" "${BATS_TMPDIR}" + done + + cp "${BATS_TEST_DIRNAME}"/* "$BATS_TMPDIR/" + cd "${BATS_TMPDIR}" + plccmk --json_ast given-grammar.lang + parse -n --json_ast < given-program.lang > result.json + diff expected.json result.json + + for f in $FILES ; do + rm "${BATS_TMPDIR}/${f}" + done + rm "${BATS_TMPDIR}/result.json" +}