diff options
| author | Michael Williamson <mike@zwobble.org> | 2025-01-01 01:39:18 +0000 |
|---|---|---|
| committer | Michael Williamson <mike@zwobble.org> | 2025-01-01 09:45:58 +0000 |
| commit | b65c719e2b65a9662b6170b0b53dfed9f1a3edcd (patch) | |
| tree | 6700b1a717d01a83b644dd06eb508e12a43b492c /src/main/java | |
| parent | 0df6725da8a0953c5643c5692c5244826e448d09 (diff) | |
Use JSON5 instead of TOML for config
Diffstat (limited to 'src/main/java')
17 files changed, 343 insertions, 349 deletions
diff --git a/src/main/java/org/shedlang/compiler/config/BinConfig.java b/src/main/java/org/shedlang/compiler/config/BinConfig.java index e91a6af4..5f2b0a7d 100644 --- a/src/main/java/org/shedlang/compiler/config/BinConfig.java +++ b/src/main/java/org/shedlang/compiler/config/BinConfig.java @@ -1,7 +1,7 @@ package org.shedlang.compiler.config; import org.shedlang.compiler.config.errors.ConfigError; -import org.shedlang.compiler.config.toml.TomlTableWithSource; +import org.shedlang.compiler.config.json.Json5ObjectWithSource; import org.shedlang.compiler.parser.SourceParser; import org.shedlang.compiler.sources.Sources; import org.shedlang.compiler.tokeniser.UnexpectedTokenException; @@ -10,10 +10,10 @@ import org.shedlang.compiler.types.NamespaceName; import java.text.MessageFormat; public record BinConfig(String name, NamespaceName mainNamespace) { - public static BinConfig fromToml(TomlTableWithSource table) { - var name = table.getString("name").value(); + public static BinConfig fromJson5(Json5ObjectWithSource object) { + var name = object.getString("name").value(); - var mainNamespaceString = table.getString("main-namespace"); + var mainNamespaceString = object.getString("mainNamespace"); try { var mainNamespace = SourceParser.parseNamespaceName(Sources.string(mainNamespaceString.value())); diff --git a/src/main/java/org/shedlang/compiler/config/ExampleConfig.java b/src/main/java/org/shedlang/compiler/config/ExampleConfig.java index abc311d3..1135066f 100644 --- a/src/main/java/org/shedlang/compiler/config/ExampleConfig.java +++ b/src/main/java/org/shedlang/compiler/config/ExampleConfig.java @@ -1,7 +1,7 @@ package org.shedlang.compiler.config; -import org.shedlang.compiler.config.toml.TomlString; -import org.shedlang.compiler.config.toml.TomlTableWithSource; +import org.shedlang.compiler.config.json.Json5ObjectWithSource; +import org.shedlang.compiler.config.json.Json5StringWithSource; import java.util.Optional; @@ -10,11 +10,11 @@ public record ExampleConfig( Optional<String> expectedOutput, Optional<String> expectedError ) { - public static ExampleConfig fromToml(TomlTableWithSource table) { + public static ExampleConfig fromJson5(Json5ObjectWithSource object) { return new ExampleConfig( - table.getString("bin").value(), - table.getStringOrNone("expected-output").map(TomlString::value), - table.getStringOrNone("expected-error").map(TomlString::value) + object.getString("bin").value(), + object.getStringOrNone("expectedOutput").map(Json5StringWithSource::value), + object.getStringOrNone("expectedError").map(Json5StringWithSource::value) ); } } diff --git a/src/main/java/org/shedlang/compiler/config/GcConfig.java b/src/main/java/org/shedlang/compiler/config/GcConfig.java index fad51f63..312c2f96 100644 --- a/src/main/java/org/shedlang/compiler/config/GcConfig.java +++ b/src/main/java/org/shedlang/compiler/config/GcConfig.java @@ -1,6 +1,6 @@ package org.shedlang.compiler.config; -import org.shedlang.compiler.config.toml.TomlTableWithSource; +import org.shedlang.compiler.config.json.Json5ObjectWithSource; import java.util.List; @@ -8,15 +8,15 @@ public sealed interface GcConfig permits GcBumpConfig, GcMarkAndSweepConfig { GcConfig BUMP = new GcBumpConfig(); GcConfig DEFAULT = GcMarkAndSweepConfig.DEFAULT; - static GcConfig fromToml(TomlTableWithSource table) { - var name = table.getString("name").value(); + static GcConfig fromJson5(Json5ObjectWithSource object) { + var name = object.getString("name").value(); return switch (name) { case "bump" -> BUMP; case "mark-and-sweep" -> - GcMarkAndSweepConfig.fromToml(table); + GcMarkAndSweepConfig.fromJson5(object); default -> throw new IllegalArgumentException("Unrecognised GC name: \"%s\"".formatted(name)); diff --git a/src/main/java/org/shedlang/compiler/config/GcMarkAndSweepConfig.java b/src/main/java/org/shedlang/compiler/config/GcMarkAndSweepConfig.java index 0c2ea9bb..8ac9ce56 100644 --- a/src/main/java/org/shedlang/compiler/config/GcMarkAndSweepConfig.java +++ b/src/main/java/org/shedlang/compiler/config/GcMarkAndSweepConfig.java @@ -1,6 +1,6 @@ package org.shedlang.compiler.config; -import org.shedlang.compiler.config.toml.TomlTableWithSource; +import org.shedlang.compiler.config.json.Json5ObjectWithSource; import java.util.ArrayList; import java.util.List; @@ -13,8 +13,8 @@ public record GcMarkAndSweepConfig( OptionalLong.empty() ); - public static GcMarkAndSweepConfig fromToml(TomlTableWithSource table) { - var collectInterval = table.getLongOrNone("collect-interval"); + public static GcMarkAndSweepConfig fromJson5(Json5ObjectWithSource object) { + var collectInterval = object.getLongOrNone("collectInterval"); return new GcMarkAndSweepConfig(collectInterval); } diff --git a/src/main/java/org/shedlang/compiler/config/ProfileConfig.java b/src/main/java/org/shedlang/compiler/config/ProfileConfig.java index cf8b4998..d922a4cb 100644 --- a/src/main/java/org/shedlang/compiler/config/ProfileConfig.java +++ b/src/main/java/org/shedlang/compiler/config/ProfileConfig.java @@ -1,19 +1,16 @@ package org.shedlang.compiler.config; -import org.shedlang.compiler.config.toml.TomlTableWithSource; +import org.shedlang.compiler.config.json.Json5ObjectWithSource; import java.util.Optional; public record ProfileConfig(String name, Optional<GcConfig> gc) { public static final ProfileConfig DEFAULT = new ProfileConfig("dev", Optional.empty()); - public static ProfileConfig fromToml(TomlTableWithSource table) { - var name = table.getString("name").value(); - var gc = table.getTable("gc").map(GcConfig::fromToml); + public static ProfileConfig fromJson5(Json5ObjectWithSource object) { + var name = object.getString("name").value(); + var gc = object.getObject("gc").map(GcConfig::fromJson5); - return new ProfileConfig( - name, - gc - ); + return new ProfileConfig(name, gc); } } diff --git a/src/main/java/org/shedlang/compiler/config/ProjectConfig.java b/src/main/java/org/shedlang/compiler/config/ProjectConfig.java index f6ddb80f..0408a143 100644 --- a/src/main/java/org/shedlang/compiler/config/ProjectConfig.java +++ b/src/main/java/org/shedlang/compiler/config/ProjectConfig.java @@ -1,6 +1,6 @@ package org.shedlang.compiler.config; -import org.shedlang.compiler.config.toml.TomlTableWithSource; +import org.shedlang.compiler.config.json.Json5ObjectWithSource; import org.shedlang.compiler.sources.Sources; import java.io.IOException; @@ -15,55 +15,49 @@ public record ProjectConfig( List<ProfileConfig> profiles ) { public static ProjectConfig read(Path projectPath) throws IOException { - var configPath = projectPath.resolve("shed.toml"); + var configPath = projectPath.resolve("shed.json5"); var contents = Files.readString(configPath); var source = Sources.file(configPath, contents); - return fromToml(projectPath, TomlTableWithSource.parse(source)); + return fromJson5(projectPath, Json5ObjectWithSource.parse(source)); } public static ProjectConfig fromString(Path projectPath, String config) { - return fromToml( + return fromJson5( projectPath, - TomlTableWithSource.parse(Sources.string(config)) + Json5ObjectWithSource.parse(Sources.string(config)) ); } - public static ProjectConfig fromToml(Path root, TomlTableWithSource table) { + public static ProjectConfig fromJson5(Path root, Json5ObjectWithSource object) { return new ProjectConfig( root, - binsFromToml(table), - examplesFromToml(table), - profilesFromToml(table) + binsFromToml(object), + examplesFromToml(object), + profilesFromToml(object) ); } - private static List<BinConfig> binsFromToml(TomlTableWithSource table) { - return table - .getTable("shed") - .flatMap(shed -> shed.getArrayOfTables("bins")) + private static List<BinConfig> binsFromToml(Json5ObjectWithSource object) { + return object.getArrayOfObjects("bins") .orElseGet(() -> List.of()) .stream() - .map(bin -> BinConfig.fromToml(bin)) + .map(bin -> BinConfig.fromJson5(bin)) .toList(); } - public static List<ExampleConfig> examplesFromToml(TomlTableWithSource table) { - return table - .getTable("shed") - .flatMap(shed -> shed.getArrayOfTables("examples")) + public static List<ExampleConfig> examplesFromToml(Json5ObjectWithSource object) { + return object.getArrayOfObjects("examples") .orElseGet(() -> List.of()) .stream() - .map(example -> ExampleConfig.fromToml(example)) + .map(example -> ExampleConfig.fromJson5(example)) .toList(); } - public static List<ProfileConfig> profilesFromToml(TomlTableWithSource table) { - return table - .getTable("shed") - .flatMap(shed -> shed.getArrayOfTables("profiles")) + public static List<ProfileConfig> profilesFromToml(Json5ObjectWithSource object) { + return object.getArrayOfObjects("profiles") .orElseGet(() -> List.of()) .stream() - .map(profile -> ProfileConfig.fromToml(profile)) + .map(profile -> ProfileConfig.fromJson5(profile)) .toList(); } diff --git a/src/main/java/org/shedlang/compiler/config/errors/Json5SyntaxConfigError.java b/src/main/java/org/shedlang/compiler/config/errors/Json5SyntaxConfigError.java new file mode 100644 index 00000000..d0db7f6c --- /dev/null +++ b/src/main/java/org/shedlang/compiler/config/errors/Json5SyntaxConfigError.java @@ -0,0 +1,19 @@ +package org.shedlang.compiler.config.errors; + +import org.shedlang.compiler.config.json.Json5Source; +import org.shedlang.compiler.sources.Source; +import org.zwobble.json5.parser.Json5ParseError; + +public class Json5SyntaxConfigError extends ConfigError { + public static Json5SyntaxConfigError fromJson5ParseError( + Json5ParseError error, + Source fullSource + ) { + var source = Json5Source.fromSourceRange(fullSource, error.sourceRange()); + return new Json5SyntaxConfigError(error.getMessage(), source); + } + + public Json5SyntaxConfigError(String message, Source source) { + super(message, source); + } +} diff --git a/src/main/java/org/shedlang/compiler/config/errors/TomlSyntaxConfigError.java b/src/main/java/org/shedlang/compiler/config/errors/TomlSyntaxConfigError.java deleted file mode 100644 index aa034738..00000000 --- a/src/main/java/org/shedlang/compiler/config/errors/TomlSyntaxConfigError.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.shedlang.compiler.config.errors; - -import org.shedlang.compiler.config.toml.TomlSource; -import org.shedlang.compiler.sources.Source; -import org.tomlj.TomlParseError; - -public class TomlSyntaxConfigError extends ConfigError { - public static TomlSyntaxConfigError fromTomljError( - TomlParseError error, - Source fullSource - ) { - var source = TomlSource.sourceAtTomlPosition(fullSource, error.position()); - return new TomlSyntaxConfigError(error.getMessage(), source); - } - - public TomlSyntaxConfigError(String message, Source source) { - super(message, source); - } -} diff --git a/src/main/java/org/shedlang/compiler/config/errors/UnexpectedTypeConfigError.java b/src/main/java/org/shedlang/compiler/config/errors/UnexpectedTypeConfigError.java index 0b0eda67..1a13f3a2 100644 --- a/src/main/java/org/shedlang/compiler/config/errors/UnexpectedTypeConfigError.java +++ b/src/main/java/org/shedlang/compiler/config/errors/UnexpectedTypeConfigError.java @@ -1,25 +1,51 @@ package org.shedlang.compiler.config.errors; -import org.shedlang.compiler.config.toml.TomlPath; +import org.shedlang.compiler.config.json.Json5Path; import org.shedlang.compiler.sources.Source; +import org.zwobble.json5.values.*; import java.text.MessageFormat; public class UnexpectedTypeConfigError extends ConfigError { public UnexpectedTypeConfigError( - TomlPath path, - String expectedType, - Class<?> actualType, + Json5Path path, + Class<? extends Json5Value> expectedType, + Class<? extends Json5Value> actualType, Source source ) { super( MessageFormat.format( "Expected {0} to be {1}, but was {2}", path, - expectedType, - actualType.getSimpleName() + // TODO: nicer names for JSON5 value types + describeJson5ValueType(expectedType), + describeJson5ValueType(actualType) ), source ); } + + private static String describeJson5ValueType(Class<? extends Json5Value> expectedType) { + if (expectedType.equals(Json5Array.class)) { + return "array"; + } else if (expectedType.equals(Json5Boolean.class)) { + return "boolean"; + } else if (expectedType.equals(Json5Null.class)) { + return "null"; + } else if (expectedType.equals(Json5NumberFinite.class)) { + return "finite number"; + } else if (expectedType.equals(Json5NumberNan.class)) { + return "NaN"; + } else if (expectedType.equals(Json5NumberPositiveInfinity.class)) { + return "Infinity"; + } else if (expectedType.equals(Json5NumberNegativeInfinity.class)) { + return "-Infinity"; + } else if (expectedType.equals(Json5Object.class)) { + return "object"; + } else if (expectedType.equals(Json5String.class)) { + return "string"; + } else { + return expectedType.getSimpleName(); + } + } } diff --git a/src/main/java/org/shedlang/compiler/config/json/Json5ObjectWithSource.java b/src/main/java/org/shedlang/compiler/config/json/Json5ObjectWithSource.java new file mode 100644 index 00000000..f073f267 --- /dev/null +++ b/src/main/java/org/shedlang/compiler/config/json/Json5ObjectWithSource.java @@ -0,0 +1,170 @@ +package org.shedlang.compiler.config.json; + +import org.shedlang.compiler.config.errors.ConfigError; +import org.shedlang.compiler.config.errors.Json5SyntaxConfigError; +import org.shedlang.compiler.config.errors.UnexpectedTypeConfigError; +import org.shedlang.compiler.sources.Source; +import org.zwobble.json5.parser.Json5ParseError; +import org.zwobble.json5.parser.Json5Parser; +import org.zwobble.json5.values.*; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.OptionalLong; + +public class Json5ObjectWithSource { + private final Json5Object object; + private final Json5Path path; + private final Source fullSource; + + private Json5ObjectWithSource( + Json5Object object, + Json5Path path, + Source fullSource + ) { + this.object = object; + this.path = path; + this.fullSource = fullSource; + } + + public static Json5ObjectWithSource parse(Source source) { + try { + var document = Json5Parser.parseText(source.contents()); + + if (document instanceof Json5Object object) { + return new Json5ObjectWithSource( + object, + Json5Path.TOP, + source + ); + } else { + // TODO: write a test for this. + throw new UnexpectedTypeConfigError( + Json5Path.TOP, + Json5Object.class, + document.getClass(), + source + ); + } + } catch (Json5ParseError error) { + throw Json5SyntaxConfigError.fromJson5ParseError(error, source); + } + } + + public Optional<Json5ObjectWithSource> getObject(String memberName) { + return getValueOfType(Json5Object.class, memberName) + .map(object -> new Json5ObjectWithSource( + object, + path.plusMemberName(memberName), + fullSource + )); + } + + private Optional<Json5Array> getArray(String memberName) { + return getValueOfType(Json5Array.class, memberName); + } + + public Optional<List<Json5ObjectWithSource>> getArrayOfObjects(String memberName) { + return getArray(memberName).map(array -> { + var objects = new ArrayList<Json5ObjectWithSource>(); + var index = 0; + var arrayPath = path.plusMemberName(memberName); + for (var element : array.elements()) { + var objectPath = arrayPath.plusArrayIndex(index); + var object = castJson5Value( + Json5Object.class, + objectPath, + element, + fullSource + ); + objects.add(new Json5ObjectWithSource( + object, + objectPath, + fullSource + )); + index++; + } + return objects; + }); + } + + public Optional<Json5StringWithSource> getStringOrNone(String memberName) { + return getValueOfType(Json5String.class, memberName) + .map(string -> new Json5StringWithSource( + string, + path.plusMemberName(memberName), + fullSource + )); + } + + public Json5StringWithSource getString(String memberName) { + return getStringOrNone(memberName) + .orElseThrow(() -> new ConfigError( + // TODO: escape member name + MessageFormat.format("{0} is missing {1}", path, memberName), + Json5Source.fromSourceRange(fullSource, object.sourceRange()) + )); + } + + public OptionalLong getLongOrNone(String memberName) { + var numberFinite = getValueOfType(Json5NumberFinite.class, memberName); + if (numberFinite.isEmpty()) { + return OptionalLong.empty(); + } + + try { + return OptionalLong.of(numberFinite.get().value().longValueExact()); + } catch (ArithmeticException exception) { + // TODO: more specific error? + throw new ConfigError( + String.format( + "%s must be a 64-bit integer", + path.plusMemberName(memberName) + ), + Json5Source.fromSourceRange(fullSource, numberFinite.get().sourceRange()) + ); + } + } + + public long getLong(String memberName) { + return getLongOrNone(memberName) + .orElseThrow(() -> new ConfigError( + // TODO: escape member name + MessageFormat.format("{0} is missing {1}", path, memberName), + Json5Source.fromSourceRange(fullSource, object.sourceRange()) + )); + } + + private <TValue extends Json5Value> Optional<TValue> getValueOfType( + Class<TValue> valueType, + String memberName + ) { + return object.getValue(memberName) + .map(value -> castJson5Value( + valueType, + path.plusMemberName(memberName), + value, + fullSource + )); + } + + private static <TValue extends Json5Value> TValue castJson5Value( + Class<TValue> valueType, + Json5Path path, + Json5Value value, + Source fullSource + ) { + try { + return valueType.cast(value); + } catch (ClassCastException exception) { + throw new UnexpectedTypeConfigError( + path, + valueType, + value.getClass(), + Json5Source.fromSourceRange(fullSource, value.sourceRange()) + ); + } + } +} diff --git a/src/main/java/org/shedlang/compiler/config/json/Json5Path.java b/src/main/java/org/shedlang/compiler/config/json/Json5Path.java new file mode 100644 index 00000000..f63e7ae4 --- /dev/null +++ b/src/main/java/org/shedlang/compiler/config/json/Json5Path.java @@ -0,0 +1,36 @@ +package org.shedlang.compiler.config.json; + +import org.pcollections.PVector; +import org.shedlang.compiler.util.CheckReturnValue; +import org.shedlang.compiler.util.P; + +import java.text.MessageFormat; + +public class Json5Path { + public static final Json5Path TOP = new Json5Path(P.vector()); + + private final PVector<String> components; + + private Json5Path(PVector<String> components) { + this.components = components; + } + + @CheckReturnValue + public Json5Path plusMemberName(String memberName) { + // TODO: handle string escaping + var component = components.isEmpty() ? memberName : "." + memberName; + return new Json5Path(this.components.plus(component)); + } + + @CheckReturnValue + public Json5Path plusArrayIndex(int index) { + var component = MessageFormat.format("[{0}]", index); + return new Json5Path(this.components.plus(component)); + } + + @Override + public String toString() { + // TODO: handle empty path + return String.join("", this.components); + } +} diff --git a/src/main/java/org/shedlang/compiler/config/json/Json5Source.java b/src/main/java/org/shedlang/compiler/config/json/Json5Source.java new file mode 100644 index 00000000..6b761a6a --- /dev/null +++ b/src/main/java/org/shedlang/compiler/config/json/Json5Source.java @@ -0,0 +1,18 @@ +package org.shedlang.compiler.config.json; + +import org.shedlang.compiler.sources.Source; +import org.zwobble.json5.sources.Json5SourceRange; + +public class Json5Source { + private Json5Source() {} + + public static Source fromSourceRange(Source fullSource, Json5SourceRange sourceRange) { + // TODO: decide whether we're using code points or code units, and make + // consistent (currently Shed sources use UTF-16 code units, JSON5 + // sources use code points) + return fullSource.at( + sourceRange.startCodePointIndex(), + sourceRange.endCodePointIndex() + ); + } +} diff --git a/src/main/java/org/shedlang/compiler/config/json/Json5StringWithSource.java b/src/main/java/org/shedlang/compiler/config/json/Json5StringWithSource.java new file mode 100644 index 00000000..bc9cf4b9 --- /dev/null +++ b/src/main/java/org/shedlang/compiler/config/json/Json5StringWithSource.java @@ -0,0 +1,28 @@ +package org.shedlang.compiler.config.json; + +import org.shedlang.compiler.sources.Source; +import org.zwobble.json5.values.Json5String; + +public class Json5StringWithSource { + private final Json5String string; + private final Json5Path path; + private final Source fullSource; + + public Json5StringWithSource( + Json5String string, + Json5Path path, + Source fullSource + ) { + this.string = string; + this.path = path; + this.fullSource = fullSource; + } + + public String value() { + return string.value(); + } + + public Source source() { + return Json5Source.fromSourceRange(fullSource, string.sourceRange()); + } +} diff --git a/src/main/java/org/shedlang/compiler/config/toml/TomlPath.java b/src/main/java/org/shedlang/compiler/config/toml/TomlPath.java deleted file mode 100644 index dc5910a9..00000000 --- a/src/main/java/org/shedlang/compiler/config/toml/TomlPath.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.shedlang.compiler.config.toml; - -import org.pcollections.PVector; -import org.shedlang.compiler.util.CheckReturnValue; -import org.shedlang.compiler.util.P; - -import java.text.MessageFormat; - -public class TomlPath { - public static final TomlPath TOP = new TomlPath(P.vector()); - - private final PVector<String> components; - - private TomlPath(PVector<String> components) { - this.components = components; - } - - @CheckReturnValue - public TomlPath plusKey(String key) { - // TODO: handle string escaping - var component = components.isEmpty() ? key : "." + key; - return new TomlPath(this.components.plus(component)); - } - - @CheckReturnValue - public TomlPath plusIndex(int index) { - var component = MessageFormat.format("[{0}]", index); - return new TomlPath(this.components.plus(component)); - } - - @Override - public String toString() { - return String.join("", this.components); - } -} diff --git a/src/main/java/org/shedlang/compiler/config/toml/TomlSource.java b/src/main/java/org/shedlang/compiler/config/toml/TomlSource.java deleted file mode 100644 index e72fbb45..00000000 --- a/src/main/java/org/shedlang/compiler/config/toml/TomlSource.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.shedlang.compiler.config.toml; - -import org.shedlang.compiler.sources.Source; -import org.tomlj.TomlPosition; - -import java.util.Arrays; - -public class TomlSource { - private TomlSource() {} - - public static Source sourceAtTomlPosition(Source fullSource, TomlPosition position) { - var lines = fullSource.contents().split("\n"); - var lineCharacterIndex = Arrays.stream(lines) - .limit(position.line() - 1) - .mapToInt(line -> line.length() + 1) - .sum(); - var characterIndex = lineCharacterIndex + position.column() - 1; - return fullSource.at(characterIndex, characterIndex); - } -} diff --git a/src/main/java/org/shedlang/compiler/config/toml/TomlString.java b/src/main/java/org/shedlang/compiler/config/toml/TomlString.java deleted file mode 100644 index b8aac9e5..00000000 --- a/src/main/java/org/shedlang/compiler/config/toml/TomlString.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.shedlang.compiler.config.toml; - -import org.shedlang.compiler.sources.Source; - -public record TomlString(String value, Source source) { -} diff --git a/src/main/java/org/shedlang/compiler/config/toml/TomlTableWithSource.java b/src/main/java/org/shedlang/compiler/config/toml/TomlTableWithSource.java deleted file mode 100644 index f7ef240f..00000000 --- a/src/main/java/org/shedlang/compiler/config/toml/TomlTableWithSource.java +++ /dev/null @@ -1,214 +0,0 @@ -package org.shedlang.compiler.config.toml; - -import org.shedlang.compiler.config.errors.ConfigError; -import org.shedlang.compiler.config.errors.TomlSyntaxConfigError; -import org.shedlang.compiler.config.errors.UnexpectedTypeConfigError; -import org.shedlang.compiler.sources.Source; -import org.tomlj.Toml; -import org.tomlj.TomlArray; -import org.tomlj.TomlPosition; -import org.tomlj.TomlTable; - -import java.text.MessageFormat; -import java.util.List; -import java.util.Optional; -import java.util.OptionalLong; -import java.util.stream.IntStream; - -public class TomlTableWithSource { - private final TomlTable table; - private final TomlPath path; - private final TomlPosition position; - private final Source fullSource; - - private TomlTableWithSource(TomlTable table, TomlPath path, TomlPosition position, Source fullSource) { - this.table = table; - this.path = path; - this.position = position; - this.fullSource = fullSource; - } - - public static TomlTableWithSource parse(Source source) { - var result = Toml.parse(source.contents()); - - if (result.hasErrors()) { - var error = result.errors().get(0); - throw TomlSyntaxConfigError.fromTomljError(error, source); - } - - return new TomlTableWithSource( - result, - TomlPath.TOP, - TomlPosition.positionAt(1, 1), - source - ); - } - - public Optional<TomlTableWithSource> getTable(String key) { - var value = table.get(key); - var position = table.inputPositionOf(key); - - return tomlValueToTable(path.plusKey(key), value, position); - } - - private Optional<TomlTableWithSource> tomlValueToTable( - TomlPath valuePath, - Object value, - TomlPosition valuePosition - ) { - if (value == null || valuePosition == null) { - return Optional.empty(); - } - - if (value instanceof TomlTable valueTable) { - return Optional.of(new TomlTableWithSource( - valueTable, - valuePath, - valuePosition, - fullSource - )); - } - - throw new UnexpectedTypeConfigError( - valuePath, - "a table", - value.getClass(), - TomlSource.sourceAtTomlPosition(fullSource, valuePosition) - ); - } - - private Optional<TomlArray> getArray(String key) { - var value = table.get(key); - var position = table.inputPositionOf(key); - - if (value == null || position == null) { - return Optional.empty(); - } - - if (value instanceof TomlArray valueArray) { - return Optional.of(valueArray); - } - - throw new UnexpectedTypeConfigError( - path.plusKey(key), - "an array", - value.getClass(), - TomlSource.sourceAtTomlPosition(fullSource, position) - ); - } - - public Optional<List<TomlTableWithSource>> getArrayOfTables(String key) { - return getArray(key).map(array -> - IntStream.range(0, array.size()) - .mapToObj(index -> { - var element = array.get(index); - var elementPosition = array.inputPositionOf(index); - return tomlValueToTable( - path.plusKey(key).plusIndex(index), - element, - elementPosition - ); - }) - .flatMap(x -> x.stream()) - .toList() - ); - } - - public Optional<TomlString> getStringOrNone(String key) { - var value = table.get(key); - var valuePosition = table.inputPositionOf(key); - - if (value == null || valuePosition == null) { - return Optional.empty(); - } - - if (value instanceof String valueString) { - var tomlString = new TomlString( - valueString, - TomlSource.sourceAtTomlPosition(fullSource, valuePosition) - ); - return Optional.of(tomlString); - } - - throw new UnexpectedTypeConfigError( - path.plusKey(key), - "a string", - value.getClass(), - TomlSource.sourceAtTomlPosition(fullSource, valuePosition) - ); - } - - public TomlString getString(String key) { - var value = table.get(key); - var valuePosition = table.inputPositionOf(key); - - if (value == null || valuePosition == null) { - throw new ConfigError( - MessageFormat.format("{0} is missing {1}", path, key), - TomlSource.sourceAtTomlPosition(fullSource, position) - ); - } - - if (value instanceof String valueString) { - return new TomlString( - valueString, - TomlSource.sourceAtTomlPosition(fullSource, valuePosition) - ); - } - - throw new UnexpectedTypeConfigError( - path.plusKey(key), - "a string", - value.getClass(), - TomlSource.sourceAtTomlPosition(fullSource, valuePosition) - ); - } - - public OptionalLong getLongOrNone(String key) { - var value = table.get(key); - var valuePosition = table.inputPositionOf(key); - - if (value == null) { - return OptionalLong.empty(); - } - - if (value instanceof Long valueLong) { - return OptionalLong.of(valueLong); - } - - throw new UnexpectedTypeConfigError( - path.plusKey(key), - "a long", - value.getClass(), - TomlSource.sourceAtTomlPosition(fullSource, valuePosition) - ); - } - - public long getLong(String key) { - var value = table.get(key); - var valuePosition = table.inputPositionOf(key); - - if (value == null) { - throw new ConfigError( - MessageFormat.format("{0} is missing {1}", path, key), - TomlSource.sourceAtTomlPosition(fullSource, position) - ); - } - - if (value instanceof Long valueLong) { - return valueLong; - } - - throw new UnexpectedTypeConfigError( - path.plusKey(key), - "a long", - value.getClass(), - TomlSource.sourceAtTomlPosition(fullSource, valuePosition) - ); - } - - public Optional<Source> getSource(String key) { - return Optional.ofNullable(table.inputPositionOf(key)) - .map(position -> TomlSource.sourceAtTomlPosition(fullSource, position)); - } -} |
