From af85c107ee9b6de8c45122e86e58bc1d9f6c03d2 Mon Sep 17 00:00:00 2001 From: Justin Rawlings Date: Wed, 14 Aug 2024 16:10:08 +1000 Subject: [PATCH] Datum Parsing --- src/MiniZinc.Client/MiniZincProcess.cs | 4 +- src/MiniZinc.Client/MiniZincResult.cs | 8 +- src/MiniZinc.Parser/Data/BoolDatum.cs | 19 + src/MiniZinc.Parser/Data/BoolSet.cs | 3 + src/MiniZinc.Parser/Data/BoolSetData.cs | 3 - src/MiniZinc.Parser/Data/DataNode.cs | 156 ------- src/MiniZinc.Parser/Data/Datum.cs | 83 ++++ .../Data/{ArrayData.cs => DatumArray.cs} | 20 +- src/MiniZinc.Parser/Data/EmptyDatum.cs | 6 + src/MiniZinc.Parser/Data/FloatDatum.cs | 19 + .../Data/{FloatRangeData.cs => FloatRange.cs} | 13 +- .../Data/{FloatSetData.cs => FloatSet.cs} | 11 +- src/MiniZinc.Parser/Data/IntDatum.cs | 19 + .../Data/{IntRangeData.cs => IntRange.cs} | 26 +- .../Data/{IntSetData.cs => IntSet.cs} | 9 +- .../Data/{RecordData.cs => RecordDatum.cs} | 21 +- .../Data/{SetData.cs => SetDatum.cs} | 23 +- src/MiniZinc.Parser/Data/StringDatum.cs | 19 + .../Expressions/BoolLiteralSyntax.cs | 6 - src/MiniZinc.Parser/MiniZincData.cs | 29 +- src/MiniZinc.Parser/ModelSyntax.cs | 4 +- src/MiniZinc.Parser/Parser.cs | 406 +++++++++++++----- src/MiniZinc.Parser/SyntaxExtensions.cs | 10 +- src/MiniZinc.Parser/Token.cs | 5 +- src/MiniZinc.Parser/Writer.cs | 38 +- test/ClientTests/ClientTest.cs | 28 +- test/ClientTests/ClientUnitTests.cs | 14 +- test/ParserTests/DataUnitTests.cs | 32 +- 28 files changed, 632 insertions(+), 402 deletions(-) create mode 100644 src/MiniZinc.Parser/Data/BoolDatum.cs create mode 100644 src/MiniZinc.Parser/Data/BoolSet.cs delete mode 100644 src/MiniZinc.Parser/Data/BoolSetData.cs delete mode 100644 src/MiniZinc.Parser/Data/DataNode.cs create mode 100644 src/MiniZinc.Parser/Data/Datum.cs rename src/MiniZinc.Parser/Data/{ArrayData.cs => DatumArray.cs} (53%) create mode 100644 src/MiniZinc.Parser/Data/EmptyDatum.cs create mode 100644 src/MiniZinc.Parser/Data/FloatDatum.cs rename src/MiniZinc.Parser/Data/{FloatRangeData.cs => FloatRange.cs} (72%) rename src/MiniZinc.Parser/Data/{FloatSetData.cs => FloatSet.cs} (66%) create mode 100644 src/MiniZinc.Parser/Data/IntDatum.cs rename src/MiniZinc.Parser/Data/{IntRangeData.cs => IntRange.cs} (54%) rename src/MiniZinc.Parser/Data/{IntSetData.cs => IntSet.cs} (72%) rename src/MiniZinc.Parser/Data/{RecordData.cs => RecordDatum.cs} (61%) rename src/MiniZinc.Parser/Data/{SetData.cs => SetDatum.cs} (54%) create mode 100644 src/MiniZinc.Parser/Data/StringDatum.cs diff --git a/src/MiniZinc.Client/MiniZincProcess.cs b/src/MiniZinc.Client/MiniZincProcess.cs index 544a517..85bcd24 100644 --- a/src/MiniZinc.Client/MiniZincProcess.cs +++ b/src/MiniZinc.Client/MiniZincProcess.cs @@ -378,8 +378,8 @@ public void Dispose() public override string ToString() => CommandString; private MiniZincResult Result( - in DataNode? objectiveValue = null, - in DataNode? objectiveBoundValue = null, + in Datum? objectiveValue = null, + in Datum? objectiveBoundValue = null, string? error = null ) { diff --git a/src/MiniZinc.Client/MiniZincResult.cs b/src/MiniZinc.Client/MiniZincResult.cs index 411179d..52c2f36 100644 --- a/src/MiniZinc.Client/MiniZincResult.cs +++ b/src/MiniZinc.Client/MiniZincResult.cs @@ -74,18 +74,18 @@ public sealed record MiniZincResult /// /// Text content of the message /// - public required DataNode? Objective { get; init; } + public required Datum? Objective { get; init; } /// /// Upper or lower bound (solver-dependent) /// - public required DataNode? ObjectiveBound { get; init; } + public required Datum? ObjectiveBound { get; init; } /// /// Absolute Gap to optimality /// `abs(objective - bound)` /// - public required DataNode? AbsoluteGapToOptimality { get; init; } + public required Datum? AbsoluteGapToOptimality { get; init; } /// /// Relative Gap to optimality @@ -98,7 +98,7 @@ public sealed record MiniZincResult /// the previous iteration /// `objective[i] - objective[i-1]` /// - public required DataNode? AbsoluteIterationGap { get; init; } + public required Datum? AbsoluteIterationGap { get; init; } /// /// Relative difference between this iteration and diff --git a/src/MiniZinc.Parser/Data/BoolDatum.cs b/src/MiniZinc.Parser/Data/BoolDatum.cs new file mode 100644 index 0000000..e8e650f --- /dev/null +++ b/src/MiniZinc.Parser/Data/BoolDatum.cs @@ -0,0 +1,19 @@ +namespace MiniZinc.Parser; + +public sealed class BoolDatum(bool value) : MiniZincDatum +{ + public bool Value => value; + + public static implicit operator bool(BoolDatum expr) => expr.Value; + + public override bool Equals(object? obj) + { + if (obj is not bool other) + return false; + if (!value.Equals(other)) + return false; + return true; + } + + public override string ToString() => Value.ToString(); +} \ No newline at end of file diff --git a/src/MiniZinc.Parser/Data/BoolSet.cs b/src/MiniZinc.Parser/Data/BoolSet.cs new file mode 100644 index 0000000..6de0123 --- /dev/null +++ b/src/MiniZinc.Parser/Data/BoolSet.cs @@ -0,0 +1,3 @@ +namespace MiniZinc.Parser; + +public sealed class BoolSet(List values) : SetDatum(values) { } diff --git a/src/MiniZinc.Parser/Data/BoolSetData.cs b/src/MiniZinc.Parser/Data/BoolSetData.cs deleted file mode 100644 index f70bd8c..0000000 --- a/src/MiniZinc.Parser/Data/BoolSetData.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace MiniZinc.Parser; - -public sealed class BoolSetData(List values) : SetData(values) { } \ No newline at end of file diff --git a/src/MiniZinc.Parser/Data/DataNode.cs b/src/MiniZinc.Parser/Data/DataNode.cs deleted file mode 100644 index f9b83bd..0000000 --- a/src/MiniZinc.Parser/Data/DataNode.cs +++ /dev/null @@ -1,156 +0,0 @@ -namespace MiniZinc.Parser; - -using System.Text.Json; -using System.Text.Json.Nodes; - -public abstract class DataNode -{ - public string Write(WriteOptions? options = null) - { - var writer = new Writer(options); - writer.WriteValue(this); - var mzn = writer.ToString(); - return mzn; - } - - public static readonly DataNode Empty = new EmptyData(); - - public static readonly DataNode True = new BoolData(true); - - public static readonly DataNode False = new BoolData(false); - - public static DataNode Float(decimal f) => new FloatData(f); - - public static DataNode Int(int i) => new IntData(i); - - public static DataNode String(string s) => new StringData(s); - - public static DataNode FromJson(JsonNode? node) - { - switch (node) - { - case JsonArray array: - return FromJson(array); - case JsonObject obj: - return FromJson(obj); - case JsonValue val: - return FromJson(val); - default: - return Empty; - } - } - - public static DataNode FromSyntax(JsonObject obj) - { - Dictionary dict = []; - foreach (var (key, node) in obj) - { - var value = FromJson(node); - dict[key] = value; - } - - var data = new RecordData(dict); - return data; - } - - public static DataNode FromSyntax(JsonArray array) - { - List items = new(); - foreach (var node in array) - { - var item = FromJson(node); - items.Add(item); - } - var data = new ArrayData(items); - return data; - } - - public static DataNode FromSyntax(JsonValue node) => - node.GetValueKind() switch - { - JsonValueKind.Null => Empty, - JsonValueKind.True => True, - JsonValueKind.False => False, - JsonValueKind.Number when node.TryGetValue(out var dec) => Float(dec), - JsonValueKind.Number when node.TryGetValue(out var i) => Int(i), - JsonValueKind.String => String(node.GetValue()), - _ => throw new ArgumentException() - }; -} - -public sealed class IntData(int value) : DataNode -{ - public int Value => value; - - public static implicit operator int(IntData expr) => expr.Value; - - public override bool Equals(object? obj) - { - if (obj is not int other) - return false; - if (!value.Equals(other)) - return false; - return true; - } - - public override string ToString() => Value.ToString(); -} - -public sealed class BoolData(bool value) : DataNode -{ - public bool Value => value; - - public static implicit operator bool(BoolData expr) => expr.Value; - - public override bool Equals(object? obj) - { - if (obj is not bool other) - return false; - if (!value.Equals(other)) - return false; - return true; - } - - public override string ToString() => Value.ToString(); -} - -public sealed class FloatData(decimal value) : DataNode -{ - public decimal Value => value; - - public static implicit operator decimal(FloatData expr) => expr.Value; - - public override bool Equals(object? obj) - { - if (obj is not decimal other) - return false; - if (!value.Equals(other)) - return false; - return true; - } - - public override string ToString() => Value.ToString(); -} - -public sealed class StringData(string value) : DataNode -{ - public string Value => value; - - public static implicit operator string(StringData expr) => expr.Value; - - public override bool Equals(object? obj) - { - if (obj is not string other) - return false; - if (!value.Equals(other)) - return false; - return true; - } - - public override string ToString() => Value; -} - -public sealed class EmptyData : DataNode -{ - public override bool Equals(object? obj) => obj is EmptyData; -} diff --git a/src/MiniZinc.Parser/Data/Datum.cs b/src/MiniZinc.Parser/Data/Datum.cs new file mode 100644 index 0000000..4900e18 --- /dev/null +++ b/src/MiniZinc.Parser/Data/Datum.cs @@ -0,0 +1,83 @@ +namespace MiniZinc.Parser; + +using System.Text.Json; +using System.Text.Json.Nodes; + +/// +/// The values that that appear in MiniZinc data files or +/// syntax. +/// +public abstract class MiniZincDatum +{ + public string Write(WriteOptions? options = null) + { + var writer = new Writer(options); + writer.WriteValue(this); + var mzn = writer.ToString(); + return mzn; + } + + public static readonly MiniZincDatum Empty = new EmptyDatum(); + + public static readonly MiniZincDatum True = new BoolDatum(true); + + public static readonly MiniZincDatum False = new BoolDatum(false); + + public static MiniZincDatum Float(decimal f) => new FloatDatum(f); + + public static MiniZincDatum Int(int i) => new IntDatum(i); + + public static MiniZincDatum String(string s) => new StringDatum(s); + + public static MiniZincDatum FromJson(JsonNode? node) + { + switch (node) + { + case JsonArray array: + return FromJson((JsonNode?)array); + case JsonObject obj: + return FromJson((JsonNode?)obj); + case JsonValue val: + return FromJson((JsonNode?)val); + default: + return Empty; + } + } + + public static MiniZincDatum FromJson(JsonObject obj) + { + Dictionary dict = []; + foreach (var (key, node) in obj) + { + var value = FromJson(node); + dict[key] = value; + } + + var data = new RecordDatum(dict); + return data; + } + + public static MiniZincDatum FromJson(JsonArray array) + { + List items = []; + foreach (var node in array) + { + var item = FromJson(node); + items.Add(item); + } + var data = new DatumArray(items); + return data; + } + + public static MiniZincDatum FromJson(JsonValue node) => + node.GetValueKind() switch + { + JsonValueKind.Null => Empty, + JsonValueKind.True => True, + JsonValueKind.False => False, + JsonValueKind.Number when node.TryGetValue(out var dec) => Float(dec), + JsonValueKind.Number when node.TryGetValue(out var i) => Int(i), + JsonValueKind.String => String(node.GetValue()), + _ => throw new ArgumentException($"Could not parse {node} as a datum") + }; +} diff --git a/src/MiniZinc.Parser/Data/ArrayData.cs b/src/MiniZinc.Parser/Data/DatumArray.cs similarity index 53% rename from src/MiniZinc.Parser/Data/ArrayData.cs rename to src/MiniZinc.Parser/Data/DatumArray.cs index 47bf18f..711027e 100644 --- a/src/MiniZinc.Parser/Data/ArrayData.cs +++ b/src/MiniZinc.Parser/Data/DatumArray.cs @@ -2,8 +2,10 @@ using System.Collections; -public abstract class ArrayData(List values) : DataNode, IReadOnlyList +public abstract class ArrayDatum(List values) : Datum, IReadOnlyList { + public override DatumKind Kind => DatumKind.Array; + public IEnumerator GetEnumerator() => values.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); @@ -12,7 +14,7 @@ public abstract class ArrayData(List values) : DataNode, IReadOnlyList public T this[int index] => values[index]; - public bool Equals(ArrayData? other) + public bool Equals(ArrayDatum? other) { if (other is null) return false; @@ -34,17 +36,17 @@ public bool Equals(ArrayData? other) return true; } - public override bool Equals(object? obj) => Equals(obj as ArrayData); + public override bool Equals(object? obj) => Equals(obj as ArrayDatum); } -public sealed class IntArrayData(List list) : ArrayData(list) { } +public sealed class IntArray(List list) : ArrayDatum(list) { } -public sealed class FloatArrayData(List list) : ArrayData(list) { } +public sealed class FloatArray(List list) : ArrayDatum(list) { } -public sealed class BoolArrayData(List list) : ArrayData(list) { } +public sealed class BoolArray(List list) : ArrayDatum(list) { } -public sealed class StringArrayData(List list) : ArrayData(list) { } +public sealed class StringArray(List list) : ArrayDatum(list) { } -public sealed class ArrayData(List list) : ArrayData(list) { } +public sealed class DatumArray(List list) : ArrayDatum(list) { } -public sealed class TupleData(List list) : ArrayData(list) { } +public sealed class DatumTuple(List list) : ArrayDatum(list) { } diff --git a/src/MiniZinc.Parser/Data/EmptyDatum.cs b/src/MiniZinc.Parser/Data/EmptyDatum.cs new file mode 100644 index 0000000..f65bb6a --- /dev/null +++ b/src/MiniZinc.Parser/Data/EmptyDatum.cs @@ -0,0 +1,6 @@ +namespace MiniZinc.Parser; + +public sealed class EmptyDatum : MiniZincDatum +{ + public override bool Equals(object? obj) => obj is EmptyDatum; +} \ No newline at end of file diff --git a/src/MiniZinc.Parser/Data/FloatDatum.cs b/src/MiniZinc.Parser/Data/FloatDatum.cs new file mode 100644 index 0000000..44b3895 --- /dev/null +++ b/src/MiniZinc.Parser/Data/FloatDatum.cs @@ -0,0 +1,19 @@ +namespace MiniZinc.Parser; + +public sealed class FloatDatum(decimal value) : MiniZincDatum +{ + public decimal Value => value; + + public static implicit operator decimal(FloatDatum expr) => expr.Value; + + public override bool Equals(object? obj) + { + if (obj is not decimal other) + return false; + if (!value.Equals(other)) + return false; + return true; + } + + public override string ToString() => Value.ToString(); +} \ No newline at end of file diff --git a/src/MiniZinc.Parser/Data/FloatRangeData.cs b/src/MiniZinc.Parser/Data/FloatRange.cs similarity index 72% rename from src/MiniZinc.Parser/Data/FloatRangeData.cs rename to src/MiniZinc.Parser/Data/FloatRange.cs index ac88352..a7be3e4 100644 --- a/src/MiniZinc.Parser/Data/FloatRangeData.cs +++ b/src/MiniZinc.Parser/Data/FloatRange.cs @@ -1,11 +1,14 @@ namespace MiniZinc.Parser; -public sealed class FloatRangeData : DataNode +public sealed class FloatRange : Datum { public readonly decimal Lower; + public readonly decimal Upper; - public FloatRangeData(decimal lower, decimal upper) + public override DatumKind Kind => DatumKind.Set; + + public FloatRange(decimal lower, decimal upper) { Lower = lower; Upper = upper; @@ -13,7 +16,7 @@ public FloatRangeData(decimal lower, decimal upper) public override bool Equals(object? obj) { - if (obj is FloatRangeData range) + if (obj is FloatRange range) { if (!Lower.Equals(range.Lower)) return false; @@ -23,7 +26,7 @@ public override bool Equals(object? obj) return true; } - else if (obj is FloatSetData set) + else if (obj is FloatSet set) { if (!set.Equals(this)) return false; @@ -34,4 +37,4 @@ public override bool Equals(object? obj) return false; } } -} \ No newline at end of file +} diff --git a/src/MiniZinc.Parser/Data/FloatSetData.cs b/src/MiniZinc.Parser/Data/FloatSet.cs similarity index 66% rename from src/MiniZinc.Parser/Data/FloatSetData.cs rename to src/MiniZinc.Parser/Data/FloatSet.cs index 372d329..200e899 100644 --- a/src/MiniZinc.Parser/Data/FloatSetData.cs +++ b/src/MiniZinc.Parser/Data/FloatSet.cs @@ -1,13 +1,16 @@ namespace MiniZinc.Parser; -public sealed class FloatSetData(List values) : SetData(values) +public sealed class FloatSet : SetDatum { + public FloatSet(List values) + : base(values) { } + public override bool Equals(object? obj) { - if (obj is FloatRangeData { Lower: var lo, Upper: var hi }) + if (obj is FloatRange { Lower: var lo, Upper: var hi }) { // Can only compare float set/range equality for the singleton set - if (values is not [var value]) + if (_values is not [var value]) return false; if (!lo.Equals(value)) @@ -23,4 +26,4 @@ public override bool Equals(object? obj) return base.Equals(obj); } } -} \ No newline at end of file +} diff --git a/src/MiniZinc.Parser/Data/IntDatum.cs b/src/MiniZinc.Parser/Data/IntDatum.cs new file mode 100644 index 0000000..df6cc9b --- /dev/null +++ b/src/MiniZinc.Parser/Data/IntDatum.cs @@ -0,0 +1,19 @@ +namespace MiniZinc.Parser; + +public sealed class IntDatum(int value) : MiniZincDatum +{ + public int Value => value; + + public static implicit operator int(IntDatum expr) => expr.Value; + + public override bool Equals(object? obj) + { + if (obj is not int other) + return false; + if (!value.Equals(other)) + return false; + return true; + } + + public override string ToString() => Value.ToString(); +} \ No newline at end of file diff --git a/src/MiniZinc.Parser/Data/IntRangeData.cs b/src/MiniZinc.Parser/Data/IntRange.cs similarity index 54% rename from src/MiniZinc.Parser/Data/IntRangeData.cs rename to src/MiniZinc.Parser/Data/IntRange.cs index 8aac047..098b330 100644 --- a/src/MiniZinc.Parser/Data/IntRangeData.cs +++ b/src/MiniZinc.Parser/Data/IntRange.cs @@ -1,13 +1,17 @@ namespace MiniZinc.Parser; -public sealed class IntRangeData : DataNode +using System.Collections; + +public sealed class IntRange : Datum, IEnumerable { public readonly int Lower; public readonly int Upper; public readonly int Size; public readonly int Count; - public IntRangeData(int lower, int upper) + public override DatumKind Kind => DatumKind.Set; + + public IntRange(int lower, int upper) { Lower = lower; Upper = upper; @@ -15,9 +19,17 @@ public IntRangeData(int lower, int upper) Count = Size + 1; } + public bool Contains(int i) => i >= Lower && i <= Upper; + + public IEnumerable Enumerate() + { + for (int i = Lower; i <= Upper; i++) + yield return i; + } + public override bool Equals(object? obj) { - if (obj is IntRangeData range) + if (obj is IntRange range) { if (!Lower.Equals(range.Lower)) return false; @@ -25,7 +37,7 @@ public override bool Equals(object? obj) return false; return true; } - else if (obj is IntSetData set) + else if (obj is IntSet set) { if (!set.Equals(this)) return false; @@ -36,4 +48,8 @@ public override bool Equals(object? obj) return false; } } -} \ No newline at end of file + + public IEnumerator GetEnumerator() => Enumerate().GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} diff --git a/src/MiniZinc.Parser/Data/IntSetData.cs b/src/MiniZinc.Parser/Data/IntSet.cs similarity index 72% rename from src/MiniZinc.Parser/Data/IntSetData.cs rename to src/MiniZinc.Parser/Data/IntSet.cs index 2dc73db..7040a00 100644 --- a/src/MiniZinc.Parser/Data/IntSetData.cs +++ b/src/MiniZinc.Parser/Data/IntSet.cs @@ -1,10 +1,13 @@ namespace MiniZinc.Parser; -public sealed class IntSetData(List values) : SetData(values) +public sealed class IntSet : SetDatum { + public IntSet(List values) + : base(values) { } + public override bool Equals(object? obj) { - if (obj is IntRangeData range) + if (obj is IntRange range) { if (Count is 0) return false; @@ -15,7 +18,7 @@ public override bool Equals(object? obj) int i = 0; for (int v = range.Lower; v <= range.Upper; v++) { - if (values[i++] != v) + if (_values[i++] != v) return false; } diff --git a/src/MiniZinc.Parser/Data/RecordData.cs b/src/MiniZinc.Parser/Data/RecordDatum.cs similarity index 61% rename from src/MiniZinc.Parser/Data/RecordData.cs rename to src/MiniZinc.Parser/Data/RecordDatum.cs index 9488bd2..38513bd 100644 --- a/src/MiniZinc.Parser/Data/RecordData.cs +++ b/src/MiniZinc.Parser/Data/RecordDatum.cs @@ -7,28 +7,30 @@ /// MiniZinc record literal /// /// (bool: a, int: c, float: f) -public sealed class RecordData(Dictionary dict) - : DataNode, - IReadOnlyDictionary +public sealed class RecordDatum(Dictionary dict) + : Datum, + IReadOnlyDictionary { - public IEnumerator> GetEnumerator() => dict.GetEnumerator(); + public IEnumerator> GetEnumerator() => dict.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public override DatumKind Kind => DatumKind.Record; + public int Count => dict.Count; public bool ContainsKey(string key) => dict.ContainsKey(key); - public bool TryGetValue(string key, [NotNullWhen(true)] out DataNode? value) => + public bool TryGetValue(string key, [NotNullWhen(true)] out Datum? value) => dict.TryGetValue(key, out value); - public DataNode this[string key] => dict[key]; + public Datum this[string key] => dict[key]; public IEnumerable Keys => dict.Keys; - public IEnumerable Values => dict.Values; + public IEnumerable Values => dict.Values; - public bool Equals(IReadOnlyDictionary? other) + public bool Equals(IReadOnlyDictionary? other) { if (other is null) return false; @@ -46,6 +48,5 @@ public bool Equals(IReadOnlyDictionary? other) return true; } - public override bool Equals(object? obj) => - Equals(obj as IReadOnlyDictionary); + public override bool Equals(object? obj) => Equals(obj as IReadOnlyDictionary); } diff --git a/src/MiniZinc.Parser/Data/SetData.cs b/src/MiniZinc.Parser/Data/SetDatum.cs similarity index 54% rename from src/MiniZinc.Parser/Data/SetData.cs rename to src/MiniZinc.Parser/Data/SetDatum.cs index a983489..9c5ae33 100644 --- a/src/MiniZinc.Parser/Data/SetData.cs +++ b/src/MiniZinc.Parser/Data/SetDatum.cs @@ -2,17 +2,26 @@ using System.Collections; -public abstract class SetData(List values) : DataNode, IReadOnlyList +public abstract class SetDatum : Datum, IReadOnlyList { - public IEnumerator GetEnumerator() => values.GetEnumerator(); + protected readonly List _values; + + public override DatumKind Kind => DatumKind.Set; + + protected SetDatum(List values) + { + _values = values; + } + + public IEnumerator GetEnumerator() => _values.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public int Count => values.Count; + public int Count => _values.Count; - public T this[int index] => values[index]; + public T this[int index] => _values[index]; - public bool Equals(SetData? other) + public bool Equals(SetDatum? other) { if (other is null) return false; @@ -36,7 +45,7 @@ public bool Equals(SetData? other) return true; } - public override bool Equals(object? obj) => Equals(obj as SetData); + public override bool Equals(object? obj) => Equals(obj as SetDatum); } -public sealed class SetData(List values) : SetData(values) { } +public sealed class SetDatum(List values) : SetDatum(values) { } diff --git a/src/MiniZinc.Parser/Data/StringDatum.cs b/src/MiniZinc.Parser/Data/StringDatum.cs new file mode 100644 index 0000000..888255e --- /dev/null +++ b/src/MiniZinc.Parser/Data/StringDatum.cs @@ -0,0 +1,19 @@ +namespace MiniZinc.Parser; + +public sealed class StringDatum(string value) : MiniZincDatum +{ + public string Value => value; + + public static implicit operator string(StringDatum expr) => expr.Value; + + public override bool Equals(object? obj) + { + if (obj is not string other) + return false; + if (!value.Equals(other)) + return false; + return true; + } + + public override string ToString() => Value; +} \ No newline at end of file diff --git a/src/MiniZinc.Parser/Expressions/BoolLiteralSyntax.cs b/src/MiniZinc.Parser/Expressions/BoolLiteralSyntax.cs index 625856e..216ff2b 100644 --- a/src/MiniZinc.Parser/Expressions/BoolLiteralSyntax.cs +++ b/src/MiniZinc.Parser/Expressions/BoolLiteralSyntax.cs @@ -6,12 +6,6 @@ public sealed class BoolLiteralSyntax : ExpressionSyntax { public bool Value { get; } - public BoolLiteralSyntax(in Token start) - : base(start) - { - Value = start.BoolValue; - } - public BoolLiteralSyntax(in Token start, bool value) : base(start) { diff --git a/src/MiniZinc.Parser/MiniZincData.cs b/src/MiniZinc.Parser/MiniZincData.cs index 7c89bca..afb3804 100644 --- a/src/MiniZinc.Parser/MiniZincData.cs +++ b/src/MiniZinc.Parser/MiniZincData.cs @@ -6,19 +6,19 @@ using MiniZinc; /// -/// Result of parsing a minizinc data from a file (.dzn) or string. +/// Result of parsing minizinc data from a file (.dzn) or string. /// /// -/// Data is different from a in that it can only -/// contain assignments of the form `$name = $expr;` +/// This is different from a in that it can only +/// contain assignments of the form `$name = $datum;` /// [DebuggerDisplay("{SourceText}")] -public sealed class MiniZincData(IReadOnlyDictionary? dict = null) - : IEquatable>, - IReadOnlyDictionary +public sealed class MiniZincData(IReadOnlyDictionary? dict = null) + : IEquatable>, + IReadOnlyDictionary { - private readonly IReadOnlyDictionary _dict = - dict ?? new Dictionary(); + private readonly IReadOnlyDictionary _dict = + dict ?? new Dictionary(); public string Write(WriteOptions? options = null) { @@ -30,7 +30,7 @@ public string Write(WriteOptions? options = null) public string SourceText => Write(WriteOptions.Minimal); - public bool Equals(IReadOnlyDictionary? other) + public bool Equals(IReadOnlyDictionary? other) { if (other is null) return false; @@ -64,23 +64,22 @@ public bool Equals(IReadOnlyDictionary? other) return true; } - public IEnumerator> GetEnumerator() => _dict.GetEnumerator(); + public IEnumerator> GetEnumerator() => _dict.GetEnumerator(); - public override bool Equals(object? obj) => - Equals(obj as IReadOnlyDictionary); + public override bool Equals(object? obj) => Equals(obj as IReadOnlyDictionary); public override int GetHashCode() => SourceText.GetHashCode(); - public bool TryGetValue(string key, [NotNullWhen(true)] out DataNode? value) => + public bool TryGetValue(string key, [NotNullWhen(true)] out Datum? value) => _dict.TryGetValue(key, out value); public bool ContainsKey(string key) => _dict.ContainsKey(key); - public DataNode this[string name] => _dict[name]; + public Datum this[string name] => _dict[name]; public IEnumerable Keys => _dict.Keys; - public IEnumerable Values => _dict.Values; + public IEnumerable Values => _dict.Values; public int Count => _dict.Count; diff --git a/src/MiniZinc.Parser/ModelSyntax.cs b/src/MiniZinc.Parser/ModelSyntax.cs index f68cc68..d87daa5 100644 --- a/src/MiniZinc.Parser/ModelSyntax.cs +++ b/src/MiniZinc.Parser/ModelSyntax.cs @@ -1,4 +1,6 @@ -namespace MiniZinc.Parser.Syntax; +namespace MiniZinc.Parser; + +using Syntax; /// /// Result of parsing a minizinc model from a file (.mzn) or string diff --git a/src/MiniZinc.Parser/Parser.cs b/src/MiniZinc.Parser/Parser.cs index 8434bb8..2cff0c3 100644 --- a/src/MiniZinc.Parser/Parser.cs +++ b/src/MiniZinc.Parser/Parser.cs @@ -246,7 +246,7 @@ internal bool ParseStatement([NotNullWhen(true)] out StatementSyntax? statement) /// a = 1;b = 2; c= true; internal bool ParseData(out MiniZincData data) { - Dictionary values = new(); + Dictionary values = new(); data = new MiniZincData(values); while (true) @@ -260,7 +260,7 @@ internal bool ParseData(out MiniZincData data) if (!Expect(TokenKind.EQUAL)) return false; - if (!ParseValue(out var value)) + if (!ParseDatum(out var value)) return false; if (values.ContainsKey(name.StringValue)) @@ -733,12 +733,12 @@ internal bool ParseExprAtom([NotNullWhen(true)] out ExpressionSyntax? expr) case TokenKind.KEYWORD_TRUE: Step(); - expr = new BoolLiteralSyntax(token); + expr = new BoolLiteralSyntax(token, true); break; case TokenKind.KEYWORD_FALSE: Step(); - expr = new BoolLiteralSyntax(token); + expr = new BoolLiteralSyntax(token, false); break; case TokenKind.STRING_LITERAL: @@ -832,50 +832,199 @@ internal bool ParseExprAtom([NotNullWhen(true)] out ExpressionSyntax? expr) private bool IsOk => _errorMessage is null; - /// Parse array data into the most refined type possible - internal bool ParseArrayData([NotNullWhen(true)] out DataNode? array) + /// Parse an int range data starting at the given token + /// The parser will step over the given lower bound + /// Will only return false in the case of an error + internal bool ParseIntDatum(out int i, out IntRange? range) { + range = null; + Token start = _current; + i = start.IntValue; Step(); + if (!Skip(TokenKind.CLOSED_RANGE)) + return true; + if (!Expect(TokenKind.INT_LITERAL, out Token upper)) + return false; + range = new IntRange(i, upper.IntValue); + return true; + } + + /// Parse a float range starting at the given token + /// The parser will step over the given lower bound + /// Will only return false in the case of an error + internal bool ParseFloatDatum(out decimal f, out FloatRange? range) + { + range = null; + Token start = _current; + f = start.FloatValue; + if (!Skip(TokenKind.CLOSED_RANGE)) + return true; + if (!Expect(TokenKind.FLOAT_LITERAL, out Token upper)) + return false; + range = new FloatRange(f, upper.FloatValue); + return true; + } + + /// Parse array data into the most refined type possible + internal bool ParseArrayDatum([NotNullWhen(true)] out Datum? array) + { array = null; + if (!Skip(TokenKind.OPEN_BRACKET)) + return false; + List? ints = null; List? bools = null; List? floats = null; List? strings = null; - List? values = null; - + List? values = null; + DatumKind type = default; + bool opt = false; while (_kind is not TokenKind.CLOSE_BRACKET) { - switch (_kind) + switch (_kind, type, opt) { - case TokenKind.INT_LITERAL: - (ints ??= []).Add(_current.IntValue); - Step(); + // First value is <> + case (TokenKind.EMPTY, DatumKind.Unknown, false): + values = [Datum.Empty]; + opt = true; break; - case TokenKind.FLOAT_LITERAL: - (floats ??= []).Add(_current.DecimalValue); - Step(); + // <> found in IntArray + case (TokenKind.EMPTY, DatumKind.Int, false): + values = []; + foreach (var ix in ints!) + values.Add(Datum.Int(ix)); + values.Add(Datum.Empty); + opt = true; break; - case TokenKind.KEYWORD_TRUE: + // <> found in FloatArray + case (TokenKind.EMPTY, DatumKind.Float, false): + values = []; + foreach (var fx in floats!) + values.Add(Datum.Float(fx)); + values.Add(Datum.Empty); + opt = true; + break; + + // <> found in StringArray + case (TokenKind.EMPTY, DatumKind.String, false): + values = []; + foreach (var s in strings!) + values.Add(Datum.String(s)); + values.Add(Datum.Empty); + opt = true; + break; + + // <> found in BoolArray + case (TokenKind.EMPTY, DatumKind.Bool, false): + values = []; + foreach (var b in bools!) + values.Add(Datum.Bool(b)); + values.Add(Datum.Empty); + opt = true; + break; + + // <> occured + case (TokenKind.EMPTY, _, _): + values!.Add(Datum.Empty); + opt = true; + break; + + // Int or IntRange + case (TokenKind.INT_LITERAL, _, _): + if (!ParseIntDatum(out int i, out var intRange)) + { + return false; + } + else if (intRange is not null) + { + (values ??= []).Add(intRange); + if (type is not DatumKind.Set) + return Expected($"datum of type {type}, got IntRange"); + type = DatumKind.Set; + } + else if (opt) + { + values!.Add(Datum.Int(i)); + type = DatumKind.Int; + } + else + { + (ints ??= []).Add(i); + type = DatumKind.Int; + } + break; + + // Float or FloatRange + case (TokenKind.FLOAT_LITERAL, _, _): + if (!ParseFloatDatum(out decimal f, out var floatRange)) + { + return false; + } + else if (floatRange is not null) + { + (values ??= []).Add(floatRange); + if (type is not DatumKind.Set) + return Expected($"datum of type {type}, got FloatRange"); + type = DatumKind.Set; + } + else if (opt) + { + values!.Add(Datum.Float(f)); + type = DatumKind.Float; + } + else + { + (floats ??= []).Add(f); + type = DatumKind.Float; + } + break; + + case (TokenKind.KEYWORD_TRUE, DatumKind.Unknown or DatumKind.Bool, false): (bools ??= []).Add(true); + type = DatumKind.Bool; Step(); break; - case TokenKind.KEYWORD_FALSE: + case (TokenKind.KEYWORD_TRUE, DatumKind.Unknown or DatumKind.Bool, true): + values!.Add(Datum.True); + type = DatumKind.Bool; + Step(); + break; + + case (TokenKind.KEYWORD_FALSE, DatumKind.Unknown or DatumKind.Bool, false): (bools ??= []).Add(false); + type = DatumKind.Bool; + Step(); + break; + + case (TokenKind.KEYWORD_FALSE, DatumKind.Unknown or DatumKind.Bool, true): + values!.Add(Datum.False); + type = DatumKind.Bool; Step(); break; - case TokenKind.STRING_LITERAL: + case (TokenKind.STRING_LITERAL, DatumKind.Unknown or DatumKind.String, false): (strings ??= []).Add(_current.StringValue); + type = DatumKind.String; + Step(); + break; + + case (TokenKind.STRING_LITERAL, DatumKind.Unknown or DatumKind.String, true): + values!.Add(Datum.String(_current.StringValue)); + type = DatumKind.String; Step(); break; default: - if (!ParseValueAtom(out var value)) + if (!ParseDatum(out var datum)) return false; - (values ??= []).Add(value); + if (type is not DatumKind.Unknown) + if (type != datum.Kind) + return Expected($"Datum of type {type}, got {datum.Kind}"); + type = datum.Kind; + (values ??= []).Add(datum); break; } @@ -887,48 +1036,50 @@ internal bool ParseArrayData([NotNullWhen(true)] out DataNode? array) return false; if (ints is not null) - array = new IntArrayData(ints); + array = new IntArray(ints); else if (floats is not null) - array = new FloatArrayData(floats); + array = new FloatArray(floats); else if (bools is not null) - array = new BoolArrayData(bools); + array = new BoolArray(bools); else if (strings is not null) - array = new StringArrayData(strings); + array = new StringArray(strings); else - array = new ArrayData(values ?? []); + array = new DatumArray(values ?? []); return true; } /// Parse set data into the most refined type possible - internal bool ParseSetData([NotNullWhen(true)] out DataNode? value) + internal bool ParseSetDatum([NotNullWhen(true)] out Datum? set) { - Step(); - value = null; - List? intSet = null; - List? boolSet = null; - List? floatSet = null; - List? set = null; + set = null; + if (!Skip(TokenKind.OPEN_BRACE)) + return false; + + List? ints = null; + List? bools = null; + List? floats = null; + List? data = null; while (_kind is not TokenKind.CLOSE_BRACE) { var token = _current; Step(); - switch (_kind) + switch (token.Kind) { case TokenKind.INT_LITERAL: - (intSet ??= []).Add(token.IntValue); + (ints ??= []).Add(token.IntValue); break; case TokenKind.FLOAT_LITERAL: - (floatSet ??= []).Add(token.DecimalValue); + (floats ??= []).Add(token.FloatValue); break; case TokenKind.KEYWORD_TRUE: - (boolSet ??= []).Add(true); + (bools ??= []).Add(true); break; case TokenKind.KEYWORD_FALSE: - (boolSet ??= []).Add(false); + (bools ??= []).Add(false); break; default: @@ -942,68 +1093,103 @@ internal bool ParseSetData([NotNullWhen(true)] out DataNode? value) if (!Expect(TokenKind.CLOSE_BRACE)) return false; - if (intSet is not null) - value = new IntSetData(intSet); - else if (floatSet is not null) - value = new FloatSetData(floatSet); - else if (boolSet is not null) - value = new BoolSetData(boolSet); + if (ints is not null) + set = new IntSet(ints); + else if (floats is not null) + set = new FloatSet(floats); + else if (bools is not null) + set = new BoolSet(bools); else - value = new SetData(set ?? []); + set = new SetDatum(data ?? []); return true; } /// - /// Parse a Value. - /// A value is a subset of Expressions that can be found in MiniZinc data files. + /// Parse a . /// /// 1 /// true /// {1,2,3} /// 1..10 - internal bool ParseValue([NotNullWhen(true)] out DataNode? value) + public bool ParseDatum([NotNullWhen(true)] out Datum? datum) { - value = null; - + datum = null; switch (_kind) { case TokenKind.OPEN_BRACE: - if (!ParseSetData(out value)) + if (!ParseSetDatum(out datum)) return false; break; case TokenKind.OPEN_BRACKET: - if (!ParseArrayData(out value)) + if (!ParseArrayDatum(out datum)) return false; break; case TokenKind.OPEN_PAREN when Peek().Kind is TokenKind.IDENTIFIER: - if (!ParseRecordData(out value)) + if (!ParseRecordDatum(out datum)) return false; break; case TokenKind.OPEN_PAREN: - if (!ParseTupleData(out value)) + if (!ParseTupleDatum(out datum)) return false; break; - default: - if (!ParseValueAtom(out value)) + case TokenKind.INT_LITERAL: + if (!ParseIntDatum(out int i, out var intRange)) + return false; + else if (intRange is not null) + datum = intRange; + else + datum = new IntDatum(i); + break; + + case TokenKind.FLOAT_LITERAL: + if (!ParseFloatDatum(out decimal f, out var floatRange)) return false; + else if (floatRange is not null) + datum = floatRange; + else + datum = new FloatDatum(f); + break; + + case TokenKind.KEYWORD_TRUE: + Step(); + datum = Datum.True; break; + + case TokenKind.KEYWORD_FALSE: + Step(); + datum = Datum.False; + break; + + case TokenKind.STRING_LITERAL: + var s = _current.StringValue; + Step(); + datum = new StringDatum(s); + break; + + case TokenKind.EMPTY: + Step(); + datum = Datum.Empty; + break; + + default: + return Error($"Unexpected token {_current}"); } return true; } - internal bool ParseTupleData([NotNullWhen(true)] out DataNode? value) + internal bool ParseTupleDatum([NotNullWhen(true)] out Datum? value) { Step(); value = null; - List values = []; + List values = []; while (_kind is not TokenKind.CLOSE_PAREN) { - if (!ParseValue(out value)) + if (!ParseDatum(out value)) return false; values.Add(value); if (!Skip(TokenKind.COMMA)) @@ -1013,19 +1199,19 @@ internal bool ParseTupleData([NotNullWhen(true)] out DataNode? value) if (!Expect(TokenKind.CLOSE_PAREN)) return false; - value = new TupleData(values); + value = new DatumTuple(values); return true; } - internal bool ParseRecordData([NotNullWhen(true)] out DataNode? value) + internal bool ParseRecordDatum([NotNullWhen(true)] out Datum? value) { value = null; if (!Expect(TokenKind.OPEN_PAREN)) return false; - Dictionary fields = []; - value = new RecordData(fields); + Dictionary fields = []; + value = new RecordDatum(fields); while (_kind is not TokenKind.CLOSE_PAREN) { @@ -1037,7 +1223,7 @@ internal bool ParseRecordData([NotNullWhen(true)] out DataNode? value) if (!Expect(TokenKind.COLON)) return false; - if (!ParseValue(out var fieldValue)) + if (!ParseDatum(out var fieldValue)) return false; if (fields.ContainsKey(fieldName)) @@ -1060,61 +1246,12 @@ internal bool ParseRecordData([NotNullWhen(true)] out DataNode? value) /// /// 1 /// true - internal bool ParseValueAtom([NotNullWhen(true)] out DataNode? value) + internal bool ParseSimpleDatum([NotNullWhen(true)] out Datum? value) { value = null; Token token = _current; - Token right; switch (_kind) { - case TokenKind.INT_LITERAL: - Step(); - if (Skip(TokenKind.CLOSED_RANGE)) - { - if (!Expect(TokenKind.INT_LITERAL, out right)) - return false; - value = new IntRangeData(token.IntValue, right.IntValue); - } - else - { - value = new IntData(token.IntValue); - } - break; - - case TokenKind.FLOAT_LITERAL: - Step(); - if (Skip(TokenKind.CLOSED_RANGE)) - { - if (!Expect(TokenKind.FLOAT_LITERAL, out right)) - return false; - value = new FloatRangeData(token.DecimalValue, right.DecimalValue); - } - else - { - value = new FloatData(token.DecimalValue); - } - break; - - case TokenKind.KEYWORD_TRUE: - Step(); - value = DataNode.True; - break; - - case TokenKind.KEYWORD_FALSE: - Step(); - value = DataNode.False; - break; - - case TokenKind.STRING_LITERAL: - Step(); - value = new StringData(token.ToString()); - break; - - case TokenKind.EMPTY: - Step(); - value = DataNode.Empty; - break; - default: return false; } @@ -1125,11 +1262,6 @@ internal bool ParseValueAtom([NotNullWhen(true)] out DataNode? value) /// /// Parse an Expression /// - /// - /// - /// - /// - /// /// a + b + 100 /// sum([1,2,3]) /// arr[1] * arr[2] @@ -2511,6 +2643,8 @@ private bool ParseListType([NotNullWhen(true)] out TypeSyntax? list) private bool Expected(string msg) => Error($"Expected {msg}"); + private bool Unexpected(string msg) => Error($"Unexpected {msg}"); + /// Record the given message as an error and return false private bool Error(string? msg = null) { @@ -2588,7 +2722,7 @@ public static ParseResult ParseModelString(string text, out ModelSyntax model) return result; } - /// + /// public static ParseResult ParseModelFile(FileInfo file, out ModelSyntax model) => ParseModelFile(file.FullName, out model); @@ -2617,6 +2751,40 @@ public static ParseResult ParseDataFile(string path, out MiniZincData data) return result; } + /// + /// Parse a from the given string. + /// + /// + /// Parser.ParseDatum("{1, 2, 3}"); // IntSet([1,2,3]); + /// + public static bool ParseDatum(string text, [NotNullWhen(true)] out Datum? datum) + { + datum = null; + var parser = new Parser(text); + if (!parser.ParseDatum(out datum)) + return false; + return true; + } + + /// + /// Parse a from the given string. + /// + /// + /// Parser.ParseDatum%ltIntArray%gt("[1,2,1,2,3]"); // IntArray([1,2,3]); + /// + public static bool ParseDatum(string text, [NotNullWhen(true)] out T? datum) + where T : Datum + { + datum = null; + var parser = new Parser(text); + if (!parser.ParseDatum(out var mzDatum)) + return false; + if (mzDatum is not T t) + return false; + datum = t; + return true; + } + /// /// Parse the given minizinc data string. /// Data strings only allow assignments eg: `a = 10;` diff --git a/src/MiniZinc.Parser/SyntaxExtensions.cs b/src/MiniZinc.Parser/SyntaxExtensions.cs index 158b1e5..797cc5c 100644 --- a/src/MiniZinc.Parser/SyntaxExtensions.cs +++ b/src/MiniZinc.Parser/SyntaxExtensions.cs @@ -36,8 +36,8 @@ public static T Clone(this T node) /// /// Name of the model variable /// The variable does not exists or was not of the expected type - public static T Get(this IReadOnlyDictionary dict, string id) - where T : DataNode + public static T Get(this IReadOnlyDictionary dict, string id) + where T : Datum { var data = dict[id]; var value = (T)data; @@ -45,12 +45,12 @@ public static T Get(this IReadOnlyDictionary dict, string i } /// Try to get the solution assigned to the given variable - public static DataNode? TryGet(this IReadOnlyDictionary dict, string id) => + public static Datum? TryGet(this IReadOnlyDictionary dict, string id) => dict.GetValueOrDefault(id); /// Try to get the solution assigned to the given variable - public static T? TryGet(this IReadOnlyDictionary dict, string id) - where T : DataNode + public static T? TryGet(this IReadOnlyDictionary dict, string id) + where T : Datum { if (!dict.TryGetValue(id, out var data)) return null; diff --git a/src/MiniZinc.Parser/Token.cs b/src/MiniZinc.Parser/Token.cs index 8a0d5b3..058d64f 100644 --- a/src/MiniZinc.Parser/Token.cs +++ b/src/MiniZinc.Parser/Token.cs @@ -12,8 +12,7 @@ public readonly struct Token public readonly object? Data; public int IntValue => (int)Data!; public string StringValue => (string)Data!; - public decimal DecimalValue => (decimal)Data!; - public bool BoolValue => Kind is TokenKind.KEYWORD_TRUE; + public decimal FloatValue => (decimal)Data!; public int End => Start + Length; public Token(TokenKind kind, int line, int col, int start, int length, object? data = null) @@ -121,7 +120,7 @@ public override string ToString() => TokenKind.PIPE => "|", TokenKind.EMPTY => "<>", TokenKind.INT_LITERAL => IntValue.ToString(), - TokenKind.FLOAT_LITERAL => DecimalValue.ToString(CultureInfo.InvariantCulture), + TokenKind.FLOAT_LITERAL => FloatValue.ToString(CultureInfo.InvariantCulture), TokenKind.STRING_LITERAL => $"\"{Data}\"", TokenKind.KEYWORD_ANONENUM => "anon_enum", TokenKind.NOT_EQUAL => "!=", diff --git a/src/MiniZinc.Parser/Writer.cs b/src/MiniZinc.Parser/Writer.cs index 0726546..a2f7df8 100644 --- a/src/MiniZinc.Parser/Writer.cs +++ b/src/MiniZinc.Parser/Writer.cs @@ -457,39 +457,39 @@ public void WriteExpr(ExpressionSyntax? expr, int? prec = null) } } - internal void WriteValue(DataNode dataSyntax) + internal void WriteValue(Datum dataSyntax) { switch (dataSyntax) { - case IntArrayData x: + case IntArray x: WriteValues(x, WriteInt); break; - case BoolArrayData x: + case BoolArray x: WriteValues(x, WriteBool); break; - case FloatArrayData x: + case FloatArray x: WriteValues(x, WriteDecimal); break; - case StringArrayData x: + case StringArray x: WriteValues(x, WriteString); break; - case ArrayData x: + case DatumArray x: WriteValues(x, WriteValue); break; - case BoolData x: + case BoolDatum x: WriteBool(x); break; - case EmptyData x: + case EmptyDatum x: WriteChar(OPEN_CHEVRON); WriteChar(CLOSE_CHEVRON); break; - case IntData x: + case IntDatum x: WriteInt(x); break; - case FloatData x: + case FloatDatum x: WriteDecimal(x); break; - case RecordData x: + case RecordDatum x: WriteValues( x, pair => @@ -502,24 +502,24 @@ internal void WriteValue(DataNode dataSyntax) after: CLOSE_PAREN ); break; - case IntSetData x: + case IntSet x: WriteValues(x, WriteInt, before: OPEN_BRACE, after: CLOSE_BRACE); break; - case FloatSetData x: + case FloatSet x: WriteValues(x, WriteDecimal, before: OPEN_BRACE, after: CLOSE_BRACE); break; - case BoolSetData x: + case BoolSet x: WriteValues(x, WriteBool, before: OPEN_BRACE, after: CLOSE_BRACE); break; - case SetData x: + case SetDatum x: WriteValues(x, WriteValue, before: OPEN_BRACE, after: CLOSE_BRACE); break; - case StringData x: + case StringDatum x: WriteChar(DOUBLE_QUOTE); WriteString(x.Value); WriteChar(DOUBLE_QUOTE); break; - case TupleData x: + case DatumTuple x: WriteChar(OPEN_PAREN); foreach (var item in x) { @@ -528,13 +528,13 @@ internal void WriteValue(DataNode dataSyntax) } WriteChar(CLOSE_PAREN); break; - case FloatRangeData x: + case FloatRange x: WriteDecimal(x.Lower); WriteChar(DOT); WriteChar(DOT); WriteDecimal(x.Upper); break; - case IntRangeData x: + case IntRange x: WriteInt(x.Lower); WriteChar(DOT); WriteChar(DOT); diff --git a/test/ClientTests/ClientTest.cs b/test/ClientTests/ClientTest.cs index 8caaa43..e7a1cc2 100644 --- a/test/ClientTests/ClientTest.cs +++ b/test/ClientTests/ClientTest.cs @@ -147,7 +147,7 @@ public bool Check(decimal a, decimal b) return ra == rb; } - public bool Check(IntLiteralSyntax value, IntRangeData rangeData) + public bool Check(IntLiteralSyntax value, IntRange rangeData) { if (!Check(value.Value, rangeData.Lower)) return false; @@ -156,7 +156,7 @@ public bool Check(IntLiteralSyntax value, IntRangeData rangeData) return true; } - public bool Check(FloatLiteralSyntax value, FloatRangeData range) + public bool Check(FloatLiteralSyntax value, FloatRange range) { if (!Check(value.Value, range.Lower)) return false; @@ -165,7 +165,7 @@ public bool Check(FloatLiteralSyntax value, FloatRangeData range) return true; } - public bool Check(IntSetData set, IntRangeData rangeData) + public bool Check(IntSet set, IntRange rangeData) { foreach (var value in set) { @@ -178,7 +178,7 @@ public bool Check(IntSetData set, IntRangeData rangeData) return true; } - public bool Check(FloatSetData set, FloatRangeData range) + public bool Check(FloatSet set, FloatRange range) { foreach (var value in set) { @@ -194,52 +194,52 @@ public bool Check(FloatSetData set, FloatRangeData range) /// /// Compare the solution against the json node /// - public bool Check(DataNode expected, DataNode actual) + public bool Check(Datum expected, Datum actual) { int i = 0; switch (expected, actual) { - case (IntData value, IntRangeData range): + case (IntDatum value, IntRange range): if (!Check(value, range)) return false; break; - case (IntRangeData range, IntData value): + case (IntRange range, IntDatum value): if (!Check(value, range)) return false; break; - case (FloatData value, FloatRangeData range): + case (FloatDatum value, FloatRange range): if (!Check(value, range)) return false; break; - case (FloatRangeData range, FloatData value): + case (FloatRange range, FloatDatum value): if (!Check(value, range)) return false; break; - case (FloatRangeData range, SetData set): + case (FloatRange range, SetDatum set): if (!Check(set, range)) return false; break; - case (SetData set, FloatRangeData range): + case (SetDatum set, FloatRange range): if (!Check(set, range)) return false; break; - case (IntRangeData range, IntSetData set): + case (IntRange range, IntSet set): if (!Check(set, range)) return false; break; - case (IntSetData set, IntRangeData range): + case (IntSet set, IntRange range): if (!Check(set, range)) return false; break; - case (ArrayData array, TupleData tuple): + case (DatumArray array, DatumTuple tuple): for (i = 0; i < array.Count; i++) { var e = array[i]; diff --git a/test/ClientTests/ClientUnitTests.cs b/test/ClientTests/ClientUnitTests.cs index d718c73..037a76c 100644 --- a/test/ClientTests/ClientUnitTests.cs +++ b/test/ClientTests/ClientUnitTests.cs @@ -35,8 +35,8 @@ async void test_solve_satisfy_result() var b = model.AddInt("b", 10, 20); model.AddConstraint(a < b); var result = await Client.Solve(model); - int aval = result.Data.Get(a); - int bval = result.Data.Get(b); + int aval = result.Data.Get(a); + int bval = result.Data.Get(b); aval.Should().BeLessThan(bval); result.Status.Should().Be(SolveStatus.Satisfied); } @@ -58,8 +58,8 @@ async void test_solve_maximize_result() model.Maximize("a + b"); var result = await Client.Solve(model); result.Status.Should().Be(SolveStatus.Optimal); - int a = result.Data.Get("a"); - int b = result.Data.Get("b"); + int a = result.Data.Get("a"); + int b = result.Data.Get("b"); a.Should().Be(20); b.Should().Be(20); result.Objective.Should().Be(40); @@ -70,7 +70,7 @@ async void test_solve_return_array() { var model = MiniZincModel.FromString("array[1..10] of var 0..100: xd;"); var result = await Client.Solve(model); - var arr = result.Data.Get("xd"); + var arr = result.Data.Get("xd"); } [Fact] @@ -81,8 +81,8 @@ async void test_solve_satisfy_foreach() model.AddVariable("b", "10..20"); await foreach (var result in Client.Solve(model)) { - int a = result.Data.Get("a"); - int b = result.Data.Get("b"); + int a = result.Data.Get("a"); + int b = result.Data.Get("b"); result.Status.Should().Be(SolveStatus.Satisfied); } } diff --git a/test/ParserTests/DataUnitTests.cs b/test/ParserTests/DataUnitTests.cs index a2c9dd9..ba66fab 100644 --- a/test/ParserTests/DataUnitTests.cs +++ b/test/ParserTests/DataUnitTests.cs @@ -1,6 +1,7 @@ namespace MiniZinc.Tests; using Parser; +using static Parser.Parser; public class DataUnitTests { @@ -9,9 +10,9 @@ public class DataUnitTests [InlineData("{1}", "{1}")] void test_data_eq(string mznA, string mznB) { - Parser.ParseDataString(mznA, out var a).Ok.Should().BeTrue(); - Parser.ParseDataString(mznB, out var b).Ok.Should().BeTrue(); - a.Should().Equal(b); + ParseDatum(mznA, out var a).Should().BeTrue(); + ParseDatum(mznB, out var b).Should().BeTrue(); + a.Should().Be(b); } [Theory] @@ -20,9 +21,30 @@ void test_data_eq(string mznA, string mznB) [InlineData("{1.5}", "1.5..1.5")] void test_data_set_eq(string mznA, string mznB) { - Parser.ParseDataString(mznA, out var a); - Parser.ParseDataString(mznB, out var b); + ParseDataString(mznA, out var a); + ParseDataString(mznB, out var b); a.Should().Equal(b); b.Should().Equal(a); } + + [Fact] + void test_empty_datum() + { + ParseDatum("<>", out var datum); + datum.Should().Be(Datum.Empty); + } + + [Fact] + void test_array_datum() + { + ParseDatum("[1,2,3,2,1]", out var array); + array.Should().Equal(1, 2, 3, 2, 1); + } + + [Fact] + void test_mixed_array() + { + var ok = ParseDatum("[1,2.0, <>, 1]", out var array); + ok.Should().BeFalse(); + } }