Adding operators should work now

This commit is contained in:
Suzanne Soy 2020-08-22 00:05:56 +00:00
parent 62f7209b14
commit 1cf8cb6bd8
21 changed files with 543 additions and 37 deletions

14
DefaultGrammar.cs Normal file
View File

@ -0,0 +1,14 @@
using S = Lexer.S;
using PrecedenceDAG = ImmutableDefaultDictionary<string, MixFix.DAGNode>;
using static Global;
using static MixFix;
using static MixFix.Associativity;
public static class DefaultGrammar {
public static PrecedenceDAG DefaultPrecedenceDAG
= EmptyPrecedenceDAG
.WithOperator("bool", NonAssociative, "equality|terminal", S.And, "equality|terminal")
.WithOperator("equality", NonAssociative, "int", S.Eq, "int")
.WithOperator("int", NonAssociative, S.Int)
.WithOperator("terminal", NonAssociative, S.Ident);
}

View File

@ -8,6 +8,10 @@ public class ParserErrorException : UserErrorException {
public ParserErrorException(string e) : base("Parser error: " + e) {}
}
public class ParserExtensionException : UserErrorException {
public ParserExtensionException(string e) : base("Parser extension error: " + e) {}
}
public class LexerErrorException : UserErrorException {
public LexerErrorException(string e) : base("Lexer error: " + e) {}
}

View File

@ -63,6 +63,25 @@ public static partial class Lexer {
Rule(S.Space, C.SpaceSeparator, S.Space),
Rule(S.Space, EOF, S.End),
Rule(S.Space, '"', S.StringOpen, S.String),
Rule(S.Space, new[]{'='}, S.Eq),
Rule(S.Eq, new[]{'='}, S.Eq, S.Space),
Rule(S.Space, new[]{'&'}, S.And),
Rule(S.And, new[]{'&'}, S.And, S.Space),
// TODO: https://unicode.org/reports/tr31/#D1 has a lot to say about
// identifiers
Rule(S.Space, C.LowercaseLetter, S.Ident),
Rule(S.Space, C.UppercaseLetter, S.Ident),
Rule(S.Space, C.TitlecaseLetter, S.Ident),
Rule(S.Space, C.ModifierLetter, S.Ident),
Rule(S.Space, C.OtherLetter, S.Ident),
Rule(S.Space, C.LetterNumber, S.Ident),
Rule(S.Ident, C.LowercaseLetter, S.Ident),
Rule(S.Ident, C.UppercaseLetter, S.Ident),
Rule(S.Ident, C.TitlecaseLetter, S.Ident),
Rule(S.Ident, C.ModifierLetter, S.Ident),
Rule(S.Ident, C.OtherLetter, S.Ident),
Rule(S.Ident, C.LetterNumber, S.Ident),
Rule(S.Int, C.DecimalDigitNumber, S.Int),
Rule(S.Int, C.SpaceSeparator, S.Space),

View File

@ -14,6 +14,8 @@ public static class LexerGenerator {
Case("Space"),
Case("Int"),
Case("Decimal"),
Case("Ident"),
Case("And"),
Case("Eq"),
Case("Space"),
Case("String"),

View File

@ -9,7 +9,6 @@ run: main.exe Makefile
main.exe: $(CS) $(GENERATED) Makefile
mcs -debug+ -out:$@ \
/reference:/usr/lib/mono/fsharp/FSharp.Core.dll \
/reference:/usr/lib/mono/4.5/System.Collections.Immutable.dll \
/reference:/usr/lib/mono/4.5/Facades/netstandard.dll \
$(filter-out Makefile, $^)

240
MixFix.cs Normal file
View File

@ -0,0 +1,240 @@
using System;
using System.Text;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Immutable;
using S = Lexer.S;
using Lexeme = Lexer.Lexeme;
using PrecedenceDAG = ImmutableDefaultDictionary<string, MixFix.DAGNode>;
using PrecedenceGroupName = System.String;
using Hole = System.Collections.Immutable.ImmutableHashSet<
string /* PrecedenceGroupName */>;
using static Global;
using static MixFix.Fixity;
public class Foo {
public string v;
public static implicit operator Foo(string s) => new Foo{v=s};
public static Foo operator |(Foo a, string b) => a.v + b;
}
public static partial class MixFix {
public partial class Operator {
private string CustomToString()
=> $"Operator(\"{precedenceGroup}\", {fixity}, {parts.Select(x => x.Match(Hole: h => h.Select(g => g.ToString()).JoinWith("|"), Name: n => $"\"{n}\"")).JoinWith(", ")})";
}
public abstract partial class Part {
public static implicit operator Part(S s)
=> Part.Name(s);
public static implicit operator Part(string s)
=> Part.Hole(s.Split("|").ToImmutableHashSet());
}
public partial class Fixity {
// Used in the fixity table below.
public static implicit operator Func<Fixity>(Fixity fixity) => () => fixity;
}
public partial class Operator {
// TODO: cache this for improved complexity
public Option<ImmutableHashSet<string>> leftmostHole {
get => parts.First().Bind(firstPart => firstPart.AsHole);
}
// TODO: cache this for improved complexity
public Option<ImmutableHashSet<string>> rightmostHole {
get => parts.Last().Bind(lastPart => lastPart.AsHole);
}
private static Func<Fixity> error =
() => throw new Exception("Internal error: unexpected fixity.");
private static Func<Fixity> notYet =
() => throw new NotImplementedException(
"NonAssociative prefix and postfix operators are not supported for now.");
private ImmutableList<ImmutableList<Func<Fixity>>> fixityTable = new [] {
// neither start end both ←Hole//↓Associativity
new[]{ Closed, notYet, notYet, InfixNonAssociative },//NonAssociative
new[]{ error, Postfix, error, InfixLeftAssociative },//LeftAssociative
new[]{ error, error, Prefix, InfixRightAssociative },//RightAssociative
}.Select(ImmutableList.ToImmutableList).ToImmutableList();
public Fixity fixity {
get {
var startsWithHole = parts.First()
.Bind(lastPart => lastPart.AsHole)
.IsSome;
var endsWithHole = parts.First()
.Bind(lastPart => lastPart.AsHole)
.IsSome;
var row = associativity.Match(
NonAssociative: () => 0,
LeftAssociative: () => 1,
RightAssociative: () => 2);
var column = ((!startsWithHole) && (!endsWithHole)) ? 0
: (( startsWithHole) && (!endsWithHole)) ? 1
: ((!startsWithHole) && ( endsWithHole)) ? 2
: (( startsWithHole) && ( endsWithHole)) ? 3
: throw new Exception("impossible");
return fixityTable[row][column]();
}
}
}
public partial class DAGNode {
// TODO: cache this for improved complexity
public IEnumerable<Operator> allOperators {
get => ImmutableList(
closed,
prefix,
postfix,
infixNonAssociative,
infixRightAssociative,
infixLeftAssociative
).SelectMany(x => x);
}
// TODO: cache this for improved complexity
public Option<ImmutableHashSet<string>> leftmostHole {
get => allOperators.First(@operator => @operator.leftmostHole);
}
// TODO: cache this for improved complexity
public Option<ImmutableHashSet<string>> rightmostHole {
get => allOperators.First(@operator => @operator.rightmostHole);
}
}
public static DAGNode EmptyDAGNode = new DAGNode(
closed: ImmutableList<Operator>.Empty,
prefix: ImmutableList<Operator>.Empty,
postfix: ImmutableList<Operator>.Empty,
infixNonAssociative: ImmutableList<Operator>.Empty,
infixRightAssociative: ImmutableList<Operator>.Empty,
infixLeftAssociative: ImmutableList<Operator>.Empty
);
public static PrecedenceDAG EmptyPrecedenceDAG
= new PrecedenceDAG(EmptyDAGNode);
public static Whole Add<Whole>(this ILens<DAGNode, Whole> node, Operator @operator) {
return @operator.fixity.Match(
Closed:
() => node.Closed(),
Prefix:
() => node.Prefix(),
Postfix:
() => node.Postfix(),
InfixNonAssociative:
() => node.InfixNonAssociative(),
InfixRightAssociative:
() => node.InfixRightAssociative(),
InfixLeftAssociative:
() => node.InfixLeftAssociative()
).Cons(@operator);
}
public static void CheckHole(PrecedenceDAG precedenceDAG, Operator @operator, string name, Option<Hole> existing, Option<Hole> @new) {
existing.IfSome(existingHole =>
@new.IfSome(newHole => {
if (! newHole.SetEquals(existingHole)) {
throw new ParserExtensionException($"Cannot extend parser with operator {@operator}, its {name} hole ({newHole.ToString()}) must either be empty or else use the same precedence groups as the existing operators in {@operator.precedenceGroup}, i.e. {existingHole}.");
}
return unit;
})
);
}
public static void CheckLeftmostHole(PrecedenceDAG precedenceDAG, Operator @operator)
=> CheckHole(
precedenceDAG,
@operator,
"leftmost",
precedenceDAG[@operator.precedenceGroup].leftmostHole,
@operator.leftmostHole);
public static void CheckRightmostHole(PrecedenceDAG precedenceDAG, Operator @operator)
=> CheckHole(
precedenceDAG,
@operator,
"rightmost",
precedenceDAG[@operator.precedenceGroup].rightmostHole,
@operator.rightmostHole);
public static void CheckNonEmpty(PrecedenceDAG precedenceDAG, Operator @operator) {
if (@operator.parts.Count == 0) {
throw new ParserExtensionException($"Cannot extend parser with operator {@operator}, it has no parts.");
}
}
public static void CheckConsecutiveHoles(PrecedenceDAG precedenceDAG, Operator @operator) {
@operator.parts.Aggregate((previous, current) => {
if (previous.IsHole && current.IsHole) {
throw new ParserExtensionException($"Cannot extend parser with operator {@operator}, it has two consecutive holes. This is not supported for now.");
}
return current;
});
}
public static void CheckSingleUseOfNames(PrecedenceDAG precedenceDAG, Operator @operator) {
// TODO: check that each name part isn't used elsewhere in a way that could cause ambiguity (use in a different namespace which is bracketed is okay, etc.). Probably something to do with paths reachable from the root.
}
public static void CheckNotAlias(PrecedenceDAG precedenceDAG, Operator @operator) {
if (@operator.parts.Single().Bind(part => part.AsHole).IsSome) {
throw new ParserExtensionException($"Cannot extend parser with operator {@operator}, it only contains a single hole. Aliases built like this are not supported for now.");
}
}
public static PrecedenceDAG With(this PrecedenceDAG precedenceDAG, Operator @operator) {
// This is where all the checks are done to ensure that the
// resulting grammar is well-formed and assuredly unambiguous.
// Future extension idea: add an "ambiguous" keyword to
// alleviate some restrictions.
CheckLeftmostHole(precedenceDAG, @operator);
CheckRightmostHole(precedenceDAG, @operator);
CheckNonEmpty(precedenceDAG, @operator);
CheckConsecutiveHoles(precedenceDAG, @operator);
CheckSingleUseOfNames(precedenceDAG, @operator);
CheckNotAlias(precedenceDAG, @operator);
return precedenceDAG.lens[@operator.precedenceGroup].Add(@operator);
}
public static PrecedenceDAG WithOperator(this PrecedenceDAG precedenceDAG, string precedenceGroup, Associativity associativity, params Part[] parts)
=> precedenceDAG.With(
new Operator(
precedenceGroup: precedenceGroup,
associativity: associativity,
parts: parts.ToImmutableList()));
public static Grammar OperatorToGrammar(Operator @operator) {
//@operator.
throw new NotImplementedException();
}
public static Grammar DAGNodeToGrammar(DAGNode node) {
// return Grammar.Or(ImmutableList<Grammar>(
// node.closed
// closed,
// succl nonassoc succr,
// (prefix | succl rightassoc)+ succr,
// succl (suffix | succr rightassoc)+
// ));
throw new NotImplementedException();
}
public static void DAGToGrammar(DAGNode precedenceDAG) {
}
public static void RecursiveDescent(IEnumerable<Lexeme> e) {
}
}

48
MixFixGenerator.cs Normal file
View File

@ -0,0 +1,48 @@
using static Generator;
public static class ParserGenerator {
public static void Main() {
Generate(
"MixFixGenerated.cs",
"using System.Collections.Immutable;\n"
+ "using S = Lexer.S;\n"
+ "using PrecedenceGroupName = System.String;",
"public static partial class MixFix {",
"}",
"MixFix.",
Types(
Variant("Grammar",
Case("ImmutableList<Grammar>", "Or"),
Case("ImmutableList<Grammar>", "Sequence")),
Variant("Fixity",
Case("Closed"),
Case("InfixLeftAssociative"),
Case("InfixRightAssociative"),
Case("InfixNonAssociative"),
Case("Prefix"),
Case("Postfix")),
Variant("Associativity",
Case("NonAssociative"),
Case("LeftAssociative"),
Case("RightAssociative")),
Record("Operator",
Field("PrecedenceGroupName", "precedenceGroup"),
Field("Associativity", "associativity"),
Field("ImmutableList<Part>", "parts")),
Variant("Part",
Case("S", "Name"),
Case("ImmutableHashSet<PrecedenceGroupName>", "Hole")),
Record("DAGNode",
Field("ImmutableList<Operator>", "infixLeftAssociative"),
Field("ImmutableList<Operator>", "infixRightAssociative"),
Field("ImmutableList<Operator>", "infixNonAssociative"),
Field("ImmutableList<Operator>", "prefix"),
Field("ImmutableList<Operator>", "postfix"),
Field("ImmutableList<Operator>", "closed"))));
}
}

View File

@ -10,16 +10,19 @@ using static Global;
public static partial class Parser {
public static Ast.Expr Parse(string source) {
Log(DefaultGrammar.DefaultPrecedenceDAG.ToString());
return Lexer.Lex(source)
.SelectMany(lexeme =>
lexeme.state.Match(
Int: () => Ast.Expr.Int(Int32.Parse(lexeme.lexeme)).Singleton(),
String: () => Ast.Expr.String(lexeme.lexeme).Singleton(),
Ident: () => Enumerable.Empty<Ast.Expr>(), // TODO
And: () => Enumerable.Empty<Ast.Expr>(), // TODO
Space: () => Enumerable.Empty<Ast.Expr>(), // ignore
Eq: () => Enumerable.Empty<Ast.Expr>(),
End: () => Enumerable.Empty<Ast.Expr>(),
Decimal: () => Enumerable.Empty<Ast.Expr>(),
StringOpen: () => Enumerable.Empty<Ast.Expr>(),
Eq: () => Enumerable.Empty<Ast.Expr>(), // TODO
End: () => Enumerable.Empty<Ast.Expr>(), // TODO
Decimal: () => Enumerable.Empty<Ast.Expr>(), // TODO
StringOpen: () => Enumerable.Empty<Ast.Expr>(), // TODO
StringClose: () => Enumerable.Empty<Ast.Expr>()
)
)

View File

@ -14,10 +14,11 @@ public static class Generator {
public static void Generate(string outputFile, string singleHeader, string header, string footer, string qualifier, ImmutableDictionary<string, Tuple<Kind, ImmutableDictionary<string, string>>> types) {
using (var o = new System.IO.StreamWriter(outputFile)) {
Action<string> w = o.WriteLine;
w("// This file was generated by Generator.cs");
w("// This file was generated by T4/Generator.cs");
w("");
w("using System;");
w("using Immutable;");
w($"{singleHeader}");
foreach (var type in types) {
var name = type.Key;

View File

@ -55,6 +55,14 @@ public static class RecordGenerator {
w($" );");
}
private static void StringConversion(this Action<string> w, string qualifier, string name, Record record) {
w($" public override string ToString()");
w($" => this.CustomToString();");
w($" private string CustomToString(params Immutable.Uninstantiatable[] _)");
w($" => $\"{name}(\\n{String.Join(",\\n", record.Select(@field => $" {@field.Key}: {{{@field.Key}.Str<{@field.Value}>()}}"))})\";");
w($" public string Str() => ToString();");
}
private static void With(this Action<string> w, string qualifier, string name, Record record) {
foreach (var @field in record) {
var F = @field.Key;
@ -101,13 +109,15 @@ public static class RecordGenerator {
}
private static void RecordClass(this Action<string> w, string qualifier, string name, Record record) {
w($" public sealed class {name} : IEquatable<{name}> {{");
w($" public sealed partial class {name} : IEquatable<{name}>, IString {{");
w.Fields(qualifier, name, record);
w($"");
w.Constructor(qualifier, name, record);
w($"");
w.Equality(qualifier, name, record);
w($"");
w.StringConversion(qualifier, name, record);
w($"");
w.With(qualifier, name, record);
w($"");
w.Lens(qualifier, name, record);

View File

@ -1,4 +1,4 @@
// Code quality of this file: medium.
// Code quality of this file: low.
using System;
using System.Collections.Generic;
@ -48,7 +48,16 @@ public static class VariantGenerator {
foreach (var @case in variant) {
var C = @case.Key;
var Ty = @case.Value;
w($" public virtual Immutable.Option<{Ty == null ? "Immutable.Unit" : Ty}> As{C}() => Immutable.Option.None<{Ty == null ? "Immutable.Unit" : Ty}>();");
// This method is overloaded by the corresponding case's class.
w($" public virtual Immutable.Option<{Ty == null ? "Immutable.Unit" : Ty}> As{C} {{ get => Immutable.Option.None<{Ty == null ? "Immutable.Unit" : Ty}>(); }}");
}
}
private static void Is(this Action<string> w, string qualifier, string name, Variant variant) {
foreach (var @case in variant) {
var C = @case.Key;
var Ty = @case.Value;
w($" public virtual bool Is{C} {{ get => As{C}.IsSome; }}");
}
}
@ -69,6 +78,19 @@ public static class VariantGenerator {
w($" }}");
}
private static void GetValue(this Action<string> w, string qualifier, string name, Variant variant) {
w($" private Immutable.Option<string> GetValue() {{");
w($" return this.Match(");
w(String.Join(",\n", variant.Select(@case =>
$" {@case.Key}: {
@case.Value == null
? $"() => Immutable.Option.None<string>()"
: $"value => value.Str<{@case.Value}>().Some()"
}")));
w($" );");
w($" }}");
}
private static void Equality(this Action<string> w, string qualifier, string name, Variant variant) {
w($" public static bool operator ==({name} a, {name} b)");
w($" => Equality.Operator(a, b);");
@ -80,6 +102,14 @@ public static class VariantGenerator {
w($" public override abstract int GetHashCode();");
}
private static void StringConversion(this Action<string> w, string qualifier, string name, Variant variant) {
w($" public override string ToString()");
w($" => this.CustomToString();");
w($" private string CustomToString(params Immutable.Uninstantiatable[] _)");
w($" => GetTag() + GetValue().Match(some: x => $\"({{x}})\", none: () => \"\");");
w($" public string Str() => this.ToString();");
}
private static void CaseValue(this Action<string> w, string qualifier, string name, string C, string Ty) {
if (Ty != null) {
w($" public readonly {Ty} value;");
@ -96,7 +126,7 @@ public static class VariantGenerator {
}
private static void CaseAs(this Action<string> w, string qualifier, string name, string C, string Ty) {
w($" public override Immutable.Option<{Ty == null ? "Immutable.Unit" : Ty}> As{C}() => Immutable.Option.Some<{Ty == null ? "Immutable.Unit" : Ty}>({Ty == null ? "Immutable.Unit.unit" : "this.value"});");
w($" public override Immutable.Option<{Ty == null ? "Immutable.Unit" : Ty}> As{C} {{ get => Immutable.Option.Some<{Ty == null ? "Immutable.Unit" : Ty}>({Ty == null ? "Immutable.Unit.unit" : "this.value"}); }}");
}
private static void CaseEquality(this Action<string> w, string qualifier, string name, string C, string Ty) {
@ -120,10 +150,6 @@ public static class VariantGenerator {
}
}
private static void CaseToString(this Action<string> w, string qualifier, string name, string C, string Ty) {
w($" public override string ToString() => \"{C}\";");
}
private static void Cases(this Action<string> w, string qualifier, string name, Variant variant) {
foreach (var @case in variant) {
var C = @case.Key;
@ -138,8 +164,6 @@ public static class VariantGenerator {
w.CaseAs(qualifier, name, C, Ty);
w($"");
w.CaseEquality(qualifier, name, C, Ty);
w($"");
w.CaseToString(qualifier, name, C, Ty);
w($" }}");
}
}
@ -148,7 +172,7 @@ public static class VariantGenerator {
// Mark as partial to allow defining implicit conversions
// and other operators. It would be cleaner to directly
// specify these and keep the class impossible to extend.
w($" public abstract partial class {name} : IEquatable<{name}> {{");
w($" public abstract partial class {name} : IEquatable<{name}>, IString {{");
w.PrivateConstructor(qualifier, name, variant);
w($"");
w.Visitor(qualifier, name, variant);
@ -159,9 +183,18 @@ public static class VariantGenerator {
w($"");
w.As(qualifier, name, variant);
w($"");
w.Is(qualifier, name, variant);
w($"");
w.ChainLens(qualifier, name, variant);
w($"");
w.GetTag(qualifier, name, variant);
w($"");
w.GetValue(qualifier, name, variant);
w($"");
w.Equality(qualifier, name, variant);
w($"");
w.StringConversion(qualifier, name, variant);
w($"");
w($" public static class Cases {{");
w.Cases(qualifier, name, variant);
w($" }}");

View File

@ -120,6 +120,21 @@ public static class Collection {
}
}
public static Option<T> Last<T>(this IEnumerable<T> ie) {
var e = ie.GetEnumerator();
T element = default(T);
bool found = false;
while (e.MoveNext()) {
element = e.Current;
found = true;
}
if (found) {
return element.Some();
} else {
return Option.None<T>();
}
}
public static Option<T> First<T>(this IEnumerable<T> ie, Func<T, bool> predicate) {
var e = ie.GetEnumerator();
while (e.MoveNext()) {
@ -130,6 +145,17 @@ public static class Collection {
return Option.None<T>();
}
public static Option<U> First<T, U>(this IEnumerable<T> ie, Func<T, Option<U>> selector) {
var e = ie.GetEnumerator();
while (e.MoveNext()) {
var found = selector(e.Current);
if (found.IsSome) {
return found;
}
}
return Option.None<U>();
}
public static Option<T> Single<T>(this IEnumerable<T> ie) {
var e = ie.GetEnumerator();
if (e.MoveNext()) {
@ -162,7 +188,26 @@ public static class Collection {
}
}
public static Option<T> Aggregate<T, T>(this IEnumerable<T> ie, Func<T, T, T> f) {
var e = ie.GetEnumerator();
if (e.MoveNext()) {
var accumulator = e.Current;
while (e.MoveNext()) {
accumulator = f(accumulator, e.Current);
}
return accumulator.Some();
}
return Option.None<T>();
}
public static string JoinWith(this IEnumerable<string> strings, string joiner)
// TODO: use StringBuilder, there is no complexity info in the docs.
=> String.Join(joiner, strings);
public static string JoinToStringWith<T>(this IEnumerable<T> objects, string joiner)
// TODO: use StringBuilder, there is no complexity info in the docs.
=> String.Join(joiner, objects.Select(o => o.ToString()));
public static bool SetEquals<T>(this ImmutableHashSet<T> a, ImmutableHashSet<T> b)
=> a.All(x => b.Contains(x)) && b.All(x => a.Contains(x));
}

View File

@ -11,10 +11,13 @@ public static class Global {
public static void Log (string str) => Console.WriteLine(str);
public static Unit unit() => Unit.unit;
public static Unit unit { get => Unit.unit; }
public static Option<T> None<T>() => Option.None<T>();
public static ImmutableList<T> ImmutableList<T>(params T[] xs)
=> xs.ToImmutableList();
public static ImmutableHashSet<T> ImmutableHashSet<T>(params T[] xs)
=> xs.ToImmutableHashSet();
}

View File

@ -1,9 +1,10 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Collections.Immutable;
public class ImmutableDefaultDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>> {
public class ImmutableDefaultDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>, IString {
public readonly TValue defaultValue;
public readonly ImmutableDictionary<TKey, TValue> dictionary;
@ -33,6 +34,25 @@ public class ImmutableDefaultDictionary<TKey, TValue> : IEnumerable<KeyValuePair
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => dictionary.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => dictionary.GetEnumerator();
public ImmutableDefaultDictionaryLens<
TKey,
TValue,
ImmutableDefaultDictionary<TKey, TValue>>
lens {
get => this.ChainLens(x => x);
}
public override string ToString()
=> "ImmutableDefaultDictionary {\n"
+ this.Select(kvp => (ks: kvp.Key.ToString(),
vs: kvp.Value.ToString()))
.OrderBy(p => p.ks)
.Select(p => $"{{ {p.ks}, {p.vs} }}")
.JoinWith(",\n")
+ "\n}";
public string Str() => ToString();
}
public static class ImmutableDefaultDictionaryExtensionMethods {

View File

@ -1,3 +1,4 @@
/*
//namespace Immutable {
using System;
using System.Linq;
@ -8,7 +9,6 @@
using System.Collections.Immutable;
// TODO: use Microsoft.FSharp.Collections.FSharpMap
/*
public class ImmutableDictionary<TKey, TValue> : Mutable.IReadOnlyDictionary<TKey, TValue> {
private readonly Mutable.Dictionary<TKey, TValue> d;
private System.Collections.Immutable.ImmutableDictionary<TKey, TValue> i = System.Collections.Immutable.ImmutableDictionary<TKey, TValue>.Empty;

View File

@ -3,6 +3,8 @@ namespace Immutable {
public interface Option<out T> : System.Collections.Generic.IEnumerable<T> {
U Match_<U>(Func<T, U> some, Func<U> none);
bool IsSome { get; }
bool IsNone { get; }
}
public static class Option {
@ -17,6 +19,9 @@ namespace Immutable {
public U Match_<U>(Func<T, U> Some, Func<U> None) => Some(value);
public bool IsSome { get => true; }
public bool IsNone { get => false; }
public System.Collections.Generic.IEnumerator<T> GetEnumerator()
=> value.Singleton().GetEnumerator();
@ -29,6 +34,9 @@ namespace Immutable {
public U Match_<U>(Func<T, U> Some, Func<U> None) => None();
public bool IsSome { get => false; }
public bool IsNone { get => true; }
public System.Collections.Generic.IEnumerator<T> GetEnumerator()
=> System.Linq.Enumerable.Empty<T>().GetEnumerator();
@ -53,6 +61,9 @@ namespace Immutable {
public static Option<U> IfSome<T, U>(this Option<T> o, Func<T, U> some)
=> o.Map(some);
public static Option<U> Bind<T, U>(this Option<T> o, Func<T, Option<U>> f)
=> o.Match_(some => f(some), () => Option.None<U>());
public static T Else<T>(this Option<T> o, Func<T> none)
=> o.Match_(some => some, none);

View File

@ -0,0 +1,6 @@
namespace Immutable {
public sealed class Uninstantiatable {
// This class has no instances.
private Uninstantiatable() {}
}
}

View File

@ -65,18 +65,12 @@ public static class ImmutableDefaultDictionaryLensExtensionMethods {
System.Func<ImmutableDefaultDictionary<TKey, TValue>, Whole> wrap)
=> new ImmutableDefaultDictionaryLens<TKey, TValue, Whole>(wrap: wrap, oldHole: hole);
// TODO: this should be an extension property (once C# supports them)
public static ImmutableDefaultDictionaryLens<TKey, TValue, ImmutableDefaultDictionary<TKey, TValue>>
lens<TKey, TValue>(
this ImmutableDefaultDictionary<TKey, TValue> d)
=> d.ChainLens(x => x);
// this is a shorthand since we don't have extension properties
public static ImmutableDefaultDictionaryValueLens<TKey, TValue, ImmutableDefaultDictionary<TKey, TValue>>
lens<TKey, TValue>(
this ImmutableDefaultDictionary<TKey, TValue> d,
TKey key)
=> d.lens()[key];
=> d.lens[key];
public static ImmutableDefaultDictionaryValueLens<TKey, TValue, Whole>
UpdateKey<TKey, TValue, Whole>(

View File

31
Utils/ToString.cs Normal file
View File

@ -0,0 +1,31 @@
using System;
using System.Linq;
using System.Collections.Immutable;
public interface IString {
string Str();
}
public static class ToStringImplementations {
// These allow string conversion for uncooperative classes
// as long as their type arguments are cooperative.
// For some reason Str<object> takes precedence over this…
public static string Str<T>(this ImmutableList<T> l)
where T : IString
=> $"ImmutableList({l.Select(x => x.Str()).JoinWith(", ")})";
// …but not over this:
public static string Str<T>(this ImmutableList<MixFix.Operator> l)
=> $"ImmutableList({l.Select(x => x.Str()).JoinWith(", ")})";
public static string Str<T>(this ImmutableList<MixFix.Part> l)
=> $"ImmutableList({l.Select(x => x.Str()).JoinWith(", ")})";
public static string Str<T>(this ImmutableHashSet<String> h)
=> $"ImmutableHashSet({h.Select(x => x.Str<String>()).JoinWith(", ")})";
public static string Str<T>(this string s) => $"\"{s}\"";
public static string Str<T>(this object o) => ""+o.ToString();
}

43
main.cs
View File

@ -18,7 +18,7 @@ public static class MainClass {
.Pipe(c => dest.Write(c));
}
public static void RunTest(string toolchainName, Compiler compile, Exe runner, File source) {
public static bool RunTest(string toolchainName, Compiler compile, Exe runner, File source) {
var destPath = tests_results.Combine(source);
var sourcePath = tests.Combine(source);
var expected = sourcePath.DropExtension().Combine(Ext(".o"));
@ -27,15 +27,29 @@ public static class MainClass {
destPath.DirName().Create();
CompileToFile(compile, sourcePath, destPath);
var actualStr = runner.Run(destPath);
var expectedStr = expected.Read();
if (actualStr != expectedStr) {
UserErrorException exception = null;
try {
CompileToFile(compile, sourcePath, destPath);
} catch (UserErrorException e) {
exception = e;
}
if (exception != null) {
Console.WriteLine("");
Console.WriteLine("\x1b[1;31mFail\x1b[m");
throw new TestFailedException($"{source}: expected {expectedStr} but got {actualStr}.");
Console.WriteLine($"\x1b[1;33m{exception.Message}\x1b[m\n");
return false;
} else {
Console.Write("\x1b[1;32mOK\x1b[m\r");
var actualStr = runner.Run(destPath);
var expectedStr = expected.Read();
if (actualStr != expectedStr) {
Console.WriteLine("\x1b[1;31mFail\x1b[m");
Console.WriteLine($"\x1b[1;33m{source}: expected {expectedStr} but got {actualStr}.\x1b[m\n");
return false;
} else {
Console.Write("\x1b[1;32mOK\x1b[m\r");
return true;
}
}
}
@ -48,13 +62,22 @@ public static class MainClass {
.Cons("eval", Evaluator.Evaluate, Exe("cat"));
var total = 0;
var passed = 0;
var failed = 0;
foreach (var t in Dir("Tests/").GetFiles("*.e", SearchOption.AllDirectories)) {
foreach (var compiler in compilers) {
RunTest(compiler.Item1, compiler.Item2, compiler.Item3, t);
if (RunTest(compiler.Item1, compiler.Item2, compiler.Item3, t)) {
passed++;
} else {
failed++;
}
total++;
}
}
Console.WriteLine($"\x1b[K{total} tests run.");
Console.WriteLine($"\x1b[K{passed}/{total} tests passed, {failed} failed.");
if (failed != 0) {
Environment.Exit(1);
}
}
public static void Main (string[] args) {