Adding operators should work now
This commit is contained in:
parent
62f7209b14
commit
1cf8cb6bd8
14
DefaultGrammar.cs
Normal file
14
DefaultGrammar.cs
Normal 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);
|
||||
}
|
|
@ -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) {}
|
||||
}
|
||||
|
|
19
Lexer.cs
19
Lexer.cs
|
@ -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),
|
||||
|
|
|
@ -14,6 +14,8 @@ public static class LexerGenerator {
|
|||
Case("Space"),
|
||||
Case("Int"),
|
||||
Case("Decimal"),
|
||||
Case("Ident"),
|
||||
Case("And"),
|
||||
Case("Eq"),
|
||||
Case("Space"),
|
||||
Case("String"),
|
||||
|
|
1
Makefile
1
Makefile
|
@ -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
240
MixFix.cs
Normal 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
48
MixFixGenerator.cs
Normal 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"))));
|
||||
}
|
||||
}
|
11
Parser.cs
11
Parser.cs
|
@ -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>()
|
||||
)
|
||||
)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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($" }}");
|
||||
|
|
|
@ -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));
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
6
Utils/Immutable/Uninstantiatable.cs
Normal file
6
Utils/Immutable/Uninstantiatable.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace Immutable {
|
||||
public sealed class Uninstantiatable {
|
||||
// This class has no instances.
|
||||
private Uninstantiatable() {}
|
||||
}
|
||||
}
|
|
@ -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>(
|
||||
|
|
0
Utils/OverridableToString.cs
Normal file
0
Utils/OverridableToString.cs
Normal file
31
Utils/ToString.cs
Normal file
31
Utils/ToString.cs
Normal 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
43
main.cs
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user