diff --git a/src/MiniZinc.Client/SolveResult.cs b/src/MiniZinc.Client/SolveResult.cs index c567473..7d26fb2 100644 --- a/src/MiniZinc.Client/SolveResult.cs +++ b/src/MiniZinc.Client/SolveResult.cs @@ -126,62 +126,6 @@ public abstract record SolveResult SolveStatus.Unsatisfiable => true, _ => false }; - - /// - /// Get the solution assigned to the given variable - /// - /// Name of the model variable - /// The variable does not exists or was not of the expected type - public U Get(string id) - where U : ExpressionSyntax - { - if (TryGet(id) is not { } value) - throw new KeyNotFoundException($"Result did not contain a solution for \"{id}\""); - - return value; - } - - public ValueSyntax? TryGet(string id) => Data.Values.GetValueOrDefault(id); - - /// - /// Try to get the solution assigned to the given variable - /// - /// Name of the model variable - public U? TryGet(string id) - where U : ExpressionSyntax - { - var value = TryGet(id); - if (value is null) - return null; - - if (value is not U u) - throw new Exception(); - - return u; - } - - public ExpressionSyntax this[string name] => Get(name); - - /// Get the int solution for the given variable - public int GetInt(string id) => Get(id).Value; - - /// Get the bool solution for the given variable - public bool GetBool(string id) => Get(id).Value; - - /// Get the float solution for the given variable - public decimal GetFloat(string id) => Get(id).Value; - - /// Get the array solution for the given variable - public IEnumerable GetArray1D(string id) - { - var array = Get(id); - foreach (var node in array.Elements) - { - if (node is not ValueSyntax literal) - throw new Exception(); - yield return literal.Value; - } - } } public sealed record SolveResult : SolveResult { } diff --git a/src/MiniZinc.Client/SolverProcess.cs b/src/MiniZinc.Client/SolverProcess.cs index 77f8b04..10ba200 100644 --- a/src/MiniZinc.Client/SolverProcess.cs +++ b/src/MiniZinc.Client/SolverProcess.cs @@ -258,7 +258,7 @@ private void OnSolutionOutput(SolutionOutput o) goto send; } - _data.Values.TryGetValue("_objective", out var objectiveNode); + _data.TryGetValue("_objective", out var objectiveNode); switch (objectiveNode) { case null: diff --git a/src/MiniZinc.Parser/DataSyntax.cs b/src/MiniZinc.Parser/DataSyntax.cs index e3ce180..f460c7b 100644 --- a/src/MiniZinc.Parser/DataSyntax.cs +++ b/src/MiniZinc.Parser/DataSyntax.cs @@ -11,11 +11,10 @@ /// Data is different from a in that it can only /// contain assignments of the form `$name = $expr;` /// -public sealed class DataSyntax(Dictionary values) - : IEquatable> +public sealed class DataSyntax(Dictionary dict) + : IEquatable>, + IReadOnlyDictionary { - public IReadOnlyDictionary Values => values; - public string Write(WriteOptions? options = null) { var writer = new Writer(options); @@ -30,6 +29,11 @@ public override string ToString() return mzn; } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + public bool Equals(IReadOnlyDictionary? other) { if (other is null) @@ -39,7 +43,7 @@ public bool Equals(IReadOnlyDictionary? other) return true; // TODO - faster/better - foreach (var kv in values) + foreach (var kv in dict) { var name = kv.Key; var a = kv.Value; @@ -57,13 +61,15 @@ public bool Equals(IReadOnlyDictionary? other) foreach (var kv in other) { var name = kv.Key; - if (!values.ContainsKey(name)) + if (!dict.ContainsKey(name)) return false; } return true; } + public IEnumerator> GetEnumerator() => dict.GetEnumerator(); + public override bool Equals(object? obj) { if (!Equals(obj as IReadOnlyDictionary)) @@ -71,4 +77,69 @@ public override bool Equals(object? obj) return true; } + + /// + /// Get the solution assigned to the given variable + /// + /// Name of the model variable + /// The variable does not exists or was not of the expected type + public U Get(string id) + where U : ExpressionSyntax + { + if (TryGet(id) is not { } value) + throw new KeyNotFoundException($"Result did not contain a solution for \"{id}\""); + + return value; + } + + public ValueSyntax? TryGet(string id) => dict.GetValueOrDefault(id); + + /// + /// Try to get the solution assigned to the given variable + /// + /// Name of the model variable + public U? TryGet(string id) + where U : ExpressionSyntax + { + var value = TryGet(id); + if (value is null) + return null; + + if (value is not U u) + throw new Exception(); + + return u; + } + + public bool ContainsKey(string key) => dict.ContainsKey(key); + + public bool TryGetValue(string key, out ValueSyntax value) => dict.TryGetValue(key, out value); + + public ValueSyntax this[string name] => Get(name); + + public IEnumerable Keys => dict.Keys; + public IEnumerable Values => dict.Values; + + /// Get the int solution for the given variable + public int GetInt(string id) => Get(id); + + /// Get the bool solution for the given variable + public bool GetBool(string id) => Get(id); + + /// Get the float solution for the given variable + public decimal GetFloat(string id) => Get(id); + + /// Get the array solution for the given variable + public IEnumerable GetArray1D(string id) + { + var array = Get(id); + foreach (var node in array.Values) + { + if (node is not ValueSyntax literal) + throw new Exception(); + yield return literal.Value; + } + } + + public int Count { get; } } diff --git a/src/MiniZinc.Parser/Expressions/IdentifierSyntax.cs b/src/MiniZinc.Parser/Expressions/IdentifierSyntax.cs index be5eff6..1443aa1 100644 --- a/src/MiniZinc.Parser/Expressions/IdentifierSyntax.cs +++ b/src/MiniZinc.Parser/Expressions/IdentifierSyntax.cs @@ -10,4 +10,6 @@ public IdentifierSyntax(in Token token) public string Name => Start.StringValue; public override string ToString() => Start.ToString(); + + public static implicit operator string(IdentifierSyntax f) => f.Name; } diff --git a/src/MiniZinc.Parser/Parser.cs b/src/MiniZinc.Parser/Parser.cs index 9fa6ebc..595eba1 100644 --- a/src/MiniZinc.Parser/Parser.cs +++ b/src/MiniZinc.Parser/Parser.cs @@ -1,4 +1,6 @@ -namespace MiniZinc.Parser; +using MiniZinc.Parser.Values; + +namespace MiniZinc.Parser; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -130,6 +132,21 @@ private bool ParseIdent([NotNullWhen(true)] out IdentifierSyntax? node) } } + private bool TryParseIdent([NotNullWhen(true)] out IdentifierSyntax? node) + { + if (_kind is TokenKind.IDENTIFIER) + { + node = new IdentifierSyntax(_token); + Step(); + return true; + } + else + { + node = null; + return false; + } + } + private bool ParseGeneric(out IdentifierSyntax ident) { if (_kind is TokenKind.GENERIC or TokenKind.GENERIC_SEQUENCE) @@ -883,7 +900,175 @@ internal bool ParseExprAtomTail(ref ExpressionSyntax expr) internal bool ParseValue([NotNullWhen(true)] out ValueSyntax? value) { value = null; - return false; + List items; + + switch (_kind) + { + case TokenKind.MINUS: + if (!ParseValueAtom(out value)) + return false; + break; + + case TokenKind.OPEN_BRACE: + Step(); + items = []; + while (_kind is not TokenKind.CLOSE_BRACE) + { + if (!ParseValueAtom(out var item)) + return false; + items.Add(item); + if (!Skip(TokenKind.COMMA)) + break; + } + + if (!Expect(TokenKind.CLOSE_BRACE)) + return false; + + value = new SetValueSyntax(items); + break; + + case TokenKind.OPEN_BRACKET: + Step(); + items = []; + while (_kind is not TokenKind.CLOSE_BRACKET) + { + if (!ParseValueAtom(out var item)) + return false; + items.Add(item); + if (!Skip(TokenKind.COMMA)) + break; + } + if (!Expect(TokenKind.CLOSE_BRACKET)) + return false; + + value = new Array1dValueSyntax(items); + break; + + case TokenKind.OPEN_PAREN: + Step(); + + if (_kind is TokenKind.IDENTIFIER) + { + var fields = new Dictionary(); + while (_kind is not TokenKind.CLOSE_PAREN) + { + if (!ParseIdent(out var ident)) + return false; + var name = ident.Name; + + if (!Expect(TokenKind.COLON)) + return false; + + if (!ParseValue(out value)) + return false; + + if (fields.ContainsKey(name)) + return Error($"Duplicate name {name}"); + + fields[name] = value; + + if (!Skip(TokenKind.COMMA)) + break; + } + + if (!Expect(TokenKind.CLOSE_PAREN)) + return false; + + value = new RecordValueSyntax(fields); + } + else + { + items = []; + while (_kind is not TokenKind.CLOSE_PAREN) + { + if (!ParseValueAtom(out value)) + return false; + items.Add(value); + if (!Skip(TokenKind.COMMA)) + break; + } + + if (!Expect(TokenKind.CLOSE_PAREN)) + return false; + + value = new TupleValueSyntax(items); + } + break; + + default: + if (!ParseValueAtom(out value)) + return false; + break; + } + + return true; + } + + /// + /// Parse a Value. + /// A value is a subset of Expressions that can be found in MiniZinc data files. + /// + /// 1 + /// true + internal bool ParseValueAtom([NotNullWhen(true)] out ValueSyntax? value) + { + value = null; + Token token = _token; + Token right; + switch (_kind) + { + case TokenKind.INT_LITERAL: + Step(); + if (Skip(TokenKind.RANGE_INCLUSIVE)) + { + if (!Expect(TokenKind.INT_LITERAL, out right)) + return false; + value = new IntRange(token.IntValue, right.IntValue); + } + else + { + value = new IntLiteralSyntax(token); + } + break; + + case TokenKind.FLOAT_LITERAL: + Step(); + if (Skip(TokenKind.RANGE_INCLUSIVE)) + { + if (!Expect(TokenKind.FLOAT_LITERAL, out right)) + return false; + value = new FloatRange(token.DecimalValue, right.DecimalValue); + } + else + { + value = new IntLiteralSyntax(token); + } + break; + + case TokenKind.TRUE: + Step(); + value = new BoolLiteralSyntax(token); + break; + + case TokenKind.FALSE: + Step(); + value = new BoolLiteralSyntax(token); + break; + + case TokenKind.STRING_LITERAL: + Step(); + value = new StringLiteralSyntax(token); + break; + + case TokenKind.EMPTY: + Step(); + value = new EmptyLiteralSyntax(token); + break; + + default: + return false; + } + return true; } /// diff --git a/src/MiniZinc.Parser/Values/FloatLiteralSyntax.cs b/src/MiniZinc.Parser/Values/FloatLiteralSyntax.cs index 85ff01b..dbdc0f7 100644 --- a/src/MiniZinc.Parser/Values/FloatLiteralSyntax.cs +++ b/src/MiniZinc.Parser/Values/FloatLiteralSyntax.cs @@ -9,4 +9,6 @@ public FloatLiteralSyntax(in Token Start, decimal Value) : base(Start, Value) { } public override string ToString() => Value.ToString("g"); + + public static implicit operator decimal(FloatLiteralSyntax f) => f.Value; } diff --git a/src/MiniZinc.Parser/Values/IntRange.cs b/src/MiniZinc.Parser/Values/IntRange.cs new file mode 100644 index 0000000..50917f6 --- /dev/null +++ b/src/MiniZinc.Parser/Values/IntRange.cs @@ -0,0 +1,13 @@ +namespace MiniZinc.Parser.Values; + +public sealed class IntRange(int lower, int upper) : ValueSyntax(default) +{ + public int Lower => lower; + public int Upper => upper; +} + +public sealed class FloatRange(decimal lower, decimal upper) : ValueSyntax(default) +{ + public decimal Lower => lower; + public decimal Upper => upper; +} diff --git a/src/MiniZinc.Parser/Values/RecordValueSyntax.cs b/src/MiniZinc.Parser/Values/RecordValueSyntax.cs index 29aed14..3415c0d 100644 --- a/src/MiniZinc.Parser/Values/RecordValueSyntax.cs +++ b/src/MiniZinc.Parser/Values/RecordValueSyntax.cs @@ -1,6 +1,6 @@ namespace MiniZinc.Parser; -public sealed class RecordValueSyntax(Dictionary map) : ValueSyntax(default) +public sealed class RecordValueSyntax(Dictionary fields) : ValueSyntax(default) { - private IReadOnlyDictionary Values => map; -} \ No newline at end of file + public IReadOnlyDictionary Fields => fields; +} diff --git a/src/MiniZinc.Parser/Values/TupleValueSyntax.cs b/src/MiniZinc.Parser/Values/TupleValueSyntax.cs index 9f35643..46eeea9 100644 --- a/src/MiniZinc.Parser/Values/TupleValueSyntax.cs +++ b/src/MiniZinc.Parser/Values/TupleValueSyntax.cs @@ -1,6 +1,6 @@ namespace MiniZinc.Parser; -public sealed class TupleValueSyntax(List<(string, ValueSyntax)> fields) : ValueSyntax(default) +public sealed class TupleValueSyntax(List fields) : ValueSyntax(default) { - private IReadOnlyList<(string, ValueSyntax)> Fields => fields; -} \ No newline at end of file + private IReadOnlyList Fields => fields; +} diff --git a/src/MiniZinc.Parser/Writer.cs b/src/MiniZinc.Parser/Writer.cs index 047cbac..a9f18ad 100644 --- a/src/MiniZinc.Parser/Writer.cs +++ b/src/MiniZinc.Parser/Writer.cs @@ -120,7 +120,7 @@ public void Clear() public void WriteData(DataSyntax data) { - foreach (var (name, value) in data.Values) + foreach (var (name, value) in data) { WriteString(name); WriteSpace(); @@ -250,6 +250,12 @@ public void WriteExpr(ExpressionSyntax? expr, int? prec = null) WriteChar(CLOSE_BRACKET); break; + case Array1dValueSyntax e: + WriteChar(OPEN_BRACKET); + WriteSep(e.Values, WriteExpr); + WriteChar(CLOSE_BRACKET); + break; + case Array2dSyntax e: WriteArray2d(e); break; @@ -409,6 +415,32 @@ public void WriteExpr(ExpressionSyntax? expr, int? prec = null) WriteChar(CLOSE_PAREN); break; + case RecordValueSyntax e: + WriteChar(OPEN_PAREN); + int j = 0; + foreach (var (name, value) in e.Fields) + { + WriteString(name); + WriteChar(COLON); + WriteExpr(value); + if (j++ < e.Fields.Count) + WriteChar(COMMA); + } + WriteChar(CLOSE_PAREN); + break; + + case SetValueSyntax e: + WriteChar(OPEN_BRACE); + int j = 0; + foreach (var value in e.Values) + { + WriteExpr(value); + if (j++ < e.Values.Count) + WriteChar(COMMA); + } + WriteChar(CLOSE_BRACE); + break; + case SetLiteralSyntax e: WriteChar(OPEN_BRACE); WriteSep(e.Elements, WriteExpr); diff --git a/test/ClientTests/ClientTest.cs b/test/ClientTests/ClientTest.cs index 1edbaff..cc4ff39 100644 --- a/test/ClientTests/ClientTest.cs +++ b/test/ClientTests/ClientTest.cs @@ -94,11 +94,11 @@ protected async Task Test( /// public bool Check(DataSyntax expectedData, DataSyntax actualData) { - foreach (var kv in expectedData.Values) + foreach (var kv in expectedData) { var name = kv.Key; var expectedVar = kv.Value; - if (!actualData.Values.TryGetValue(name, out var actualVar)) + if (!actualData.TryGetValue(name, out var actualVar)) continue; if (!Check(expectedVar, actualVar)) diff --git a/test/ClientTests/ClientUnitTests.cs b/test/ClientTests/ClientUnitTests.cs index 2e5d540..7ba6e94 100644 --- a/test/ClientTests/ClientUnitTests.cs +++ b/test/ClientTests/ClientUnitTests.cs @@ -36,8 +36,8 @@ async void test_solve_satisfy_result() var b = model.AddInt("b", 10, 20); model.AddConstraint(a < b); var result = await Client.Solve(model); - var aval = result.GetInt(a); - var bval = result.GetInt(b); + var aval = result.Data.GetInt(a); + var bval = result.Data.GetInt(b); aval.Should().BeLessThan(bval); result.Status.Should().Be(SolveStatus.Satisfied); } @@ -59,8 +59,8 @@ async void test_solve_maximize_result() model.Maximize("a + b"); var result = await Client.Solve(model); result.Status.Should().Be(SolveStatus.Optimal); - var a = result.GetInt("a"); - var b = result.GetInt("b"); + var a = result.Data.GetInt("a"); + var b = result.Data.GetInt("b"); a.Should().Be(20); b.Should().Be(20); result.Objective.Should().Be(40); @@ -71,7 +71,7 @@ async void test_solve_return_array() { var model = Model.FromString("array[1..10] of var 0..100: xd;"); var result = await Client.Solve(model); - var arr = result.GetArray1D("xd").ToArray(); + var arr = result.Data.GetArray1D("xd").ToArray(); } [Fact] @@ -82,8 +82,8 @@ async void test_solve_satisfy_foreach() model.AddVariable("b", "10..20"); await foreach (var result in Client.Solve(model)) { - var a = result.GetInt("a"); - var b = result.GetInt("b"); + var a = result.Data.GetInt("a"); + var b = result.Data.GetInt("b"); result.Status.Should().Be(SolveStatus.Satisfied); } }