From 16cd545824d94cc11c722384223b9f37323dc38d Mon Sep 17 00:00:00 2001 From: Not Zed Date: Wed, 30 Jul 2025 21:01:07 +0930 Subject: [PATCH] Major reworking of the generator, added junit5 tests. - all values now go through Value.{Scalar,Array,Hash} with simpler semantics. Value is now an interface and each instance is a record. - junit5 tests for some classes, they run from the makefile only as yet. - enhanced api format - flags and named options on some objects/fields - pattern matched 'things' add named-group's from the pattern to the map - namespace for certain options - enhanced templates - 'builtin function's for naming convention conversion. - relative paths better defined (using '/') including leading '..' parts - better range specification/behaviour - comparisons use numbers (long) if they are numbers - bit better logging - removed some dumb ideas/dead code etc. --- Makefile | 5 +- config.make.in | 3 +- java.make | 5 +- junit5.make | 98 ++ maven.make | 86 ++ nbproject/project.properties | 2 + .../classes/au/notzed/nativez/tools/API.java | 838 ++++++++---------- .../au/notzed/nativez/tools/Array.java | 59 -- .../au/notzed/nativez/tools/Context.java | 477 +++------- .../classes/au/notzed/nativez/tools/Data.java | 84 -- .../au/notzed/nativez/tools/Export.java | 155 ++-- .../au/notzed/nativez/tools/Generator.java | 150 ++-- .../classes/au/notzed/nativez/tools/Hash.java | 118 --- .../au/notzed/nativez/tools/Scalar.java | 51 -- .../au/notzed/nativez/tools/Template.java | 327 +++---- .../classes/au/notzed/nativez/tools/Text.java | 69 -- .../au/notzed/nativez/tools/Tokeniser.java | 252 +++--- .../classes/au/notzed/nativez/tools/Util.java | 61 +- .../au/notzed/nativez/tools/Value.java | 294 +++++- .../au/notzed/nativez/tools/ContextTest.java | 109 +++ .../tests/au/notzed/nativez/tools/Data.java | 85 ++ .../au/notzed/nativez/tools/TemplateTest.java | 178 ++++ .../au/notzed/nativez/tools/UtilTest.java | 122 +++ .../au/notzed/nativez/tools/ValueTest.java | 145 +++ 24 files changed, 2019 insertions(+), 1754 deletions(-) create mode 100644 junit5.make create mode 100644 maven.make delete mode 100644 src/notzed.nativez.tools/classes/au/notzed/nativez/tools/Array.java delete mode 100644 src/notzed.nativez.tools/classes/au/notzed/nativez/tools/Data.java delete mode 100644 src/notzed.nativez.tools/classes/au/notzed/nativez/tools/Hash.java delete mode 100644 src/notzed.nativez.tools/classes/au/notzed/nativez/tools/Scalar.java delete mode 100644 src/notzed.nativez.tools/classes/au/notzed/nativez/tools/Text.java create mode 100644 src/notzed.nativez.tools/tests/au/notzed/nativez/tools/ContextTest.java create mode 100644 src/notzed.nativez.tools/tests/au/notzed/nativez/tools/Data.java create mode 100644 src/notzed.nativez.tools/tests/au/notzed/nativez/tools/TemplateTest.java create mode 100644 src/notzed.nativez.tools/tests/au/notzed/nativez/tools/UtilTest.java create mode 100644 src/notzed.nativez.tools/tests/au/notzed/nativez/tools/ValueTest.java diff --git a/Makefile b/Makefile index 6b5c0a5..21f7b77 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,8 @@ dist_EXTRA=README \ include config.make -java_MODULES=notzed.nativez +java_MODULES=notzed.nativez notzed.nativez.tools include java.make - +include junit5.make +include maven.make diff --git a/config.make.in b/config.make.in index e598104..f2e8fc3 100644 --- a/config.make.in +++ b/config.make.in @@ -4,11 +4,12 @@ TARGET ?= linux-amd64 JAVA_HOME ?= /usr/local/jdk JAVAC ?= $(JAVA_HOME)/bin/javac +JAVA ?= $(JAVA_HOME)/bin/java JAR ?= $(JAVA_HOME)/bin/jar JMOD ?= $(JAVA_HOME)/bin/jmod GCCPLUGINDIR:=$(shell gcc -print-file-name=plugin) -JAVACFLAGS += +JAVACFLAGS += # Linux options linux-amd64_CPPFLAGS = \ diff --git a/java.make b/java.make index c3b9030..41e9362 100644 --- a/java.make +++ b/java.make @@ -153,6 +153,8 @@ # ###################################################################### +MAKEFLAGS += --no-builtin-rules + all_MODULES = $(java_MODULES) $(native_MODULES) E:= @@ -194,7 +196,7 @@ all: jar: gen: -.PHONY: all clean jar gen $(java_MODULES) +.PHONY: all clean jar gen $(java_MODULES) dist clean: rm -rf bin @@ -341,7 +343,6 @@ $(foreach module,$(java_MODULES),$(eval $(call java_targets,$(module)))) # setup run-* targets define run_targets= run-$1/$2: bin/status/$1.sdk $($1_JDEPMOD:%=bin/status/%.sdk) - LD_LIBRARY_PATH=$(FFMPEG_HOME)/lib \ $(JAVA) \ $(if $(strip $(JAVAMODPATH) $($1_JAVAMODPATH)),--module-path $(subst $(S),:,$(strip $(JAVAMODPATH) $($1_JAVAMODPATH)))) \ $(JMAINFLAGS) $($1_JMAINFLAGS) \ diff --git a/junit5.make b/junit5.make new file mode 100644 index 0000000..850bcc5 --- /dev/null +++ b/junit5.make @@ -0,0 +1,98 @@ +# +# Copyright (C) 2025 Michael Zucchi +# +# This is the copyright for java.make +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +# ###################################################################### +# junit(5) makefile fragment + +# This runs tests names *Test.java or *IT.java in the src/*/tests +# directories. + +# Couldn't work out how netbeans does it, might only do junit4. + +# This integrates with java.make and maven.make: + +# include java.make +# include junit5.make +# include maven.make + +maven_central_JARS += org.junit.platform:junit-platform-console-standalone:1.13.4 + +junit5_module = org.junit.platform.console.standalone +junit5_launcher = org.junit.platform.console.ConsoleLauncher + +dist_EXTRA+=junit5.make + +# ###################################################################### + +define java_tests= +ifndef $$($1_TEST_JAVA) +$1_TEST_JAVA := $(shell find src/$1/tests -type f -name '*.java') +$1_TEST_PACKAGE = $$(subst /,.,$$(patsubst src/$1/tests/%/,%,$$(sort $$(dir $$($1_TEST_JAVA))))) +endif + +ifdef $1_TEST_JAVA +test: $1-test +test-it: $1-it +check: $1-test $1-it +bin/status/$1.tests: bin/status/$1.classes $$($1_TEST_JAVA) +.PHONY: $1-test $1-it + +bin/status/$1.tests: + @install -d $$(@D) + $$(JAVAC) \ + --module-path bin/modules:.lib \ + --patch-module $1=src/$1/tests \ + --add-modules $(junit5_module) \ + --add-reads $1=$(junit5_module),ALL-UNNAMED \ + --module-source-path 'src/*/tests' \ + -d bin/tests \ + $$($1_TEST_JAVA) + touch $$@ + +$1-test: bin/status/$1.tests + $$(JAVA) \ + --module-path bin/modules:.lib \ + --patch-module $1=bin/tests/$1 \ + --add-modules $1 \ + --add-reads $1=$(junit5_module),ALL-UNNAMED \ + $$(patsubst %,--add-opens $1/%=$(junit5_module),$$($1_TEST_PACKAGE)) \ + $$(patsubst %,--add-exports $1/%=$(junit5_module),$$($1_TEST_PACKAGE)) \ + -m $(junit5_module)/$(junit5_launcher) \ + --scan-modules --include-classname='.*Test' + +$1-it: bin/status/$1.tests + $$(JAVA) \ + --module-path bin/modules:.lib \ + --patch-module $1=bin/tests/$1 \ + --add-modules $1 \ + --add-reads $1=$(junit5_module),ALL-UNNAMED \ + $$(patsubst %,--add-opens $1/%=$(junit5_module),$$($1_TEST_PACKAGE)) \ + $$(patsubst %,--add-exports $1/%=$(junit5_module),$$($1_TEST_PACKAGE)) \ + -m $(junit5_module)/$(junit5_launcher) \ + --scan-modules --include-classname='.*IT' +endif + +endef + +.PHONY: test check + +$(foreach module,$(java_MODULES),$(if $(wildcard src/$(module)/tests),$(eval $(call java_tests,$(module))))) +#$(foreach module,$(java_MODULES),$(if $(wildcard src/$(module)/tests),$(info $(call java_tests,$(module))))) + +# ###################################################################### diff --git a/maven.make b/maven.make new file mode 100644 index 0000000..2fd31d3 --- /dev/null +++ b/maven.make @@ -0,0 +1,86 @@ +# +# Copyright (C) 2021 Michael Zucchi +# +# This is the copyright for maven.make +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +# This lets one download maven packages using simple automake syntax. + +# maven__URL = baseurl + +# Define the base url. maven_central_URL is already defined as +# maven_central_URL:=https://repo1.maven.org/maven2 + +# maven__JARS = group:artifact:version group:artifact:version ... + +# Define the artifacts required from the given maven repository. + +# That's it! + +# It defines several make targets. + +# make maven-init +# Will download the jar files. + +# make maven-verify +# Will download and check the signatures using gpg. The public key +# required for verification must be imported to gpg separately. + +# make distclean +# Will delete .lib + +# define maven central +maven_central_URL:=https://repo1.maven.org/maven2 +maven_repository_URL:=https://mvnrepository.com/artifact + +# find out what repositories the makefile defined +maven_REPOS=$(patsubst maven_%_URL,%,$(filter maven_%_URL,$(.VARIABLES))) + +dist_EXTRA+=maven.make + +# (group artifact version baseurl) +define maven_func= +$2_jar=.lib/$2-$3.jar +.lib/$2-$3.jar: + mkdir -p .lib + wget -O $$@ $(4)/$(subst .,/,$1)/$2/$3/$2-$3.jar || ( rm $$@ ; exit 1 ) +.lib/$2-$3.pom: + mkdir -p .lib + wget -O $$@ $(4)/$(subst .,/,$1)/$2/$3/$2-$3.pom || ( rm $$@ ; exit 1 ) +.lib-sources/$2-$3-sources.jar: + mkdir -p .lib-sources + wget -O $$@ $(4)/$(subst .,/,$1)/$2/$3/$2-$3-sources.jar || ( rm $$@ ; exit 1 ) +.lib-javadoc/$2-$3-javadoc.jar: + mkdir -p .lib-javadoc + wget -O $$@ $(4)/$(subst .,/,$1)/$2/$3/$2-$3-javadoc.jar || ( rm $$@ ; exit 1 ) +.lib/$2-$3.jar.asc: .lib/$2-$3.jar + wget -O $$@ $(4)/$(subst .,/,$1)/$2/$3/$2-$3.jar.asc + gpg --batch --verify $$@ $$< || ( rm $$@ ; echo "GPG verification failed, you may need to import the public key." ; exit 1 ) +maven-init: .lib/$2-$3.jar .lib-sources/$2-$3-sources.jar .lib-javadoc/$2-$3-javadoc.jar .lib/$2-$3.pom +maven-verify: .lib/$2-$3.jar.asc +endef + +maven-init: +maven-verify: + +.PHONY: maven-init maven-verify + +$(foreach repo,$(maven_REPOS),\ + $(foreach jar,$(maven_$(repo)_JARS), \ + $(eval $(call maven_func,$(word 1,$(subst :, ,$(jar))),$(word 2,$(subst :, ,$(jar))),$(word 3,$(subst :, ,$(jar))),$(maven_$(repo)_URL))))) + +distclean: + rm -rf .lib .lib-javadoc .lib-sources diff --git a/nbproject/project.properties b/nbproject/project.properties index 796d4da..f408e86 100644 --- a/nbproject/project.properties +++ b/nbproject/project.properties @@ -36,6 +36,7 @@ dist.jlink.dir=${dist.dir}/jlink dist.jlink.output=${dist.jlink.dir}/notzed.nativez endorsed.classpath= excludes= +file.reference.junit-platform-console-standalone-1.13.4.jar=.lib/junit-platform-console-standalone-1.13.4.jar includes=** jar.compress=false javac.classpath= @@ -50,6 +51,7 @@ javac.processorpath=\ javac.source=24 javac.target=24 javac.test.classpath=\ + ${file.reference.junit-platform-console-standalone-1.13.4.jar}:\ ${javac.classpath} javac.test.modulepath=\ ${javac.modulepath}:\ diff --git a/src/notzed.nativez.tools/classes/au/notzed/nativez/tools/API.java b/src/notzed.nativez.tools/classes/au/notzed/nativez/tools/API.java index 287266a..0ed2710 100644 --- a/src/notzed.nativez.tools/classes/au/notzed/nativez/tools/API.java +++ b/src/notzed.nativez.tools/classes/au/notzed/nativez/tools/API.java @@ -16,13 +16,14 @@ */ package au.notzed.nativez.tools; +import au.notzed.nativez.tools.Value.*; +import java.io.EOFException; import java.io.IOException; import java.io.PrintStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Collection; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; @@ -31,11 +32,15 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.function.Function; +import java.util.function.IntPredicate; import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; import java.util.stream.Stream; +//Still working on cleanup of API and Template +//Tokeniser might need a revisit too. /** * Loads and processes an .api file. *

@@ -43,87 +48,79 @@ import java.util.stream.Stream; public class API { /* data loaded from api file */ - List libraries; - Deque types; - // Expanded code-map with key "name:field" - Map codes; - // Expanded system-map with key 'type:name'? Is this useful? - Map system; - - public API(List system, List libraries, Deque types, Collection codes) { + List libraries; + List structs; + // Pattern types + List types; + // All code block fields key "name:field" + Map codes; + + private API(List libraries, List structs, List types, List codes) { this.libraries = libraries; + this.structs = structs; this.types = types; this.codes = new HashMap<>(); - this.system = new HashMap<>(); - // Codes are ordered in reverse, only take first entry to override others - for (var c: codes) { - for (var e: c.fields.entrySet()) { - this.codes.putIfAbsent(c.name + ":" + e.getKey(), e.getValue()); + for (var code: codes) { + String prefix = code.name.name.value(); + for (var e: code.entries.entrySet()) { + this.codes.put(prefix + ":" + e.getKey(), e.getValue()); } } - for (var c: system) { - for (var e: c.system.entrySet()) { - this.system.put(c.name + ":" + e.getKey(), e.getValue()); - } - } - } - - public Data code() { - return Data.ofTextMap(codes); - } - - public Data code(String module) { - return Data.ofTextMap(codes, module + ":"); } - public Data system() { - return Data.ofTextMap(system); + public Value code(String prefix) { + return (key) -> codes.getOrDefault(prefix + ":" + key, Scalar.UNDEFINED); } - public Data system(String part) { - return Data.ofTextMap(system, part + ":"); + public Value.Hash code() { + return Value.Hash.of(codes); } - public Text getCode(String type, String name) { - return codes.getOrDefault(type + ":" + name, Text.UNDEFINED); + public Scalar getCode(String type, String name) { + return (Scalar)codes.getOrDefault(type + ":" + name, Scalar.UNDEFINED); } - public Text getCode(Text key) { + public Scalar getCode(Scalar key) { switch (key.type()) { case LITERAL: case STRING: return key; case IDENTIFIER: - return codes.getOrDefault(key.value(), Text.UNDEFINED); + return (Scalar)codes.getOrDefault(key.value(), Scalar.UNDEFINED); default: throw new NoSuchElementException("No key for :" + key); } } - public Text getSystem(Match.Type type, String key, Text fallback) { - var v = system.get(type.name() + ":" + key); - if (v != null) - return v; - else - return fallback; + public APIView newDependencyMap(Export capi) { + return new APIView(capi); } - public DependencyMap newDependencyMap(Export capi) { - return new DependencyMap(capi); - } - - public class DependencyMap { - Set seen = new HashSet<>(); + public class APIView { + // All types the api needs + private Set seen = new HashSet<>(); Export capi; - // Stores expadned types - Map visited = new HashMap<>(); + // Concrete types. Used for each field or parameter. + Map visited = new HashMap<>(); - public DependencyMap(Export capi) { + public APIView(Export capi) { this.capi = capi; } - public Stream visisted(Match.Type type) { + public Function getTypes() { + return key -> Hash.of(visited.get(key).entries); + } + + public Set allTypes() { + return seen; + } + + public Set allTypes(String klass) { + return seen.stream().filter(s->s.startsWith(klass+":")).collect(Collectors.toSet()); + } + + public Stream visited(Field.Type type) { String match = type.name() + ":"; return seen.stream().filter(s -> s.startsWith(match)).map(capi::getType); } @@ -134,57 +131,40 @@ public class API { // Types from the start set for (String s: roots) { - Hash h = capi.getType(s); - assert h.defined(); + var h = capi.getType(s); + assert h != null; if (seen.add(s)) queue.add(h); } - /* - for (Value v: start.hash.values()) { - Hash h = (Hash)v; - System.out.println(h.getScalar("type").getValue() + ":" + h.getScalar("name").getValue()); - seen.add(h.getScalar("type").getValue() + ":" + h.getScalar("name").getValue()); - queue.add((Hash)v); - }*/ while (!queue.isEmpty()) { - Hash type = queue.removeFirst(); - - //System.out.println("\nprocess: " + type.getScalar("type").value + type.getScalar("name").value); - - //type.dump(System.out); - //Export.allFields(type).forEach(h -> h.dump(System.out)); + var type = queue.removeFirst(); Export.allFields(type) - .map(Hash.field("deref")) + .map(h -> h.getString("deref")) .forEach(this::visitType); - // What do i add to the queue? - // What about all the types it added, how do look them up? - // Field 'type' will map t capi->{key} Export.allFields(type) - .map(Hash.field("type")) + .map(h -> h.getString("type")) .filter(s -> s.indexOf(':') >= 0) .filter(seen::add) - .filter(s -> { - Hash v = capi.types.getHash(s); - if (!v.defined()) { - throw new RuntimeException("no type: " + s); - } - /* - if (!v.defined()) { - if (s.startsWith("struct:")) { - v = new Hash(); - v.put("type", s); - } else { - System.getLogger(API.class.getName()).log(System.Logger.Level.WARNING, () -> "Unsupported anonymous type: " + s); - } - }*/ - return v.defined(); - }) - .map(s -> capi.types.getHash(s)) + .map(s -> capi.getType(s)) .forEach(queue::add); } + + // Copy all flags from types to instances of/pointers to types + // other than struct? + seen.stream().filter(s -> s.startsWith("struct:")).map(capi::getType).forEach(type -> { + String name = type.getString("name"); + Type temp = visited.get("u64:${" + name + "}"); + if (temp != null) { + type.value().entrySet().stream().filter(e -> e.getKey().endsWith("?")) + .forEach(e -> temp.entries.put(e.getKey(), (Scalar)e.getValue())); + } else { + System.getLogger(API.class.getName()).log(System.Logger.Level.DEBUG, () -> "No pointer for for type: " + name); + } + }); + } /** @@ -194,155 +174,56 @@ public class API { * * @param deref Expanded raw type name. */ - public void visitType(String deref) { - Matcher matcher = null; - TemplateMap matched = null; - + void visitType(String deref) { //System.out.printf("visit: %s\n", deref); - if (visited.containsKey(deref)) return; // Find the last matching type, exact matches override pattern matching. for (var type: types) { - if (type.exact(deref)) { - System.out.println("add exact type: " + deref); - visited.put(deref, type); - return; - } else if (matched == null) { - Matcher test = type.pattern.matcher(deref); + Matcher test = type.pattern().matcher(deref); - if (test.matches()) { - matcher = test; - matched = type; - } - } - } + if (test.matches()) { + Type copy = type.copyAs(deref); - if (matcher != null && matched != null) { - TemplateMap type = matched.copyAs(deref); + for (var e: test.namedGroups().entrySet()) + copy.entries.put(e.getKey(), Scalar.ofLiteral(test.group(e.getValue()))); - for (var e: matcher.namedGroups().entrySet()) - type.fields.put(e.getKey(), Text.ofLiteral(matcher.group(e.getValue()))); - - System.out.println("add matching type: " + deref); - visited.put(deref, type); - } else { - throw new NoSuchElementException("No match for type: " + deref); - } - } - - public Function typeMap() { - // Copy all flags from types to instances of/pointers to types - seen.stream().filter(s -> s.startsWith("struct:")).map(capi::getType).forEach(type -> { - String name = type.getScalar("name").value; - TemplateMap temp = visited.get("u64:${" + name + "}"); - if (temp != null) { - type.hash.entrySet().stream().filter(e -> e.getKey().endsWith("?")) - .forEach(e -> { - temp.setTemplate(e.getKey(), Text.ofValue(e.getValue())); - }); - } else { - System.getLogger(API.class.getName()).log(System.Logger.Level.INFO, () -> "No pointer for anonymous type: " + name); + System.getLogger(API.class.getName()).log(System.Logger.Level.TRACE, () -> "Adding matched: " + type.pattern() + " type: " + deref); + visited.put(deref, copy); + return; } - }); + } - return (key) -> { - return visited.get(key); - }; + throw new NoSuchElementException("No match for type: " + deref); } } /** - * For storing code and type blocks. - *

- * TODO: templates can be pre-compiled, do that here on-demand? + * ***************************************************************************************** */ - static class TemplateMap implements Data { - String name; - Pattern pattern; - Map fields = new HashMap<>(); - - public TemplateMap(String name, boolean regex) { - this.name = name; - this.pattern = regex ? Pattern.compile(name) : null; - } - - public TemplateMap(String name) { - this(name, false); - } - - public TemplateMap copyAs(String name) { - TemplateMap map = new TemplateMap(name); - map.fields.putAll(fields); - return map; - } - - public boolean exact(String name) { - return pattern == null && this.name.equals(name); - } - - public void setTemplate(String name, Text template) { - fields.put(name, template); - } - - // pre-parse here? - public Text getTemplate(String name) { - return fields.get(name); - } - - public void inherit(TemplateMap b) { - fields.putAll(b.fields); + static record Flag(Scalar name, Scalar code) { + Flag(Scalar name) { + this(name, Scalar.TRUE); } + } - @Override - public Value getValue(Text key) { - return getText(key).toValue(); - } + static record Field(Type type, Scalar name, Pattern pattern, List options) { - @Override - public Text getText(Text key) { - return fields.getOrDefault(key.value(), Text.UNDEFINED); + Field(Type type, Scalar name, List options) { + this(type, name, name.type() == Scalar.Type.PATTERN ? Pattern.compile(name.value()) : null, options); } - public void dump(PrintStream out) { - out.printf("\ttype: %s\n", name); - for (var e: fields.entrySet()) { - out.printf("\t\t%s", e.getKey()); - switch (e.getValue().type()) { - case IDENTIFIER -> - out.printf(" %s\n", e.getValue().value()); - case LITERAL -> - out.printf(" <[%s]>\n", e.getValue().value()); - case STRING -> - out.printf(" \"%s\";\n", e.getValue().value()); - } - } + public Scalar code() { + // save perhaps? + return options.stream().filter(o -> o.name.value().equals("code")).findFirst().orElseThrow().code; } - } - static record Option(String name, Text code) { - Option(Text code) { - this("#flag", code); - } - } - - static record Match(Type type, Predicate matcher, List

    - *
  • func /pattern/ template-or-literal; - *
  • struct /pattern/ template-or-literal; - *
  • code template-or-literal; - *
  • option name value; - *
- */ - static class LibraryInfo { - String name; - List load = new ArrayList<>(); - // non-pattern fields - Map system = new HashMap<>(); - // Patterns to match things to templates - List field = new ArrayList<>(); - - public LibraryInfo(String name) { - this.name = name; - } + static class Type { + final Field name; + final Map entries; - // Use system() ? - public Text getSystem(Match.Type type, String key, Text fallback) { - var v = system.get(type.name() + ":" + key); - if (v != null) - return v; - else - return fallback; + public Type(Field.Type type, Scalar name, List options, Map entries) { + this.name = new Field(type, name, options); + this.entries = entries; } - public Data system() { - return Data.ofTextMap(system); + public Type copyAs(String target) { + return new Type(name.type, Scalar.ofIdentifier(target), List.copyOf(name.options), new HashMap<>(entries)); } - public Data system(String type) { - return Data.ofTextMap(system, type + ":"); + Pattern pattern() { + return name.pattern; } - void dump(PrintStream out) { - out.printf("library: %s\n", name); - for (var l: load) - out.printf(" load %s\n", l); - for (var m: field) { - out.printf(" %s\n", m); - } + public void dump(PrintStream out) { + out.printf("Type: %s\n", name); + for (var e: entries.entrySet()) + out.printf("\t%s: %s\n", e.getKey(), e.getValue()); } - } - static class APIParser implements AutoCloseable { + static class GroupParser implements AutoCloseable { static final char[] assoc = {'=', '>'}; private final Tokeniser in; private final Path base; - List libraries = new ArrayList<>(); - List system = new ArrayList<>(); - Deque typeClasses = new ArrayDeque<>(); - Deque types = new ArrayDeque<>(); - Deque codeClasses = new ArrayDeque<>(); - Deque codes = new ArrayDeque<>(); - - public APIParser(Path base, Tokeniser in) { + public GroupParser(Path base, Tokeniser in) { this.in = in; this.base = base; } - enum Types { - code, - type, - include, - library, - system; - } - - Types getType(String key) throws IOException { - try { - return Types.valueOf(key); - } catch (IllegalArgumentException x) { - throw new IOException(in.fatalMessage("Invalid object type: " + key)); - } - } - @Override public void close() throws IOException { in.close(); } - String identifier(int t) throws IOException { - if (t != Tokeniser.TOK_IDENTIFIER) - throw new IOException(in.fatalMessage(t, "Expeccting identifier")); - return in.getText(); - } - - /** - * Read a list of strings until ';'. - * Strings are string or identifier. - * - * @return - * @throws IOException - */ - List stringList() throws IOException { - List list = new ArrayList<>(); + List readOptions(IntPredicate when) throws IOException { + List list = new ArrayList<>(); + Scalar value = null; + int state = 0; int t; - while ((t = in.nextToken()) != -1 && t != ';') { - switch (t) { - case Tokeniser.TOK_IDENTIFIER: - case Tokeniser.TOK_STRING_SQ: - case Tokeniser.TOK_STRING_DQ: - list.add(in.getText()); - break; - default: - throw new IOException(in.fatalMessage(t, "Expecting STRING IDENTIFIER or ';'")); - } - } - return list; - } - /** - * Looks for ';' and nothing else. - * - * @throws IOException - */ - void emptyList() throws IOException { - int t = in.nextToken(); - if (t != ';') - throw new IOException(in.fatalMessage(t, "Expecting ';'")); - } - - /** - * Load a 'type' specification. - * Applies inheritance as it goes. - * - * @throws IOException - */ - TemplateMap loadType(Deque classes, Deque types) throws IOException { - TemplateMap map; - int t; - String text; - - switch ((t = in.nextToken())) { - case Tokeniser.TOK_REGEX: - text = in.getText(); - map = new TemplateMap(in.getText(), true); - types.add(map); - break; - case Tokeniser.TOK_IDENTIFIER: - map = new TemplateMap(in.getText()); - classes.addFirst(map); - break; - default: - throw new IOException(in.fatalMessage(t, "Expected name or regex")); - } + while ((t = in.nextToken()) != -1 && when.test(t)) { + int lstate = state; + Scalar lvalue = value; + System.getLogger(Tokeniser.class.getName()).log(System.Logger.Level.DEBUG, () -> "Options " + lstate + " value " + lvalue); - // options before {, inherit - // do i need options? or just inherit? - while ((t = in.nextToken()) != -1 && t != '{') { - String tmp; - - switch (t) { - case Tokeniser.TOK_IDENTIFIER: - tmp = in.getText(); - classes.stream().filter(c -> c.exact(tmp)).findFirst().ifPresent(map::inherit); - break; - case ';': - return map; - default: - throw new IOException(in.fatalMessage(t, "Expected name")); + switch (state) { + case 0 -> { + value = in.getText(t); + state = 1; } - } - - // Parse fields - while ((t = in.nextToken()) != -1 && t != '}') { - String key = identifier(t); - - // TODO: support options, do i need options? - Text value = textValue(); - - map.setTemplate(key, value); - } - return map; - } - - Text textValue() throws IOException { - int c = in.nextToken(); - Text text = in.getText(c); - - if (c == Tokeniser.TOK_QUOTED || c == Tokeniser.TOK_LITERAL) - return text; - - c = in.nextToken(); - if (c != ';') - throw new IOException(in.fatalMessage(c, "Expected ';'")); - return text; - } - - Text textOrNoValue() throws IOException { - int c = in.nextToken(); - - if (c == ';') - return Text.ofLiteral("#true"); - - Text text = in.getText(c); - - if (c == Tokeniser.TOK_QUOTED || c == Tokeniser.TOK_LITERAL) - return text; - - c = in.nextToken(); - if (c != ';') - throw new IOException(in.fatalMessage(c, "Expected ';'")); - return text; - } - - List