IEnumerable <ParserOutput> Parse([NotNull] CharBuffer chars) { while (true) { var po = ParseOne(chars, out var src); switch (po.Type) { case ParserOutputType.SyntaxError: case ParserOutputType.EndOfInput: yield return(po); yield break; case ParserOutputType.Terminator: yield return(ParserOutput.FromException(new ExpectedButFound("object", $"'{chars.Current.Rebang()}'"))); yield break; default: if (po.Object != null) { po.Object.SourceLine = srcOverride ?? src; } yield return(po); break; } } }
ParserOutput ParseOneNonAdecl(CharBuffer chars, [NotNull] out ISourceLine sourceLine) { try { // handle whitespace if (!SkipWhitespace(chars)) { sourceLine = new FileSourceLine(site.CurrentFilePath, line); return(ParserOutput.EndOfInput); } sourceLine = new FileSourceLine(site.CurrentFilePath, line); var c = chars.Current; // '!' adds 128 to the next character (assuming it's below 128) if (c == '!') { if (!chars.MoveNext()) { throw new ExpectedButFound("character after '!'", "<EOF>"); } c = chars.Current; // two bangs in a row? preposterous. if (c == '!') { throw new ExpectedButFound("character after '!'", "another '!'"); } if (c < 128) { c += (char)128; } } switch (c) { case '(': return(ParserOutput.FromObject( ParseCurrentStructure( chars, ')', Bang.RightParen, zos => new ZilList(zos)))); case '<': return(ParserOutput.FromObject( ParseCurrentStructure( chars, '>', Bang.RightAngle, zos => new ZilForm(zos)))); case '[': return(ParserOutput.FromObject( ParseCurrentStructure( chars, ']', Bang.RightBracket, zos => new ZilVector(zos.ToArray())))); case Bang.LeftParen: // !(foo!) is identical to (foo) return(ParserOutput.FromObject( ParseCurrentStructure( chars, ')', Bang.RightParen, zos => new ZilList(zos)))); case Bang.LeftAngle: // !<foo!> is a segment return(ParserOutput.FromObject( ParseCurrentStructure( chars, '>', Bang.RightAngle, zos => new ZilSegment(new ZilForm(zos))))); case Bang.LeftBracket: // ![foo!] is a uvector, but we alias it to vector return(ParserOutput.FromObject( ParseCurrentStructure( chars, ']', Bang.RightBracket, zos => new ZilVector(zos.ToArray())))); case Bang.Dot: case Bang.Comma: case Bang.SingleQuote: // !.X is equivalent to !<LVAL X>, and so on chars.PushBack((char)(c - 128)); return(ParsePrefixed(chars, c, zo => ParserOutput.FromObject(new ZilSegment(zo)))); case '{': case Bang.LeftCurly: return(ParseCurrentStructure( chars, '}', Bang.RightCurly, zos => { var zarr = zos.ToArray(); if (zarr.Length != 1) { throw new ExpectedButFound("1 object inside '{}'", zarr.Length.ToString()); } foreach (var zo in ZilObject.ExpandTemplateToken(zarr[0], templateParams)) { heldObjects.Enqueue(zo); } return heldObjects.Count == 0 ? ParserOutput.EmptySplice : ParserOutput.FromObject(heldObjects.Dequeue()); })); case '.': return(ParsePrefixed(chars, c, zo => ParserOutput.FromObject( new ZilForm(new[] { site.ParseAtom("LVAL"), zo })))); case ',': return(ParsePrefixed(chars, c, zo => ParserOutput.FromObject( new ZilForm(new[] { site.ParseAtom("GVAL"), zo })))); case '\'': return(ParsePrefixed(chars, c, zo => ParserOutput.FromObject( new ZilForm(new[] { site.ParseAtom("QUOTE"), zo })))); case '%': case Bang.Percent: bool drop = false; if (chars.MoveNext()) { c = chars.Current; if (c == '%' || c == Bang.Percent) { drop = true; } else { chars.PushBack(c); } } var po = ParsePrefixed(chars, c, zo => ParserOutput.FromObject(site.Evaluate(zo))); if (po.Type != ParserOutputType.Object) { return(po); } if (drop) { return(ParserOutput.EmptySplice); } if (po.Object is ZilSplice splice) { foreach (var zo in splice) { heldObjects.Enqueue(zo); } return(heldObjects.Count == 0 ? ParserOutput.EmptySplice : ParserOutput.FromObject(heldObjects.Dequeue())); } return(po); case '#': case Bang.Hash: return(ParsePrefixed( chars, c, zo => { switch (zo) { case ZilFix fix when fix.Value == 2: if (!SkipWhitespace(chars)) { throw new ExpectedButFound("binary number after '#2'", "<EOF>"); } var sb = new StringBuilder(); bool run = true; do { var c2 = chars.Current; switch (c2) { case '0': case '1': sb.Append(c2); break; case var _ when c2.IsTerminator(): chars.PushBack(c2); run = false; break; default: throw new ExpectedButFound("binary number after '#2'", $"{sb}{c2}"); } } while (run && chars.MoveNext()); try { return ParserOutput.FromObject(new ZilFix(Convert.ToInt32(sb.ToString(), 2))); } catch (OverflowException ex) { throw new ParsedNumberOverflowed(sb.ToString(), "binary", ex); } case ZilAtom atom: return ParsePrefixed( chars, atom.Text, zo2 => ParserOutput.FromObject(site.ChangeType(zo2, atom))); } throw new ExpectedButFound($"atom or '2' after '{c.Rebang()}'", site.GetTypeAtom(zo).ToString()); })); case ';': case Bang.Semicolon: return(ParsePrefixed(chars, c, ParserOutput.FromComment)); case '"': return(ParserOutput.FromObject(ParseCurrentString(chars))); case var _ when c.IsTerminator(): chars.PushBack(c); return(ParserOutput.Terminator); case Bang.Backslash: case Bang.DoubleQuote: if (chars.MoveNext()) { return(ParserOutput.FromObject(new ZilChar(chars.Current))); } throw new ExpectedButFound("character after '!\\'", "<EOF>"); case var _ when c.IsNonAtomChar(): throw new ExpectedButFound("atom", $"'{c.Rebang()}'"); default: return(ParserOutput.FromObject(ParseCurrentAtomOrNumber(chars))); } } catch (ParserException ex) { sourceLine = new FileSourceLine(site.CurrentFilePath, line); return(ParserOutput.FromException(ex)); } }
ParserOutput ParseOne(CharBuffer chars, [NotNull] out ISourceLine sourceLine) { if (heldObjects.Count > 0) { sourceLine = SourceLines.Unknown; return(ParserOutput.FromObject(heldObjects.Dequeue())); } var po = ParseOneNonAdecl(chars, out sourceLine); if (po.Type != ParserOutputType.Object && po.Type != ParserOutputType.Comment) { System.Diagnostics.Debug.Assert(po.Object == null); return(po); } System.Diagnostics.Debug.Assert(po.Object != null); if (!SkipWhitespace(chars)) { return(po); } var c = chars.Current; if (c != ':' && c != Bang.Colon) { chars.PushBack(c); return(po); } ParserOutput po2; do { po2 = ParseOneNonAdecl(chars, out _); // TODO: store comment somewhere? (set SourceLine if so) } while (po2.IsIgnorable); switch (po2.Type) { case ParserOutputType.EndOfInput: throw new ExpectedButFound("object after ':'", "<EOF>"); case ParserOutputType.Object: var adecl = new ZilAdecl(po.Object, po2.Object); return(po.Type == ParserOutputType.Comment ? ParserOutput.FromComment(adecl) : ParserOutput.FromObject(adecl)); case ParserOutputType.SyntaxError: return(po2); case ParserOutputType.Terminator: chars.MoveNext(); throw new ExpectedButFound("object after ':'", $"'{chars.Current.Rebang()}'"); default: throw new UnhandledCaseException("object after ':'"); } }