diff --git a/AstGenerator.cs b/AstGenerator.cs index c65a314..861b4c9 100644 --- a/AstGenerator.cs +++ b/AstGenerator.cs @@ -12,11 +12,23 @@ public static class AstGenerator { Variant("Expr", Case("int", "Int"), Case("string", "String")), + Variant("ParserResult", - Case("(MixFix.Annotation, IEnumerable)", "Annotated"), + Case("(MixFix.Annotation, ParserResult)", "Annotated"), + Case("Lexer.Lexeme", "Terminal"), + Case("IEnumerable", "Productions")), + + Variant("ParserResult2", + Case("ValueTuple>", "SamePrecedence")), + Variant("OperatorOrHole", + Case("ValueTuple>", "Operator"), + Case("ParserResult2", "Hole")), + Variant("SamePrecedenceOrTerminal", + Case("ParserResult2", "SamePrecedence"), Case("Lexer.Lexeme", "Terminal")), + Variant("AstNode", Case("Lexer.Lexeme", "Terminal"), - Case("IEnumerable", "Operator")))); + Case("ValueTuple>", "Operator")))); } } \ No newline at end of file diff --git a/DefaultGrammar.cs b/DefaultGrammar.cs index ff13e35..9290693 100644 --- a/DefaultGrammar.cs +++ b/DefaultGrammar.cs @@ -14,7 +14,7 @@ public static class DefaultGrammar { .WithOperator("multiplicative", LeftAssociative, "int|terminal", S.Times, "int|terminal") .WithOperator("terminal", NonAssociative, S.Ident) // This is the root set of operators - .WithOperator("program", NonAssociative, + .WithOperator("program", LeftAssociative, // "bool" // TODO: this needs aliases "equality|terminal", S.And, "equality|terminal"); } \ No newline at end of file diff --git a/MixFix.cs b/MixFix.cs index cc80704..0b58357 100644 --- a/MixFix.cs +++ b/MixFix.cs @@ -185,7 +185,9 @@ public static partial class MixFix { : Paren(l.Count() != 1, l.Select(x => x.Str()).JoinWith(", ")), RepeatOnePlus: g => $"{g.Str()}+", Terminal: t => t.Str(), - Annotated: a => $"Annotated({a.Item1.Str()}, {a.Item2.Str()})" + Annotated: a => + //$"Annotated({a.Item1.Str()}, {a.Item2.Str()})" + $"~{a.Item2.Str()}" ); } diff --git a/Parser.cs b/Parser.cs index b465e3a..cea8519 100644 --- a/Parser.cs +++ b/Parser.cs @@ -21,10 +21,11 @@ public static partial class Parser { IImmutableEnumerator tokens, Grammar2 grammar ) + //=> Log($"Parser {grammar.ToString()} against {tokens.FirstAndRest().IfSome((first, rest) => first)}", () => grammar.Match( RepeatOnePlus: g => tokens.FoldMapWhileSome(restI => Parse3(restI, g)) - .If((restN, nodes) => nodes.Count() > 1) + .If((restN, nodes) => nodes.Count() >= 1) .IfSome((restN, nodes) => (restN, ParserResult.Productions(nodes))), // TODO: to check for ambiguous parses, we can use // .Single(…) instead of .First(…). @@ -59,10 +60,25 @@ public static partial class Parser { // Variant("ParserResult", - // Case("(Annotation, IEnumerable)", "Annotated"), - // Case("Lexer.Lexeme", "Terminal")) + // Case("(MixFix.Annotation, ParserResult)", "Annotated"), + // Case("Lexer.Lexeme", "Terminal"), + // Case("IEnumerable", "Productions")), - // ParserResult = A(SamePrecedence, *) | A(Operator, *) | A(Hole, *) + // ParserResult = A(SamePrecedence, *) | A(Operator, repeat|Terminal) | A(Hole, SamePrecedence) + + // Variant("ParserResult", + // Case("(MixFix.Annotation, ParserResult)", "Annotated"), + // Case("Lexer.Lexeme", "Terminal"), + // Case("IEnumerable", "Productions")), + + // Variant("ParserResult2", + // Case("IEnumerable", "SamePrecedence")), + // Variant("OperatorOrHole", + // Case("IEnumerable", "Operator") + // Case("Ast.SamePrecedence", "Hole")), + // Variant("SamePrecedenceOrTerminal", + // Case("Ast.SamePrecedence", "SamePrecedence"), + // Case("Lexer.Lexeme", "Terminal")), // Annotated(Hole, lsucc); // Annotated(Operator, closed, nonAssoc, prefix, postfix, infixl, infixr) @@ -75,11 +91,251 @@ public static partial class Parser { // | ((prefix || infixr) ? R( ((prefix | (lsucc, infixr))["+"], rsucc) ) : Impossible) // | ((postfix || infixl) ? L( (lsucc, (postfix || (infixl, rsucc))["+"]) ) : Impossible); - public static AstNode PostProcess(this ParserResult parserResult) { - parserResult.Match( - Annotated: - ) - throw new ParserErrorException("TODO:" + parserResult.ToString()); + // We lost some typing information and the structure is scattered around + // in Annotation nodes. For now gather everything back into the right + // structure after the fact. + public static ParserResult2 Gather(this ParserResult parserResult) + => parserResult + .AsAnnotated + .ElseThrow(new ParserErrorException("Internal error: Expected Annotated")) + .Pipe(a => a.Item1.AsSamePrecedence + .ElseThrow(new ParserErrorException("Internal error: Expected SamePrecedence")) + .Pipe(associativity => + ParserResult2.SamePrecedence( + (associativity, a.Item2.GatherOperatorOrHole())))); + + public static IEnumerable GatherOperatorOrHole(this ParserResult parserResult) + => parserResult.Match( + Annotated: a => a.Item1.Match( + Operator: @operator => + OperatorOrHole.Operator( + (@operator, a.Item2.GatherSamePrecedenceOrTerminal())) + .Singleton(), + Hole: () => + OperatorOrHole.Hole( + a.Item2.Gather()) + .Singleton(), + SamePrecedence: associativity => + throw new ParserErrorException("Internal error: Expected Operator or Hole") + ), + Productions: p => + p.SelectMany(GatherOperatorOrHole), + Terminal: t => + throw new ParserErrorException("Internal error: Expected Annotated or Productions")); + + public static IEnumerable GatherSamePrecedenceOrTerminal(this ParserResult parserResult) + => parserResult.Match( + Annotated: a => a.Item1.Match( + SamePrecedence: associativity => + SamePrecedenceOrTerminal.SamePrecedence( + parserResult.Gather()) + .Singleton(), + Hole: () => + throw new ParserErrorException("Internal error: Expected SamePrecedence or Terminal"), + Operator: associativity => + throw new ParserErrorException("Internal error: Expected SamePrecedence or Terminal") + ), + Productions: p => + p.SelectMany(GatherSamePrecedenceOrTerminal), + Terminal: lexeme => + SamePrecedenceOrTerminal.Terminal(lexeme) + .Singleton()); + + // ParserResult2 = + // | (MixFix.Associativity, IEnumerable) SamePrecedence + // OperatorOrHole = + // | (MixFix.Operator, IEnumerable) Operator + // | ParserResult2 Hole + // SamePrecedenceOrTerminal = + // | ParserResult2 SamePrecedence + // | Lexer.Lexeme Terminal + + public static ValueTuple> Get(this ParserResult2 parserResult) + => parserResult.Match(SamePrecedence: p =>p); + + /* + public static IEnumerable> Split(this IEnumerable e, Func predicate) { + var currentList = ImmutableStack.Empty; + T prev = default(T); + var first = true; + foreach (var x in e) { + if (first) { + first = false; + } else { + if (predicate(prev, x)) { + yield return currentList.Reverse(); + currentList = ImmutableStack.Empty; + } + } + currentList = currentList.Push(x); + prev = x; + } + yield return currentList.Reverse(); + }*/ + + public static IEnumerable> Split(this IEnumerable e, Func predicate) { + var currentList = ImmutableStack.Empty; + foreach (var x in e) { + if (predicate(x)) { + // yield the elements + yield return currentList.Reverse(); + currentList = ImmutableStack.Empty; + // yield the separator + yield return x.Singleton(); + } else { + currentList = currentList.Push(x); + } + } + // yield the last (possibly empty) unclosed batch of elements + yield return currentList.Reverse(); + } + + /* + // TODO: use an Either class instead of an Option with another Func. + public static IEnumerable> Split(this IEnumerable e, Func> predicate, Func, U> transform) { + var currentList = ImmutableStack.Empty; + foreach (var x in e) { + var p = predicate(x); + if (p.IsSome) { + // yield the elements + yield return transform(currentList.Reverse()); + currentList = ImmutableStack.Empty; + // yield the separator + yield return p.AsSome.ElseThrow("impossible"); + } else { + currentList = currentList.Push(x); + } + } + // yield the last (possibly empty) unclosed batch of elements + yield return transform(currentList.Reverse()); + }*/ + + public static A FoldLeft3(this IEnumerable ie, Func init, Func f) { + var e = ie.GetEnumerator(); + e.MoveNext(); + T a = e.Current; + e.MoveNext(); + T b = e.Current; + e.MoveNext(); + T c = e.Current; + A acc = init(a, b, c); + while (e.MoveNext()) { + T x = e.Current; + e.MoveNext(); + T y = e.Current; + acc = f(acc, x, y); + } + return acc; + } + + public static A FoldRight3(this IEnumerable ie, Func init, Func f) { + var e = ie.Reverse().GetEnumerator(); + e.MoveNext(); + T a = e.Current; + e.MoveNext(); + T b = e.Current; + e.MoveNext(); + T c = e.Current; + A acc = init(c, b, a); + while (e.MoveNext()) { + T x = e.Current; + e.MoveNext(); + T y = e.Current; + acc = f(y, x, acc); + } + return acc; + } + + public static AstNode PostProcess(this SamePrecedenceOrTerminal samePrecedenceOrTerminal) + => samePrecedenceOrTerminal.Match( + SamePrecedence: x => PostProcess(x), + // TODO: just writing Terminal: AstNode.Terminal causes a null exception + Terminal: x => AstNode.Terminal(x)); + + public static IEnumerable PostProcess(this OperatorOrHole operatorOrHole) + => operatorOrHole.Match( + Operator: o => o.Item2.Select(PostProcess), + Hole: h => h.PostProcess().Singleton()); + + + public static AstNode PostProcess(this ParserResult2 parserResult) { + // Let's start with right associativity + // TODO: handle other associativities + + // We flatten by converting to a sequence of SamePrecedenceOrTerminal + + // turn this: h h o h o o o h h o o h + // into this: (h h) (o) (h) (o) () (o) () (o) (h h) (o) () (o) (h) + // and this: o h o o o h h o o + // into this: () (o) (h) (o) () (o) () (o) (h h) (o) () (o) () + // i.e. always have a (possibly empty) list on both ends. + + var split = parserResult.Get().Item2.Split(x => x.IsOperator); + + return parserResult.Get().Item1.Match( + NonAssociative: () => { + if (split.Count() != 3) { + throw new ParserErrorException($"Internal error: NonAssociative operator within group of {split.Count()} elements, expected exactly 3"); + } else { + var @operator = + split.ElementAt(1) + .Single().ElseThrow(new Exception("impossible")) + .AsOperator.ElseThrow(new Exception("impossible")); + return AstNode.Operator( + (@operator.Item1, + split.ElementAt(0).SelectMany(PostProcess) + .Concat(split.ElementAt(1).SelectMany(PostProcess)) + .Concat(split.ElementAt(2).SelectMany(PostProcess)))); + } + }, + RightAssociative: () => + split.FoldRight3( + // Last group of three + (hsl, o, hsr) => { + var @operator = o + .Single().ElseThrow(new Exception("impossible")) + .AsOperator.ElseThrow(new Exception("impossible")); + return AstNode.Operator( + (@operator.Item1, + hsl.SelectMany(PostProcess) + .Concat(o.SelectMany(PostProcess)) + .Concat(hsr.SelectMany(PostProcess)))); + }, + // Subsequent groups of two starting with accumulator + (hsl, o, a) => { + var @operator = o + .Single().ElseThrow(new Exception("impossible")) + .AsOperator.ElseThrow(new Exception("impossible")); + return AstNode.Operator( + (@operator.Item1, + hsl.SelectMany(PostProcess) + .Concat(o.SelectMany(PostProcess)) + .Concat(a))); + }), + LeftAssociative: () => + split.FoldLeft3( + // Fist group of three + (hsl, o, hsr) => { + var @operator = o + .Single().ElseThrow(new Exception("impossible")) + .AsOperator.ElseThrow(new Exception("impossible")); + return AstNode.Operator( + (@operator.Item1, + hsl.SelectMany(PostProcess) + .Concat(o.SelectMany(PostProcess)) + .Concat(hsr.SelectMany(PostProcess)))); + }, + // Subsequent groups of two starting with accumulator + (a, o, hsr) => { + var @operator = o + .Single().ElseThrow(new Exception("impossible")) + .AsOperator.ElseThrow(new Exception("impossible")); + return AstNode.Operator( + (@operator.Item1, + a.Singleton() + .Concat(o.SelectMany(PostProcess)) + .Concat(hsr.SelectMany(PostProcess)))); + })); } /* @@ -186,16 +442,16 @@ public static partial class Parser { Parse3 ); + Log(grammar.ToString()); + return P(Lexer.Lex(source), grammar) - .IfSome((rest, result) => (rest, PostProcess(result))); + .IfSome((rest, result) => (rest, result.Gather().PostProcess())); } public static Ast.Expr Parse(string source) { - Parse2(source).ToString(); - //Log(""); - //Log("" + Parse2(source).ToString()); - //Log(""); - Environment.Exit(0); + Log(""); + Log("Parsed:" + Parse2(source).ToString()); + Log(""); return Lexer.Lex(source) .SelectMany(lexeme => diff --git a/T4/RecordGenerator.cs b/T4/RecordGenerator.cs index 3ec1a86..e2d82ad 100644 --- a/T4/RecordGenerator.cs +++ b/T4/RecordGenerator.cs @@ -32,6 +32,9 @@ public static class RecordGenerator { var F = @field.Key; var Ty = @field.Value; w($" this.{F} = {F};"); + w($" if (object.ReferenceEquals({F}, null)) {{"); + w($" throw new Exception(\"Argument {Ty} {F} to {name} was null.\");"); + w($" }}"); } w($" this.hashCode = Equality.HashCode(\"{name}\","); w(String.Join(",\n", record.Select(@field => diff --git a/T4/VariantGenerator.cs b/T4/VariantGenerator.cs index 6a5639a..bc1f254 100644 --- a/T4/VariantGenerator.cs +++ b/T4/VariantGenerator.cs @@ -122,6 +122,11 @@ public static class VariantGenerator { private static void CaseConstructor(this Action w, string qualifier, string name, string C, string Ty) { w($" public {C}({Ty == null ? "" : $"{Ty} value"}) {{"); w($" {Ty == null ? "" : $"this.value = value; "}"); + if (Ty != null) { + w($" if (object.ReferenceEquals(value, null)) {{"); + w($" throw new Exception(\"Argument of type {Ty} to {name}.{C} was null.\");"); + w($" }}"); + } if (Ty == null) { w($" this.hashCode = HashCode.Combine(\"{C}\");"); } else { diff --git a/Tests/001-42.e b/Tests/001-42.e index 5aa6034..e69de29 100644 --- a/Tests/001-42.e +++ b/Tests/001-42.e @@ -1 +0,0 @@ -true && false \ No newline at end of file diff --git a/Tests/006-eq.e b/Tests/006-eq.e new file mode 100644 index 0000000..b9fa03e --- /dev/null +++ b/Tests/006-eq.e @@ -0,0 +1 @@ +40 + 2 == 40 + 1 + 1 && true \ No newline at end of file diff --git a/Tests/006-eq.o b/Tests/006-eq.o new file mode 100644 index 0000000..f32a580 --- /dev/null +++ b/Tests/006-eq.o @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/Utils/ToString.cs b/Utils/ToString.cs index 739fa7f..bef832b 100644 --- a/Utils/ToString.cs +++ b/Utils/ToString.cs @@ -35,6 +35,21 @@ public static class ToStringImplementations { public static string Str(this IEnumerable e) => $"IEnumerab({e.Select(x => x.Str()).JoinWith(", ")})"; + public static string Str(this IEnumerable e) + => $"IEnumerab({e.Select(x => x.Str()).JoinWith(", ")})"; + + public static string Str(this ValueTuple> t) + => $"({t.Item1.Str()}, {t.Item2.Str>()})"; + + public static string Str(this IEnumerable e) + => $"IEnumerab({e.Select(x => x.Str()).JoinWith(", ")})"; + + public static string Str(this ValueTuple> t) + => $"({t.Item1.Str()}, {t.Item2.Str>()})"; + + public static string Str(this ValueTuple> t) + => $"({t.Item1.Str()}, {t.Item2.Str>()})"; + public static string Str(this ImmutableDictionary h) => $"ImmutableDictionary(\n{h.Select(x => $" {x.Key.Str()}:{x.Value.Str()}").JoinWith(",\n")}\n)";