From 1747d946ecb597e59b3a8bbd0af2ffd6afe235bb Mon Sep 17 00:00:00 2001 From: Walter Oggioni Date: Tue, 3 Oct 2023 15:02:19 +0800 Subject: [PATCH] temporary commit --- gradle.properties | 4 +- settings.gradle | 2 +- wcfg/build.gradle | 16 ++ .../main/antlr/net/woggioni/wson/wcfg/WCFG.g4 | 101 ++++++++ .../wson/wcfg/CompositeObjectValue.java | 95 ++++++++ .../net/woggioni/wson/wcfg/ListenerImpl.java | 226 ++++++++++++++++++ .../net/woggioni/wson/wcfg/ParseError.java | 15 ++ .../net/woggioni/wson/wcfg/ValueHolder.java | 110 +++++++++ .../java/net/woggioni/wson/wcfg/WConfig.java | 47 ++++ .../net/woggioni/wson/wcfg/ParseTest.java | 56 +++++ wcfg/src/test/resources/build.wcfg | 55 +++++ wcfg/src/test/resources/recursive.wcfg | 4 + wcfg/src/test/resources/test.wcfg | 23 ++ 13 files changed, 751 insertions(+), 3 deletions(-) create mode 100644 wcfg/build.gradle create mode 100644 wcfg/src/main/antlr/net/woggioni/wson/wcfg/WCFG.g4 create mode 100644 wcfg/src/main/java/net/woggioni/wson/wcfg/CompositeObjectValue.java create mode 100644 wcfg/src/main/java/net/woggioni/wson/wcfg/ListenerImpl.java create mode 100644 wcfg/src/main/java/net/woggioni/wson/wcfg/ParseError.java create mode 100644 wcfg/src/main/java/net/woggioni/wson/wcfg/ValueHolder.java create mode 100644 wcfg/src/main/java/net/woggioni/wson/wcfg/WConfig.java create mode 100644 wcfg/src/test/java/net/woggioni/wson/wcfg/ParseTest.java create mode 100644 wcfg/src/test/resources/build.wcfg create mode 100644 wcfg/src/test/resources/recursive.wcfg create mode 100644 wcfg/src/test/resources/test.wcfg diff --git a/gradle.properties b/gradle.properties index 0757002..5ed8463 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -wson.version = 2023.009.30 +wson.version = 2023.10.01 -lys.version = 2023.09.26 +lys.version = 2023.10.01 diff --git a/settings.gradle b/settings.gradle index c3c49c0..0fe099b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -31,7 +31,7 @@ dependencyResolutionManagement { rootProject.name = 'wson' -def includeDirs = ['antlr', 'benchmark', 'test-utils', 'wson-cli', 'wson-w3c-json'] +def includeDirs = ['antlr', 'benchmark', 'test-utils', 'wson-cli', 'wson-w3c-json', 'wcfg'] includeDirs.each { include(it) diff --git a/wcfg/build.gradle b/wcfg/build.gradle new file mode 100644 index 0000000..68026f9 --- /dev/null +++ b/wcfg/build.gradle @@ -0,0 +1,16 @@ +plugins { + id 'antlr' +} + +dependencies { + implementation catalog.jwo + implementation rootProject + + antlr catalog.antlr + antlr catalog.antlr.runtime +} + +generateGrammarSource { + maxHeapSize = "64m" + arguments += ['-package', 'net.woggioni.wson.wcfg'] +} \ No newline at end of file diff --git a/wcfg/src/main/antlr/net/woggioni/wson/wcfg/WCFG.g4 b/wcfg/src/main/antlr/net/woggioni/wson/wcfg/WCFG.g4 new file mode 100644 index 0000000..966d1fe --- /dev/null +++ b/wcfg/src/main/antlr/net/woggioni/wson/wcfg/WCFG.g4 @@ -0,0 +1,101 @@ +grammar WCFG; + +wcfg + : assignment* + ; + +assignment + : IDENTIFIER ':=' (expression | value) ';' + ; + +expression + : value ('<<' value)+ + ; + +obj + : '{' pair (',' pair)* '}' + | '{' '}' + ; + +pair + : STRING ':' (expression | value) + ; + +array + : '[' value (',' value)* ']' + | '[' ']' + ; + +value + : NULL + | BOOLEAN + | NUMBER + | STRING + | IDENTIFIER + | obj + | array + ; + +BOOLEAN + : 'true' + | 'false' + ; + +NULL + : 'null' + ; + +NUMBER + : '-'? INT ('.' [0-9] +)? EXP? + ; + +IDENTIFIER: Letter LetterOrDigit*; + +STRING + : '"' (ESC | SAFECODEPOINT)* '"' + ; + +fragment ESC + : '\\' (["\\/bfnrt] | UNICODE) + ; + + +fragment UNICODE + : 'u' HEX HEX HEX HEX + ; + + +fragment HEX + : [0-9a-fA-F] + ; + + +fragment SAFECODEPOINT + : ~ ["\\\u0000-\u001F] + ; + + +fragment INT + : '0' | [1-9] [0-9]* + ; + +// no leading zeros + +fragment EXP + : [Ee] [+\-]? INT + ; + +fragment LetterOrDigit + : Letter + | [0-9] + ; + +fragment Letter + : [a-zA-Z$_] // these are the "java letters" below 0x7F + | ~[\u0000-\u007F\uD800-\uDBFF] // covers all characters above 0x7F which are not a surrogate + | [\uD800-\uDBFF] [\uDC00-\uDFFF] // covers UTF-16 surrogate pairs encodings for U+10000 to U+10FFFF + ; + +WS + : [ \t\n\r] + -> skip + ; diff --git a/wcfg/src/main/java/net/woggioni/wson/wcfg/CompositeObjectValue.java b/wcfg/src/main/java/net/woggioni/wson/wcfg/CompositeObjectValue.java new file mode 100644 index 0000000..f079eb3 --- /dev/null +++ b/wcfg/src/main/java/net/woggioni/wson/wcfg/CompositeObjectValue.java @@ -0,0 +1,95 @@ +package net.woggioni.wson.wcfg; + +import lombok.RequiredArgsConstructor; +import net.woggioni.wson.traversal.ValueIdentity; +import net.woggioni.wson.value.ObjectValue; +import net.woggioni.wson.xface.Value; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static net.woggioni.jwo.JWO.dynamicCast; + +@RequiredArgsConstructor +public class CompositeObjectValue implements ObjectValue { + + private final List elements; + + private ObjectValue wrapped; + + public CompositeObjectValue(List elements, Value.Configuration cfg) { + this.elements = elements; + wrapped = ObjectValue.newInstance(cfg); + List identities = new ArrayList<>(); + for (ObjectValue element : elements) { + CompositeObjectValue compositeObjectValue; + if ((compositeObjectValue = dynamicCast(element, CompositeObjectValue.class)) != null) { + boolean differenceFound = false; + for (int i = 0; i < compositeObjectValue.elements.size(); i++) { + ObjectValue objectValue = compositeObjectValue.elements.get(i); + if (!differenceFound && (i >= identities.size() || !Objects.equals( + identities.get(i), + new ValueIdentity(compositeObjectValue.elements.get(i))))) { + differenceFound = true; + } + if (differenceFound) { + merge(wrapped, objectValue); + identities.add(new ValueIdentity(objectValue)); + } + } + } else { + merge(wrapped, element); + identities.add(new ValueIdentity(element)); + } + } + } + + private static void merge(ObjectValue v1, ObjectValue v2) { + for (Map.Entry entry : v2) { + Value putResult = v1.getOrPut(entry.getKey(), entry.getValue()); + if (putResult != entry.getValue()) { + if (putResult.type() == Value.Type.OBJECT && entry.getValue().type() == Value.Type.OBJECT) { + ObjectValue ov = ObjectValue.newInstance(); + merge(ov, (ObjectValue) putResult); + merge(ov, (ObjectValue) entry.getValue()); + v1.put(entry.getKey(), ov); + } else { + v1.put(entry.getKey(), entry.getValue()); + } + } + } + } + + @Override + public Iterator> iterator() { + return wrapped.iterator(); + } + + @Override + public Value get(String key) { + return wrapped.get(key); + } + + @Override + public Value getOrDefault(String key, Value defaultValue) { + return wrapped.getOrDefault(key, defaultValue); + } + + @Override + public boolean has(String key) { + return wrapped.has(key); + } + + @Override + public int size() { + return wrapped.size(); + } + + @Override + public Iterable> asObject() { + return this::iterator; + } +} diff --git a/wcfg/src/main/java/net/woggioni/wson/wcfg/ListenerImpl.java b/wcfg/src/main/java/net/woggioni/wson/wcfg/ListenerImpl.java new file mode 100644 index 0000000..248d552 --- /dev/null +++ b/wcfg/src/main/java/net/woggioni/wson/wcfg/ListenerImpl.java @@ -0,0 +1,226 @@ +package net.woggioni.wson.wcfg; + +import lombok.Getter; +import net.woggioni.wson.value.ArrayValue; +import net.woggioni.wson.value.BooleanValue; +import net.woggioni.wson.value.FloatValue; +import net.woggioni.wson.value.IntegerValue; +import net.woggioni.wson.value.NullValue; +import net.woggioni.wson.value.ObjectValue; +import net.woggioni.wson.value.StringValue; +import net.woggioni.wson.xface.Value; +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.tree.ErrorNode; +import org.antlr.v4.runtime.tree.TerminalNode; + +import java.util.ArrayList; +import java.util.List; + +import static net.woggioni.jwo.JWO.dynamicCast; + +class ListenerImpl implements WCFGListener { + + private final Value.Configuration cfg; + + @Getter + private final Value result; + + private interface StackLevel { + Value getValue(); + } + + private static class ArrayStackLevel implements StackLevel { + private final ArrayValue value = new ArrayValue(); + + @Override + public Value getValue() { + return value; + } + } + + private static class ObjectStackLevel implements StackLevel { + public String currentKey; + private final ObjectValue value; + + public ObjectStackLevel(Value.Configuration cfg) { + value = ObjectValue.newInstance(cfg); + } + + @Override + public Value getValue() { + return value; + } + } + + private static class ExpressionStackLevel implements StackLevel { + private final Value.Configuration cfg; + private ObjectValue value = null; + public List elements = new ArrayList<>(); + + public ExpressionStackLevel(Value.Configuration cfg) { + this.cfg = cfg; + } + + @Override + public Value getValue() { + if(value == null) { + value = new CompositeObjectValue(elements, cfg); + } + return value; + } + } + + private final List stack = new ArrayList<>(); + + private void add2Last(Value value) { + StackLevel last = stack.get(stack.size() - 1); + ArrayStackLevel asl; + ObjectStackLevel osl; + ExpressionStackLevel esl; + if ((asl = dynamicCast(last, ArrayStackLevel.class)) != null) { + asl.value.add(value); + } else if ((osl = dynamicCast(last, ObjectStackLevel.class)) != null) { + osl.value.put(osl.currentKey, value); + osl.currentKey = null; + } else if((esl = dynamicCast(last, ExpressionStackLevel.class)) != null) { + esl.elements.add((ObjectValue) value); + } + } + private static String unquote(String quoted) { + return quoted.substring(1, quoted.length() - 1); + } + + public ListenerImpl(Value.Configuration cfg) { + this.cfg = cfg; + StackLevel sl = new ObjectStackLevel(cfg); + result = sl.getValue(); + stack.add(sl); + } + + private StackLevel pop() { + int size = stack.size() - 1; + StackLevel sl = stack.get(size); + stack.remove(size); + return sl; + } + + @Override + public void enterWcfg(WCFGParser.WcfgContext ctx) { + } + + + @Override + public void exitWcfg(WCFGParser.WcfgContext ctx) { + stack.clear(); + } + + @Override + public void enterAssignment(WCFGParser.AssignmentContext ctx) { + ObjectStackLevel osl = (ObjectStackLevel) stack.get(0); + osl.currentKey = ctx.IDENTIFIER().getText(); + } + + @Override + public void exitAssignment(WCFGParser.AssignmentContext ctx) { + ObjectStackLevel osl = (ObjectStackLevel) stack.get(0); + osl.currentKey = null; + } + + @Override + public void enterExpression(WCFGParser.ExpressionContext ctx) { + ExpressionStackLevel esl = new ExpressionStackLevel(cfg); + stack.add(esl); + } + + @Override + public void exitExpression(WCFGParser.ExpressionContext ctx) { + add2Last(pop().getValue()); + } + + @Override + public void enterObj(WCFGParser.ObjContext ctx) { + ObjectStackLevel osl = new ObjectStackLevel(cfg); + stack.add(osl); + } + + @Override + public void exitObj(WCFGParser.ObjContext ctx) { + add2Last(pop().getValue()); + } + + @Override + public void enterPair(WCFGParser.PairContext ctx) { + ObjectStackLevel osl = (ObjectStackLevel) stack.get(stack.size() - 1); + osl.currentKey = unquote(ctx.STRING().getText()); + } + + @Override + public void exitPair(WCFGParser.PairContext ctx) { + } + + @Override + public void enterArray(WCFGParser.ArrayContext ctx) { + ArrayStackLevel asl = new ArrayStackLevel(); + stack.add(asl); + } + + @Override + public void exitArray(WCFGParser.ArrayContext ctx) { + add2Last(pop().getValue()); + } + + @Override + public void enterValue(WCFGParser.ValueContext ctx) { + if (ctx.obj() != null) { + } else if (ctx.array() != null) { + } else if (ctx.STRING() != null) { + add2Last(new StringValue(unquote(ctx.STRING().getText()))); + } else if (ctx.BOOLEAN() != null) { + add2Last(new BooleanValue(Boolean.parseBoolean(ctx.BOOLEAN().getText()))); + } else if (ctx.NULL() != null) { + add2Last(new NullValue()); + } else if (ctx.NUMBER() != null) { + String text = ctx.NUMBER().getText(); + if (text.indexOf('.') < 0) { + add2Last(new IntegerValue(Long.parseLong(text))); + } else { + add2Last(new FloatValue(Double.parseDouble(text))); + } + } else if(ctx.IDENTIFIER() != null) { + String name = ctx.IDENTIFIER().getText(); + Value referredValue = result.getOrDefault(name, null); + if(referredValue == null) { + throw new ParseError( + "Undeclared identifier '" + name + "'", + ctx.start.getLine(), + ctx.start.getCharPositionInLine() + 1 + ); + } + add2Last(referredValue); + } + } + + @Override + public void exitValue(WCFGParser.ValueContext ctx) { + + } + + @Override + public void visitTerminal(TerminalNode node) { + + } + + @Override + public void visitErrorNode(ErrorNode node) { + } + + @Override + public void enterEveryRule(ParserRuleContext ctx) { + + } + + @Override + public void exitEveryRule(ParserRuleContext ctx) { + + } +} diff --git a/wcfg/src/main/java/net/woggioni/wson/wcfg/ParseError.java b/wcfg/src/main/java/net/woggioni/wson/wcfg/ParseError.java new file mode 100644 index 0000000..51c5b1f --- /dev/null +++ b/wcfg/src/main/java/net/woggioni/wson/wcfg/ParseError.java @@ -0,0 +1,15 @@ +package net.woggioni.wson.wcfg; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class ParseError extends RuntimeException { + public ParseError(String message, int line, int column) { + super(message + String.format(" at %d:%d", line, column)); + } + @Override + public String getMessage() { + return super.getMessage(); + } +} diff --git a/wcfg/src/main/java/net/woggioni/wson/wcfg/ValueHolder.java b/wcfg/src/main/java/net/woggioni/wson/wcfg/ValueHolder.java new file mode 100644 index 0000000..ac1ff29 --- /dev/null +++ b/wcfg/src/main/java/net/woggioni/wson/wcfg/ValueHolder.java @@ -0,0 +1,110 @@ +package net.woggioni.wson.wcfg; + +import lombok.Setter; +import net.woggioni.wson.xface.Value; + +import java.util.Map; + +public class ValueHolder implements Value { + @Setter + private Value delegate = Value.Null; + @Override + public Type type() { + return delegate.type(); + } + + @Override + public boolean isNull() { + return delegate.isNull(); + } + + @Override + public boolean asBoolean() { + return delegate.asBoolean(); + } + + @Override + public long asInteger() { + return delegate.asInteger(); + } + + @Override + public double asFloat() { + return delegate.asFloat(); + } + + @Override + public String asString() { + return delegate.asString(); + } + + @Override + public Iterable asArray() { + return delegate.asArray(); + } + + @Override + public Iterable> asObject() { + return delegate.asObject(); + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public void add(Value value) { + delegate.add(value); + } + + @Override + public void set(int index, Value value) { + delegate.set(index, value); + } + + @Override + public Value pop() { + return delegate.pop(); + } + + @Override + public Value head() { + return delegate.head(); + } + + @Override + public Value tail() { + return delegate.tail(); + } + + @Override + public Value get(int index) { + return delegate.get(index); + } + + @Override + public void put(String key, Value value) { + delegate.put(key, value); + } + + @Override + public Value get(String key) { + return delegate.get(key); + } + + @Override + public Value getOrDefault(String key, Value defaultValue) { + return delegate.getOrDefault(key, defaultValue); + } + + @Override + public Value getOrPut(String key, Value value2Put) { + return delegate.getOrPut(key, value2Put); + } + + @Override + public boolean has(String key) { + return delegate.has(key); + } +} diff --git a/wcfg/src/main/java/net/woggioni/wson/wcfg/WConfig.java b/wcfg/src/main/java/net/woggioni/wson/wcfg/WConfig.java new file mode 100644 index 0000000..a82b2ae --- /dev/null +++ b/wcfg/src/main/java/net/woggioni/wson/wcfg/WConfig.java @@ -0,0 +1,47 @@ +package net.woggioni.wson.wcfg; + +import lombok.SneakyThrows; +import net.woggioni.wson.value.ObjectValue; +import net.woggioni.wson.xface.Value; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CodePointCharStream; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.tree.ParseTreeWalker; + +import java.io.Reader; +import java.util.Arrays; +import java.util.stream.Collectors; + +public class WConfig { + + private final Value.Configuration cfg; + private final Value value; + + @SneakyThrows + public WConfig(Reader reader, Value.Configuration cfg) { + this.cfg = cfg; + CodePointCharStream inputStream = CharStreams.fromReader(reader); + WCFGLexer lexer = new WCFGLexer(inputStream); + CommonTokenStream commonTokenStream = new CommonTokenStream(lexer); + WCFGParser parser = new WCFGParser(commonTokenStream); + ListenerImpl listener = new ListenerImpl(cfg); + ParseTreeWalker walker = new ParseTreeWalker(); + walker.walk(listener, parser.wcfg()); + value = listener.getResult(); + } + + public Value get(String key) { + return value.get(key); + } + + public Value get(String ...overrides) { + return new CompositeObjectValue( + Arrays.stream(overrides) + .map(k -> (ObjectValue) value.get(k)) + .collect(Collectors.toList()), cfg); + } + + public Value whole() { + return value; + } +} diff --git a/wcfg/src/test/java/net/woggioni/wson/wcfg/ParseTest.java b/wcfg/src/test/java/net/woggioni/wson/wcfg/ParseTest.java new file mode 100644 index 0000000..d61aa47 --- /dev/null +++ b/wcfg/src/test/java/net/woggioni/wson/wcfg/ParseTest.java @@ -0,0 +1,56 @@ +package net.woggioni.wson.wcfg; + +import lombok.SneakyThrows; +import net.woggioni.wson.serialization.json.JSONDumper; +import net.woggioni.wson.xface.Value; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CodePointCharStream; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.tree.ParseTreeWalker; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.Reader; + +public class ParseTest { + + @SneakyThrows + @ParameterizedTest + @ValueSource(strings = { +// "build.wcfg", +// "test.wcfg", + "recursive.wcfg", + }) + public void test(String resource) { + try(Reader reader = new InputStreamReader(getClass().getClassLoader().getResourceAsStream(resource))) { + CodePointCharStream inputStream = CharStreams.fromReader(reader); + WCFGLexer lexer = new WCFGLexer(inputStream); + CommonTokenStream commonTokenStream = new CommonTokenStream(lexer); + WCFGParser parser = new WCFGParser(commonTokenStream); + Value.Configuration cfg = Value.Configuration.builder().serializeReferences(true).build(); + ListenerImpl listener = new ListenerImpl(cfg); + ParseTreeWalker walker = new ParseTreeWalker(); + walker.walk(listener, parser.wcfg()); + Value result = listener.getResult(); + new JSONDumper(cfg).dump(result, System.out); + } + } + + @Test + @SneakyThrows + public void test2() { + Value.Configuration cfg = Value.Configuration.builder().serializeReferences(true).build(); + try (Reader reader = new InputStreamReader(getClass().getResourceAsStream("/build.wcfg"))) { + WConfig wConfig = new WConfig(reader, cfg); + Value result = wConfig.get("release", "dev"); + try (OutputStream os = new BufferedOutputStream(new FileOutputStream("/tmp/build.json"))) { + new JSONDumper(cfg).dump(result, os); + } + } + } +} diff --git a/wcfg/src/test/resources/build.wcfg b/wcfg/src/test/resources/build.wcfg new file mode 100644 index 0000000..b8a1c54 --- /dev/null +++ b/wcfg/src/test/resources/build.wcfg @@ -0,0 +1,55 @@ +default := { + "source-directory": ".", + "generator": "Unix Makefiles", + "options": { + "CMAKE_BUILD_TYPE": "Release" + } +}; + +dev := default << { + "options": { + "CMAKE_BUILD_TYPE": "Debug" + } +}; + +jenkins := { + "build-directory" : "build" +}; + +release := default << { + "clean": true, + "options": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + } +}; + +lin64 := default << { + "options" : { + "STATIC_LIBSTDCXX" : true, + "QT5_STATIC": "ON" + } +}; + +win64 := default << { + "options": { + "CMAKE_TOOLCHAIN_FILE": "/opt/x-tools/x86_64-w64-mingw32/cmake/toolchain-x86_64-w64-mingw32.cmake", + "STATIC_BUILD": "ON", + "QT5_STATIC": "ON" + } +}; + +win32 := default << { + "options": { + "CMAKE_TOOLCHAIN_FILE": "/opt/x-tools/i686-w64-mingw32/cmake/toolchain-i686-w64-mingw32.cmake", + "STATIC_BUILD": "ON", + "QT5_STATIC": "ON" + } +}; + +musl := default << { + "options": { + "CMAKE_TOOLCHAIN_FILE": "/opt/x-tools/x86_64-woggioni-linux-musl/cmake/toolchain-x86_64-woggioni-linux-musl.cmake", + "QT5_STATIC": "ON", + "STATIC_BUILD": "ON" + } +}; diff --git a/wcfg/src/test/resources/recursive.wcfg b/wcfg/src/test/resources/recursive.wcfg new file mode 100644 index 0000000..12ae3f1 --- /dev/null +++ b/wcfg/src/test/resources/recursive.wcfg @@ -0,0 +1,4 @@ +value1 := { + "key" : "value", + "myself" : value1 +}; \ No newline at end of file diff --git a/wcfg/src/test/resources/test.wcfg b/wcfg/src/test/resources/test.wcfg new file mode 100644 index 0000000..bc2b634 --- /dev/null +++ b/wcfg/src/test/resources/test.wcfg @@ -0,0 +1,23 @@ +default := { + "foo" : 2, + "bar" : true +}; + +overridden := default << { + "foo" : 0, + "enabled" : false, + "window" : null +}; + +window := { + "width" : 640, + "height" : 480 +}; + +foobar := { + "child" : overridden << { + "window" : window + } << { + "enabled" : true + } +}; \ No newline at end of file