From e0055b3127ed900af8f5ab0d175b113fcce48bea Mon Sep 17 00:00:00 2001 From: Jesse McGrew Date: Sun, 17 Jan 2021 00:28:43 -0800 Subject: [PATCH 1/6] Added limited support for reader macro extensions. will register a prefix macro, such that $FOO will be replaced with the value returned by at read time. > will remove the previously registered macro. Partly addresses ZILF-181. --- src/Dezapf/Dezapf.csproj | 2 +- src/Zapf.Parsing/Zapf.Parsing.csproj | 2 +- src/Zapf/Zapf.csproj | 2 +- src/Zilf.Common/Zilf.Common.csproj | 2 +- src/Zilf.Emit/Zilf.Emit.csproj | 2 +- src/Zilf/.editorconfig | 2 +- src/Zilf/Interpreter/Context.cs | 8 +- src/Zilf/Interpreter/ParserMacros.cs | 55 ++++ src/Zilf/Interpreter/Subrs.Output.cs | 13 + src/Zilf/Language/{ => Parsing}/Bang.cs | 2 +- src/Zilf/Language/Parsing/CharBuffer.cs | 40 +++ src/Zilf/Language/Parsing/IParserSite.cs | 25 ++ src/Zilf/Language/{ => Parsing}/Parser.cs | 265 ++----------------- src/Zilf/Language/Parsing/ParserException.cs | 74 ++++++ src/Zilf/Language/Parsing/ParserOutput.cs | 106 ++++++++ src/Zilf/Program.cs | 1 + src/Zilf/Zilf.csproj | 2 +- src/Zilf/Zilf.ruleset | 2 + test/Dezapf.Tests/Dezapf.Tests.csproj | 2 +- test/Zilf.Tests.Integration/MacroTests.cs | 8 + test/Zilf.Tests/Interpreter/ParserTests.cs | 26 +- zillib/verbs.zil | 6 +- 22 files changed, 387 insertions(+), 260 deletions(-) create mode 100644 src/Zilf/Interpreter/ParserMacros.cs rename src/Zilf/Language/{ => Parsing}/Bang.cs (96%) create mode 100644 src/Zilf/Language/Parsing/CharBuffer.cs create mode 100644 src/Zilf/Language/Parsing/IParserSite.cs rename src/Zilf/Language/{ => Parsing}/Parser.cs (76%) create mode 100644 src/Zilf/Language/Parsing/ParserException.cs create mode 100644 src/Zilf/Language/Parsing/ParserOutput.cs diff --git a/src/Dezapf/Dezapf.csproj b/src/Dezapf/Dezapf.csproj index 61f32c2..632ad03 100644 --- a/src/Dezapf/Dezapf.csproj +++ b/src/Dezapf/Dezapf.csproj @@ -10,7 +10,7 @@ Copyright 2010-2018 Jesse McGrew - + diff --git a/src/Zapf.Parsing/Zapf.Parsing.csproj b/src/Zapf.Parsing/Zapf.Parsing.csproj index 0ef643c..7bf048f 100644 --- a/src/Zapf.Parsing/Zapf.Parsing.csproj +++ b/src/Zapf.Parsing/Zapf.Parsing.csproj @@ -6,7 +6,7 @@ enable - + Zilf.licenseheader diff --git a/src/Zapf/Zapf.csproj b/src/Zapf/Zapf.csproj index 0e85484..3d6b575 100644 --- a/src/Zapf/Zapf.csproj +++ b/src/Zapf/Zapf.csproj @@ -12,7 +12,7 @@ true - + Zilf.licenseheader diff --git a/src/Zilf.Common/Zilf.Common.csproj b/src/Zilf.Common/Zilf.Common.csproj index f02fb69..58c2722 100644 --- a/src/Zilf.Common/Zilf.Common.csproj +++ b/src/Zilf.Common/Zilf.Common.csproj @@ -10,7 +10,7 @@ true - + Zilf.licenseheader diff --git a/src/Zilf.Emit/Zilf.Emit.csproj b/src/Zilf.Emit/Zilf.Emit.csproj index e04a7e3..e7ee8ef 100644 --- a/src/Zilf.Emit/Zilf.Emit.csproj +++ b/src/Zilf.Emit/Zilf.Emit.csproj @@ -5,7 +5,7 @@ enable - + Zilf.licenseheader diff --git a/src/Zilf/.editorconfig b/src/Zilf/.editorconfig index 72993b1..69e977b 100644 --- a/src/Zilf/.editorconfig +++ b/src/Zilf/.editorconfig @@ -1,7 +1,7 @@ [*.cs] # CA1303: Do not pass literals as localized parameters -dotnet_diagnostic.CA1303.severity = none +dotnet_diagnostic.CA1303.severity = suggestion # RCS1139: Add summary element to documentation comment. dotnet_diagnostic.RCS1139.severity = none diff --git a/src/Zilf/Interpreter/Context.cs b/src/Zilf/Interpreter/Context.cs index 4682963..5086aa1 100644 --- a/src/Zilf/Interpreter/Context.cs +++ b/src/Zilf/Interpreter/Context.cs @@ -32,6 +32,7 @@ using Zilf.Language; using Zilf.ZModel; using Zilf.ZModel.Values; using Zilf.ZModel.Vocab; +using Zilf.Language.Parsing; namespace Zilf.Interpreter { @@ -101,6 +102,8 @@ namespace Zilf.Interpreter public DiagnosticManager DiagnosticManager { get; } readonly ObList compilationFlagsObList, hooksObList; + public ParserMacros ParserMacros { get; } + readonly Stack previousObPaths; LocalEnvironment localEnvironment; readonly Dictionary globalValues; @@ -135,6 +138,8 @@ namespace Zilf.Interpreter { this.IgnoreCase = ignoreCase; + this.ParserMacros = new ParserMacros(this); + this.DiagnosticManager = new DiagnosticManager(); this.DiagnosticManager.TooManyErrors += (sender, args) => { @@ -519,7 +524,6 @@ namespace Zilf.Interpreter /// The MDL documentation refers to the macro expansion environment as a /// "top level environment", but for the purposes of MDL-ZIL?, the environment is /// not considered "top level" (i.e. SUBR names are not redirected). - [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] public T ExecuteInMacroEnvironment(Func func) { var oblistAtom = GetStdAtom(StdAtom.OBLIST); @@ -1463,6 +1467,8 @@ B * ZilObject IParserSite.Evaluate(ZilObject zo) => (ZilObject)zo.Eval(this); + SimplePrefixMacroHandler? IParserSite.GetPrefixMacro(char prefix) => ParserMacros.GetPrefixMacro(prefix); + #endregion } } diff --git a/src/Zilf/Interpreter/ParserMacros.cs b/src/Zilf/Interpreter/ParserMacros.cs new file mode 100644 index 0000000..be998e6 --- /dev/null +++ b/src/Zilf/Interpreter/ParserMacros.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Text; +using Zilf.Interpreter.Values; +using Zilf.Language.Parsing; + +namespace Zilf.Interpreter +{ + delegate ParserOutput SimplePrefixMacroHandlerWithContext(Context ctx, ZilObject zo); + + class ParserMacros + { + readonly Dictionary prefixMacros = + new Dictionary(); + + readonly Context ctx; + + public ParserMacros(Context ctx) + { + this.ctx = ctx; + } + + public void MakePrefixMacro(char prefix, SimplePrefixMacroHandlerWithContext? convert) + { + switch (prefix) + { + case '.': + case Bang.Dot: + case '!': + case '\\': + case Bang.Backslash: + case var _ when prefix.IsNonAtomChar(): + throw new ArgumentException($"Reserved character: '{prefix}'", nameof(prefix)); + } + + if (convert != null) + { + var closureCtx = ctx; + prefixMacros[prefix] = zo => convert(closureCtx, zo); + } + else + { + prefixMacros.Remove(prefix); + } + } + + [Pure] + public SimplePrefixMacroHandler? GetPrefixMacro(char prefix) + { + prefixMacros.TryGetValue(prefix, out var result); + return result; + } + } +} diff --git a/src/Zilf/Interpreter/Subrs.Output.cs b/src/Zilf/Interpreter/Subrs.Output.cs index 19704e5..e07564a 100644 --- a/src/Zilf/Interpreter/Subrs.Output.cs +++ b/src/Zilf/Interpreter/Subrs.Output.cs @@ -23,6 +23,7 @@ using System.Text.RegularExpressions; using Zilf.Interpreter.Values; using Zilf.Language; using Zilf.Diagnostics; +using Zilf.Language.Parsing; namespace Zilf.Interpreter { @@ -301,5 +302,17 @@ namespace Zilf.Interpreter return position; } + + [Subr("MAKE-PREFIX-MACRO", ObList = "READER-MACROS")] + public static ZilObject MAKE_PREFIX_MACRO(Context ctx, ZilChar ch, + [Either(typeof(IApplicable), typeof(ZilFalse))] + object handlerOrFalse) + { + ctx.ParserMacros.MakePrefixMacro(ch.Char, + handlerOrFalse is IApplicable a + ? (c, zo) => ParserOutput.FromObject((ZilObject)a.ApplyNoEval(c, new[] { zo })) + : (SimplePrefixMacroHandlerWithContext?)null); + return ctx.TRUE; + } } } diff --git a/src/Zilf/Language/Bang.cs b/src/Zilf/Language/Parsing/Bang.cs similarity index 96% rename from src/Zilf/Language/Bang.cs rename to src/Zilf/Language/Parsing/Bang.cs index 5ec8a77..ae7f5f3 100644 --- a/src/Zilf/Language/Bang.cs +++ b/src/Zilf/Language/Parsing/Bang.cs @@ -16,7 +16,7 @@ * along with ZILF. If not, see . */ -namespace Zilf.Language +namespace Zilf.Language.Parsing { /// /// Character constants for ASCII symbols with the 8th bit set, diff --git a/src/Zilf/Language/Parsing/CharBuffer.cs b/src/Zilf/Language/Parsing/CharBuffer.cs new file mode 100644 index 0000000..759dc68 --- /dev/null +++ b/src/Zilf/Language/Parsing/CharBuffer.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; + +namespace Zilf.Language.Parsing +{ + sealed class CharBuffer + { + readonly IEnumerator source; + readonly Stack heldChars = new Stack(2); + char? curChar; + + public CharBuffer(IEnumerable source) + { + this.source = source.GetEnumerator(); + } + + public bool MoveNext() + { + if (heldChars.Count > 0) + { + curChar = heldChars.Pop(); + return true; + } + + if (source.MoveNext()) + { + curChar = source.Current; + return true; + } + + curChar = null; + return false; + } + + /// No character to read + public char Current => curChar ?? throw new InvalidOperationException("No character to read"); + + public void PushBack(char ch) => heldChars.Push(ch); + } +} \ No newline at end of file diff --git a/src/Zilf/Language/Parsing/IParserSite.cs b/src/Zilf/Language/Parsing/IParserSite.cs new file mode 100644 index 0000000..1793a64 --- /dev/null +++ b/src/Zilf/Language/Parsing/IParserSite.cs @@ -0,0 +1,25 @@ +using System; +using Zilf.Interpreter; +using Zilf.Interpreter.Values; + +namespace Zilf.Language.Parsing +{ + delegate ParserOutput SimplePrefixMacroHandler(ZilObject zo); + + interface IParserSite + { + ZilAtom ParseAtom(string text); + + ZilAtom GetTypeAtom(ZilObject zo); + + ZilObject ChangeType(ZilObject zo, ZilAtom type); + + ZilObject Evaluate(ZilObject zo); + + ZilObject? GetGlobalVal(ZilAtom atom); + + string CurrentFilePath { get; } + + SimplePrefixMacroHandler? GetPrefixMacro(char prefix); + } +} \ No newline at end of file diff --git a/src/Zilf/Language/Parser.cs b/src/Zilf/Language/Parsing/Parser.cs similarity index 76% rename from src/Zilf/Language/Parser.cs rename to src/Zilf/Language/Parsing/Parser.cs index 0806cf1..99d0715 100644 --- a/src/Zilf/Language/Parser.cs +++ b/src/Zilf/Language/Parsing/Parser.cs @@ -18,241 +18,15 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; -using System.Runtime.Serialization; using System.Text; using Zilf.Common; +using Zilf.Interpreter; using Zilf.Interpreter.Values; -using System.Globalization; -namespace Zilf.Language +namespace Zilf.Language.Parsing { - sealed class CharBuffer - { - readonly IEnumerator source; - readonly Stack heldChars = new Stack(2); - char? curChar; - - public CharBuffer(IEnumerable source) - { - this.source = source.GetEnumerator(); - } - - public bool MoveNext() - { - if (heldChars.Count > 0) - { - curChar = heldChars.Pop(); - return true; - } - - if (source.MoveNext()) - { - curChar = source.Current; - return true; - } - - curChar = null; - return false; - } - - /// No character to read - public char Current => curChar ?? throw new InvalidOperationException("No character to read"); - - public void PushBack(char ch) => heldChars.Push(ch); - } - - [Serializable] - public abstract class ParserException : Exception - { - protected ParserException(string message, Exception innerException) - : base(message, innerException) { } - - protected ParserException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - - public ParserException() : base() - { - } - - public ParserException(string message) : base(message) - { - } - } - - [Serializable] - sealed class ExpectedButFound : ParserException - { - public ExpectedButFound(string expected, string actual, Exception innerException) - : base($"expected {expected} but found {actual}", innerException) { } - - public ExpectedButFound(string expected, string actual) - : base($"expected {expected} but found {actual}") { } - - ExpectedButFound(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - - public ExpectedButFound() - { - } - - public ExpectedButFound(string message) : base(message) - { - } - - public ExpectedButFound(string message, Exception innerException) : base(message, innerException) - { - } - } - - [Serializable] - sealed class ParsedNumberOverflowed : ParserException - { - const string DefaultRadix = "decimal"; - - public ParsedNumberOverflowed(string number, string radix, Exception innerException) - : base($"{radix} number '{number}' cannot be represented in 32 bits", innerException) { } - - public ParsedNumberOverflowed(string number, string radix = DefaultRadix) - : base($"{radix} number '{number}' cannot be represented in 32 bits") { } - - public ParsedNumberOverflowed(string number, Exception innerException) - : this(number, DefaultRadix, innerException) { } - - ParsedNumberOverflowed(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - - public ParsedNumberOverflowed() : base() - { - } - - public ParsedNumberOverflowed(string message) : base(message) - { - } - } - - interface IParserSite - { - ZilAtom ParseAtom(string text); - - ZilAtom GetTypeAtom(ZilObject zo); - - ZilObject ChangeType(ZilObject zo, ZilAtom type); - - ZilObject Evaluate(ZilObject zo); - - ZilObject? GetGlobalVal(ZilAtom atom); - - string CurrentFilePath { get; } - } - - enum ParserOutputType - { - /// - /// A valid was parsed. - /// - Object, - /// - /// A valid was parsed, with a comment prefix. - /// - Comment, - /// - /// A valid object could not be parsed. - /// - SyntaxError, - /// - /// There are no more characters to read. - /// - EndOfInput, - /// - /// A character was read (and pushed back) that may have terminated an outer structure. - /// - Terminator, - /// - /// A special object was parsed and evaluated, and there were no objects to insert in its place. - /// - /// - /// This happens whenever a %%macro is evaluated, or when a %macro returns #SPLICE (), or when - /// the left side of a {...:SPLICE} template invocation evaluates to an empty structure. - /// - EmptySplice, - } - - struct ParserOutput - { - public ParserOutputType Type; - public ZilObject Object; - public ParserException Exception; - - public bool IsIgnorable => Type == ParserOutputType.Comment || Type == ParserOutputType.EmptySplice; - - public override string ToString() - { - var sb = new StringBuilder(); - - sb.Append(Type); - - if (Object != null) - { - sb.Append(' '); - sb.Append(Object); - } - - if (Exception != null) - { - sb.Append(' '); - sb.Append(Exception.GetType().Name); - sb.Append("(\""); - sb.Append(Exception.Message); - sb.Append("\")"); - } - - return sb.ToString(); - } - - public static readonly ParserOutput EmptySplice = - new ParserOutput { Type = ParserOutputType.EmptySplice }; - - public static readonly ParserOutput EndOfInput = - new ParserOutput { Type = ParserOutputType.EndOfInput }; - - public static readonly ParserOutput Terminator = - new ParserOutput { Type = ParserOutputType.Terminator }; - - public static ParserOutput FromObject(ZilObject zo) - { - return new ParserOutput - { - Type = ParserOutputType.Object, - Object = zo - }; - } - - public static ParserOutput FromComment(ZilObject zo) - { - return new ParserOutput - { - Type = ParserOutputType.Comment, - Object = zo - }; - } - - public static ParserOutput FromException(ParserException ex) - { - return new ParserOutput - { - Type = ParserOutputType.SyntaxError, - Exception = ex - }; - } - } - // TODO: Non-evaluating parser mode, to return %MACROs, %%MACROs, and LINKs as-is, and keep inner comments sealed class Parser { @@ -260,7 +34,6 @@ namespace Zilf.Language readonly ISourceLine? srcOverride; readonly ZilObject[]? templateParams; readonly Queue heldObjects = new Queue(); - int line = 1; public Parser(IParserSite site) : this(site, (ISourceLine?)null, null) @@ -279,12 +52,9 @@ namespace Zilf.Language this.templateParams = templateParams; } - public int Line => line; + public int Line { get; private set; } = 1; - public IEnumerable Parse(IEnumerable chars) - { - return Parse(new CharBuffer(chars)); - } + public IEnumerable Parse(IEnumerable chars) => Parse(new CharBuffer(chars)); IEnumerable Parse(CharBuffer chars) { @@ -376,7 +146,7 @@ namespace Zilf.Language } catch (ParserException ex) { - sourceLine = new FileSourceLine(site.CurrentFilePath, line); + sourceLine = new FileSourceLine(site.CurrentFilePath, Line); return ParserOutput.FromException(ex); } } @@ -388,11 +158,11 @@ namespace Zilf.Language // handle whitespace if (!SkipWhitespace(chars)) { - sourceLine = new FileSourceLine(site.CurrentFilePath, line); + sourceLine = new FileSourceLine(site.CurrentFilePath, Line); return ParserOutput.EndOfInput; } - sourceLine = new FileSourceLine(site.CurrentFilePath, line); + sourceLine = new FileSourceLine(site.CurrentFilePath, Line); var c = chars.Current; // '!' adds 128 to the next character (assuming it's below 128) @@ -616,13 +386,16 @@ namespace Zilf.Language case var _ when c.IsNonAtomChar(): throw new ExpectedButFound("atom", $"'{c.Rebang()}'"); + case var _ when site.GetPrefixMacro(c) is SimplePrefixMacroHandler handler: + return ParsePrefixed(chars, c, handler); + default: return ParserOutput.FromObject(ParseCurrentAtomOrNumber(chars)); } } catch (ParserException ex) { - sourceLine = new FileSourceLine(site.CurrentFilePath, line); + sourceLine = new FileSourceLine(site.CurrentFilePath, Line); return ParserOutput.FromException(ex); } } @@ -647,7 +420,7 @@ namespace Zilf.Language case '\n': // count line breaks - line++; + Line++; continue; case '!': @@ -669,7 +442,7 @@ namespace Zilf.Language case '\n': // count line breaks - line++; + Line++; continue; default: @@ -712,7 +485,7 @@ namespace Zilf.Language c = chars.Current; if (c == '\n') - line++; + Line++; sb.Append(c); } @@ -823,7 +596,7 @@ namespace Zilf.Language c = chars.Current; if (c == '\n') - line++; + Line++; sb.Append(c); } @@ -834,7 +607,7 @@ namespace Zilf.Language break; case '\n': - line++; + Line++; goto default; default: @@ -901,12 +674,12 @@ namespace Zilf.Language } } - ParserOutput ParsePrefixed(CharBuffer chars, char prefix, Func convert) + ParserOutput ParsePrefixed(CharBuffer chars, char prefix, SimplePrefixMacroHandler convert) { return ParsePrefixed(chars, prefix.Rebang(), convert); } - ParserOutput ParsePrefixed(CharBuffer chars, string prefix, Func convert) + ParserOutput ParsePrefixed(CharBuffer chars, string prefix, SimplePrefixMacroHandler convert) { ParserOutput po; ISourceLine src; diff --git a/src/Zilf/Language/Parsing/ParserException.cs b/src/Zilf/Language/Parsing/ParserException.cs new file mode 100644 index 0000000..b8617ee --- /dev/null +++ b/src/Zilf/Language/Parsing/ParserException.cs @@ -0,0 +1,74 @@ +using System; +using System.Runtime.Serialization; + +namespace Zilf.Language.Parsing +{ + [Serializable] + public abstract class ParserException : Exception + { + protected ParserException(string message, Exception? innerException) + : base(message, innerException) { } + + protected ParserException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + public ParserException() + { + } + + public ParserException(string message) : base(message) + { + } + } + + [Serializable] + sealed class ParsedNumberOverflowed : ParserException + { + const string DefaultRadix = "decimal"; + + public ParsedNumberOverflowed(string number, string radix = DefaultRadix, Exception? innerException = null) + : base($"{radix} number '{number}' cannot be represented in 32 bits", innerException) { } + + public ParsedNumberOverflowed(string number, Exception? innerException) + : this(number, DefaultRadix, innerException) { } + + ParsedNumberOverflowed(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + public ParsedNumberOverflowed() + { + } + + public ParsedNumberOverflowed(string message) : base(message) + { + } + } + + [Serializable] + sealed class ExpectedButFound : ParserException + { + public ExpectedButFound(string expected, string actual, Exception? innerException = null) + : base($"expected {expected} but found {actual}", innerException) { } + + ExpectedButFound(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + public ExpectedButFound() + { + } + + public ExpectedButFound(string message) : base(message) + { + } + + public ExpectedButFound(string message, Exception innerException) : base(message, innerException) + { + } + } +} \ No newline at end of file diff --git a/src/Zilf/Language/Parsing/ParserOutput.cs b/src/Zilf/Language/Parsing/ParserOutput.cs new file mode 100644 index 0000000..2135bcb --- /dev/null +++ b/src/Zilf/Language/Parsing/ParserOutput.cs @@ -0,0 +1,106 @@ +using System.Text; +using Zilf.Interpreter.Values; + +namespace Zilf.Language.Parsing +{ + enum ParserOutputType + { + /// + /// A valid was parsed. + /// + Object, + /// + /// A valid was parsed, with a comment prefix. + /// + Comment, + /// + /// A valid object could not be parsed. + /// + SyntaxError, + /// + /// There are no more characters to read. + /// + EndOfInput, + /// + /// A character was read (and pushed back) that may have terminated an outer structure. + /// + Terminator, + /// + /// A special object was parsed and evaluated, and there were no objects to insert in its place. + /// + /// + /// This happens whenever a %%macro is evaluated, or when a %macro returns #SPLICE (), or when + /// the left side of a {...:SPLICE} template invocation evaluates to an empty structure. + /// + EmptySplice, + } + + struct ParserOutput + { + public ParserOutputType Type; + public ZilObject Object; + public ParserException Exception; + + public bool IsIgnorable => Type == ParserOutputType.Comment || Type == ParserOutputType.EmptySplice; + + public override string ToString() + { + var sb = new StringBuilder(); + + sb.Append(Type); + + if (Object != null) + { + sb.Append(' '); + sb.Append(Object); + } + + if (Exception != null) + { + sb.Append(' '); + sb.Append(Exception.GetType().Name); + sb.Append("(\""); + sb.Append(Exception.Message); + sb.Append("\")"); + } + + return sb.ToString(); + } + + public static readonly ParserOutput EmptySplice = + new ParserOutput { Type = ParserOutputType.EmptySplice }; + + public static readonly ParserOutput EndOfInput = + new ParserOutput { Type = ParserOutputType.EndOfInput }; + + public static readonly ParserOutput Terminator = + new ParserOutput { Type = ParserOutputType.Terminator }; + + public static ParserOutput FromObject(ZilObject zo) + { + return new ParserOutput + { + Type = ParserOutputType.Object, + Object = zo + }; + } + + public static ParserOutput FromComment(ZilObject zo) + { + return new ParserOutput + { + Type = ParserOutputType.Comment, + Object = zo + }; + } + + public static ParserOutput FromException(ParserException ex) + { + return new ParserOutput + { + Type = ParserOutputType.SyntaxError, + Exception = ex + }; + } + } +} \ No newline at end of file diff --git a/src/Zilf/Program.cs b/src/Zilf/Program.cs index 6616cf7..2b3a96c 100644 --- a/src/Zilf/Program.cs +++ b/src/Zilf/Program.cs @@ -30,6 +30,7 @@ using Zilf.Diagnostics; using Zilf.Interpreter; using Zilf.Interpreter.Values; using Zilf.Language; +using Zilf.Language.Parsing; namespace Zilf { diff --git a/src/Zilf/Zilf.csproj b/src/Zilf/Zilf.csproj index 07206f8..cea7671 100644 --- a/src/Zilf/Zilf.csproj +++ b/src/Zilf/Zilf.csproj @@ -26,7 +26,7 @@ - + Zilf.licenseheader diff --git a/src/Zilf/Zilf.ruleset b/src/Zilf/Zilf.ruleset index bd49bf0..ad39ea6 100644 --- a/src/Zilf/Zilf.ruleset +++ b/src/Zilf/Zilf.ruleset @@ -95,5 +95,7 @@ + + \ No newline at end of file diff --git a/test/Dezapf.Tests/Dezapf.Tests.csproj b/test/Dezapf.Tests/Dezapf.Tests.csproj index e1c00a6..23340fc 100644 --- a/test/Dezapf.Tests/Dezapf.Tests.csproj +++ b/test/Dezapf.Tests/Dezapf.Tests.csproj @@ -16,7 +16,7 @@ - + Zilf.licenseheader diff --git a/test/Zilf.Tests.Integration/MacroTests.cs b/test/Zilf.Tests.Integration/MacroTests.cs index 074ecab..96676bf 100644 --- a/test/Zilf.Tests.Integration/MacroTests.cs +++ b/test/Zilf.Tests.Integration/MacroTests.cs @@ -61,5 +61,13 @@ namespace Zilf.Tests.Integration .GivesNumber("123"); } + [TestMethod, TestCategory("Reader Macros")] + public void MAKE_PREFIX_MACRO_Should_Work() + { + AssertExpr(@"") + .WithGlobal(@" BUZZ>>>") + .Outputs("hello world\n"); + } + } } diff --git a/test/Zilf.Tests/Interpreter/ParserTests.cs b/test/Zilf.Tests/Interpreter/ParserTests.cs index 7514af4..6e1ce80 100644 --- a/test/Zilf.Tests/Interpreter/ParserTests.cs +++ b/test/Zilf.Tests/Interpreter/ParserTests.cs @@ -25,6 +25,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Zilf.Interpreter; using Zilf.Interpreter.Values; using Zilf.Language; +using Zilf.Language.Parsing; namespace Zilf.Tests.Interpreter { @@ -38,6 +39,8 @@ namespace Zilf.Tests.Interpreter { readonly Dictionary atoms = new Dictionary(); + public ParserMacros Macros { get; } = new ParserMacros(new Context()); + [DisallowNull] public Func? OnEvaluate { get; set; } @@ -83,6 +86,8 @@ namespace Zilf.Tests.Interpreter { atoms.Add(name, new ZilAtom(name, null, stdAtom)); } + + public SimplePrefixMacroHandler? GetPrefixMacro(char prefix) => Macros.GetPrefixMacro(prefix); } TestParserSite site = default!; @@ -316,7 +321,7 @@ namespace Zilf.Tests.Interpreter } [TestMethod] - public void TestParsingReadMacro() + public void TestParsingStandardReadMacro() { var parser = new Parser(site); @@ -346,6 +351,25 @@ namespace Zilf.Tests.Interpreter Assert.AreEqual(ParserOutputType.EndOfInput, result[1].Type); } + [TestMethod] + public void TestParsingCustomReadMacro() + { + var keywords = new ObList(); + var nameKeyword = keywords["NAME"]; + + var parser = new Parser(site); + site.Macros.MakePrefixMacro('&', + (_, zo) => zo is ZilAtom atom + ? ParserOutput.FromObject(keywords[atom.Text]) + : ParserOutput.FromException(new ExpectedButFound("atom", zo.ToString()))); + + var result = parser.Parse("&NAME").ToArray(); + Assert.AreEqual(2, result.Length); + Assert.AreEqual(ParserOutputType.Object, result[0].Type); + Assert.AreSame(nameKeyword, result[0].Object); + Assert.AreEqual(ParserOutputType.EndOfInput, result[1].Type); + } + [TestMethod] public void TestParsingAdecl() { diff --git a/zillib/verbs.zil b/zillib/verbs.zil index b8a0d24..99956f2 100644 --- a/zillib/verbs.zil +++ b/zillib/verbs.zil @@ -1149,7 +1149,7 @@ Returns: (ELSE )>> ) + ;"FIXME: impossible?") ( ) ( @@ -1346,7 +1346,7 @@ Returns: > " yourself." CR>) + " yourself." CR> ;"FIXME: impossible?") ( ) (ELSE T>)> > @@ -1354,7 +1354,7 @@ Returns: ) - (ELSE " yourself." CR>)>) + (ELSE " yourself." CR> ;"FIXME: impossible?")>) ( ) ( ) (> ) -- GitLab From 81cd96387af8ef18e3d2440789885c794d6dded2 Mon Sep 17 00:00:00 2001 From: Jesse McGrew Date: Sun, 17 Jan 2021 00:29:23 -0800 Subject: [PATCH 2/6] Added QQ package for quasiquoting. After loading the package with , ` and ~ can be used to quote values while replacing the inner parts. Before: .N>> After: ~.N>> Fixes ZILF-180. --- zillib/experimental/qq.mud | 43 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 zillib/experimental/qq.mud diff --git a/zillib/experimental/qq.mud b/zillib/experimental/qq.mud new file mode 100644 index 0000000..00cafc0 --- /dev/null +++ b/zillib/experimental/qq.mud @@ -0,0 +1,43 @@ + + + + + ANY>> + +)) + > + +> + +>> + +) (T )) + + > + FORM> .ENV>>) + ( + .ENV>) + ( + >> + .STRUC) + ( >> + ) + (ELSE + > .T>)>> + +> + .STRUC>> + +> +> + +;"TODO: USE READER-MACROS" + + + )> + + -- GitLab From 0d8adb193973890c62de25a196540afa28d20c25 Mon Sep 17 00:00:00 2001 From: Jesse McGrew Date: Sun, 17 Jan 2021 00:30:25 -0800 Subject: [PATCH 3/6] Make READER-MACROS a builtin package. --- src/Zilf/Interpreter/Context.cs | 4 ++-- src/Zilf/Interpreter/Subrs.Output.cs | 2 +- test/Zilf.Tests.Integration/MacroTests.cs | 3 ++- zillib/experimental/qq.mud | 8 ++++---- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Zilf/Interpreter/Context.cs b/src/Zilf/Interpreter/Context.cs index 5086aa1..e6cc6a4 100644 --- a/src/Zilf/Interpreter/Context.cs +++ b/src/Zilf/Interpreter/Context.cs @@ -319,7 +319,7 @@ namespace Zilf.Interpreter void InitPackages() { - var emptyPackageNames = new[] { "NEWSTRUC", "ZILCH", "ZIL" }; + var emptyPackageNames = new[] { "NEWSTRUC", "ZILCH", "ZIL", "READER-MACROS" }; foreach (var name in emptyPackageNames) { @@ -351,7 +351,7 @@ namespace Zilf.Interpreter // these atoms need to be on the root oblist var atom = ZilAtom.Parse(name + "!-", this); - SetGlobalVal(atom, isFSubr ? new ZilFSubr(name, del) : new ZilSubr(name, del)); + SetGlobalVal(atom, isFSubr ? new ZilFSubr(baseName, del) : new ZilSubr(baseName, del)); } } } diff --git a/src/Zilf/Interpreter/Subrs.Output.cs b/src/Zilf/Interpreter/Subrs.Output.cs index e07564a..ada37c8 100644 --- a/src/Zilf/Interpreter/Subrs.Output.cs +++ b/src/Zilf/Interpreter/Subrs.Output.cs @@ -303,7 +303,7 @@ namespace Zilf.Interpreter return position; } - [Subr("MAKE-PREFIX-MACRO", ObList = "READER-MACROS")] + [Subr("MAKE-PREFIX-MACRO", ObList = "READER-MACROS!-PACKAGE")] public static ZilObject MAKE_PREFIX_MACRO(Context ctx, ZilChar ch, [Either(typeof(IApplicable), typeof(ZilFalse))] object handlerOrFalse) diff --git a/test/Zilf.Tests.Integration/MacroTests.cs b/test/Zilf.Tests.Integration/MacroTests.cs index 96676bf..f9e4c10 100644 --- a/test/Zilf.Tests.Integration/MacroTests.cs +++ b/test/Zilf.Tests.Integration/MacroTests.cs @@ -65,7 +65,8 @@ namespace Zilf.Tests.Integration public void MAKE_PREFIX_MACRO_Should_Work() { AssertExpr(@"") - .WithGlobal(@" BUZZ>>>") + .WithGlobal(@"") + .WithGlobal(@" BUZZ>>>") .Outputs("hello world\n"); } diff --git a/zillib/experimental/qq.mud b/zillib/experimental/qq.mud index 00cafc0..6da194d 100644 --- a/zillib/experimental/qq.mud +++ b/zillib/experimental/qq.mud @@ -2,6 +2,8 @@ + + ANY>> )) @@ -35,9 +37,7 @@ > > -;"TODO: USE READER-MACROS" - - - )> + + -- GitLab From ace64f34e2425fe16dfd2381dfb103b98378b4d1 Mon Sep 17 00:00:00 2001 From: Jesse McGrew Date: Sun, 17 Jan 2021 00:30:56 -0800 Subject: [PATCH 4/6] Converted zillib to use QQ. --- zillib/experimental/closure.mud | 2 +- zillib/orphan.zil | 10 ++- zillib/parser.zil | 145 ++++++++++++++++--------------- zillib/pronouns.zil | 121 ++++++++++++-------------- zillib/pseudo.zil | 15 ++-- zillib/{experimental => }/qq.mud | 0 zillib/scope.zil | 55 ++++++------ zillib/template.zil | 6 +- zillib/verbs.zil | 26 +++--- 9 files changed, 187 insertions(+), 193 deletions(-) rename zillib/{experimental => }/qq.mud (100%) diff --git a/zillib/experimental/closure.mud b/zillib/experimental/closure.mud index febf7b6..945cbfc 100644 --- a/zillib/experimental/closure.mud +++ b/zillib/experimental/closure.mud @@ -5,7 +5,7 @@ ]> FUNCTION>>
!.ARGS>>>> + ~!.ARGS>>>> diff --git a/zillib/orphan.zil b/zillib/orphan.zil index 73deb65..592e0d4 100644 --- a/zillib/orphan.zil +++ b/zillib/orphan.zil @@ -31,6 +31,8 @@ In the future, we may also need to store something about which part of the noun we're trying to improve (e.g. which YSPEC is ambiguous). So we use the top bits of a word to recall whether we're orphaning and why, and reserve the rest for future use." + + @@ -57,10 +59,10 @@ to recall whether we're orphaning and why, and reserve the rest for future use." >) ( )>)> - - ' - '>> + ` + + >> ;"Not an orphaning response; parse as usual" ;"We asked another question; abort parse" diff --git a/zillib/parser.zil b/zillib/parser.zil index 4292cac..d27bf37 100644 --- a/zillib/parser.zil +++ b/zillib/parser.zil @@ -1,5 +1,7 @@ "Library header" + + - ' - > - '(ELSE T)>> + ` + + ) + (ELSE T)>> !.A> - '(ELSE T)>> + ` ~!.A) + (ELSE T)>> > @@ -91,15 +93,15 @@ other versions. These macros let us write the same code for all versions." > - > - > - >) + > + > + > + >) (ELSE - > - > - > - >)> + > + > + > + >)> "Property and flag defaults" @@ -183,14 +185,14 @@ other versions. These macros let us write the same code for all versions." >>> - >>>>> + `>> + >>>> >>> - >>>>> + `>> + >>>> @@ -203,21 +205,20 @@ other versions. These macros let us write the same code for all versions." >>>> + >>> .A>> - > + `> > + `> > + `> >>> - >>>>> + `>> + ,~>>>> >> '("ARGS" A) - >>> + >> ("ARGS" A) + `<~.FIELD ,P-OOPS-DATA ~'~!.A>>>> '(OOPS-WN OOPS-CONT OOPS-O-REASON OOPS-WINNER)> "Structured types for storing noun phrases. @@ -350,19 +351,19 @@ Args: - ) + `) ( - <* ,P-OBJSPEC-SIZE <- .I 1>>>) + ` <* ,P-OBJSPEC-SIZE ~<- .I 1>>>) (ELSE - >>)>> + ` <* ,P-OBJSPEC-SIZE <- ~.I 1>>>)>> - ) + `) ( - <* ,P-OBJSPEC-SIZE <- .I 1>>>) + ` <* ,P-OBJSPEC-SIZE ~<- .I 1>>>) (ELSE - >>)>> + ` <* ,P-OBJSPEC-SIZE <- ~.I 1>>>)>> >) + `>) (ELSE >)> + `>)> )> @@ -470,7 +471,7 @@ Args: > .SRC .DEST <+ 1 ,P-MAX-OBJECTS>>> + `<~ ~.SRC ~.DEST <+ 1 ,P-MAX-OBJECTS>>> "Structured type for backing up a complete parsed command" >> >>> - - >> .RA> - >> + `>>> + + <~>> ~.RA> + .~.RA>> >) - (ELSE >)> - > + <* .LEN 2>) + (ELSE `<* ~.LEN 2>)>> + `> >)> + `>)> ;"Determines whether a given word can start a noun phrase. @@ -1982,10 +1983,10 @@ Returns: > .O>> + ` ~.O>> 255>> + ` 255>> ;"Searches scope for a usable light source. @@ -2196,13 +2197,13 @@ Returns: <+ ,L-PRSTABLE ,L-THE ,L-OR>> > -;"Determines whether an object is excluded by a NOUN-PHRASE's NTBL. +;"Determines whether an object is included by a NOUN-PHRASE's YTBL. Note: NP may be evaluated twice." .O>> + ` ~.O>> .PDO>> + ` ~.PDO>> )> @@ -2221,10 +2222,10 @@ Returns: ;"Determines whether an object is excluded by a NOUN-PHRASE's NTBL. Note: NP may be evaluated twice." .O>> + ` ~.O>> .PDO>> + ` ~.PDO>> ;"Determines whether a local-global object is present in a given room. @@ -2449,38 +2450,38 @@ Example: .GATOMS>> > >> + .TEMPATOMS .GATOMS>> > + > .GATOMS .NEWVALUES>> >> + > .GATOMS .TEMPATOMS>> ;"And finally..." - > - !.RESTORES - '.?RESULT>> + `> + ~!.RESTORES + .?RESULT>> >>> + >>> )> - .F - '>>) + ` )> + ~.F + >>) (ELSE >)> + `>)> "Action framework" @@ -2771,10 +2772,10 @@ Returns: - 1>>> 1>) + `<- <+ ~.LO 1>>> 1>) (ELSE - 1>>> 1>>)>>> + ` 1>>> 1>>)>>> ;"Returns a random element from a table, possibly repeating. diff --git a/zillib/pronouns.zil b/zillib/pronouns.zil index 432fb0b..2780cdc 100644 --- a/zillib/pronouns.zil +++ b/zillib/pronouns.zil @@ -8,11 +8,10 @@ (PRO-STMTS LIST)> )> >)>> + >)>> @@ -23,96 +22,90 @@ OBJS-TBL-NAME CONDITION) ;"Define table P-PRO-THEM-OBJS to hold the objects." "-OBJS">>> - >> + >> ;"Define routine PRO-TRY-SET-THEM to try to set the pronoun." >>> > >) - (ELSE >)> + (ELSE >)> > - PRO?OBJS - !>> - >> - '.PRO?OBJS>>>>> + `> + PRO?OBJS + ~!>) + >> .PRO?OBJS>)>>> ;"Define routine PRO-FORCE-SET-THEM to unconditionally set the pronoun." >>> " to ">> - <+ ,L-PRSTABLE ,L-THE>> - '> - > - '>> + ` " to "> + <+ ,L-PRSTABLE ,L-THE>> + > + + >> >> ,PRONOUN-DEFINITIONS> ;"Define SET-PRONOUNS" ,ROOMS> ) - (> - 2> 1>> - .OBJS>> - )> - !> - >>> - > - ,PRONOUN-DEFINITIONS>>> + ` ,ROOMS> ) + (> + 2> 1>> + .OBJS>> + )> + ~!> + >>> + `<~.RTN-NAME .O .OBJS>> + ,PRONOUN-DEFINITIONS>>> ;"Define EXPAND-PRONOUN" > - "-OBJS">>> - OBJECT>> - >> - ,PRONOUN-DEFINITIONS> - '(ELSE )> - '> - ' - - )> - '> - )> - ' >) - (ELSE )>>> + `> + OBJECT>> + "-OBJS">>> + `(<=? .W ~.V> )> + ,PRONOUN-DEFINITIONS> + (ELSE )> + > + + + )> + > + )> + >) + (ELSE )>>> ;"Define V-PRONOUNS" > "-OBJS">>> - " means ">> - - <> - <+ ,L-PRSTABLE ,L-THE ,L-SCENERY>> - >> + ,SP-MEANS-SP> + ` <+ ,L-PRSTABLE ,L-THE ,L-SCENERY>> + '>> ,PRONOUN-DEFINITIONS>>> > + + ;"Sets the appropriate pronouns to refer to an object." diff --git a/zillib/pseudo.zil b/zillib/pseudo.zil index ea2fb2f..e069329 100644 --- a/zillib/pseudo.zil +++ b/zillib/pseudo.zil @@ -32,19 +32,19 @@ > + `> > + `> > + `> > + `> > + `> ;"Like REFERS? but for pseudo entries. @@ -213,9 +213,8 @@ Returns: > >>> - - >>>> + )>>> .NAME>) ( .F) (ELSE )>>>>> diff --git a/zillib/experimental/qq.mud b/zillib/qq.mud similarity index 100% rename from zillib/experimental/qq.mud rename to zillib/qq.mud diff --git a/zillib/scope.zil b/zillib/scope.zil index f9352f4..5789165 100644 --- a/zillib/scope.zil +++ b/zillib/scope.zil @@ -22,14 +22,14 @@ ,LIST "-SCOPE-STAGE">>> .NAMES>> - - !.SSS>> + `<==? + + ~!.SSS>> - '>> + ` + >> "Scope stages" @@ -73,9 +73,9 @@ > > - >>>> + `>>> ,SCOPE-STAGES> ;"Define enough state for the most demanding stage" @@ -110,7 +110,7 @@ ( 2> <==? <1 .SV> BITS>> > >>>) + `(>)>) ( 2> <==? <1 .SV> STAGES>> > >) @@ -128,24 +128,23 @@ > - "-SCOPE-STAGE">>>> + ` "-SCOPE-STAGE">>>> .STAGES>>> > + > .INIT-STAGES>>)> - > - )> - - > - > - '>> - !.BODY>>> + `> + )> + > + )> + ~!.BODY>>> )) - > - "-SCOPE-STAGE">>>> + `> + ~ "-SCOPE-STAGE">>>> ,SCOPE-STAGES>> )> ;"We don't distinguish between HELD and CARRIED, or ON-GROUND and IN-ROOM." diff --git a/zillib/template.zil b/zillib/template.zil index 8470084..fcb81d4 100644 --- a/zillib/template.zil +++ b/zillib/template.zil @@ -78,9 +78,9 @@ object (whether or not they're overridden!): (ELSE .DEF)>> > - >> + > + <~.TYPE .NAME !.PS>>> > > .IF-PL> >> + ` ~.IF-PL) (ELSE ~.IF-SG)>> @@ -422,8 +422,8 @@ Args: > > - ' > + ` > <- .?TMP 32>) (ELSE .?TMP)>>> @@ -699,15 +699,15 @@ Returns: ;"Checks whether PRSA is a meta-verb that does not cause time to pass." - ! - !,EXTRA-GAME-VERBS>> + ` + ~! + ~!,EXTRA-GAME-VERBS>> > )> @@ -976,7 +976,7 @@ Returns: >>>>> > + `> > -- GitLab From 2ef87b079e7bee5ed2590e5051f4b288771d81e0 Mon Sep 17 00:00:00 2001 From: Jesse McGrew Date: Sun, 17 Jan 2021 00:31:26 -0800 Subject: [PATCH 5/6] Improve source line tracking in CHTYPE and QQ. Add SET-SOURCE-INFO (in the READER-MACROS package) for use by QQ. --- src/Zilf/Interpreter/Context.cs | 38 ++++++++++++++++++---------- src/Zilf/Interpreter/Subrs.Output.cs | 7 +++++ zillib/qq.mud | 2 +- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/Zilf/Interpreter/Context.cs b/src/Zilf/Interpreter/Context.cs index e6cc6a4..0193040 100644 --- a/src/Zilf/Interpreter/Context.cs +++ b/src/Zilf/Interpreter/Context.cs @@ -1166,6 +1166,8 @@ namespace Zilf.Interpreter if (value.GetTypeAtom(this) == newType) return value; + ZilObject result; + // TODO: standardize special cases switch (newType.StdAtom) { @@ -1178,34 +1180,42 @@ namespace Zilf.Interpreter if (value.PrimType != PrimType.ATOM) throw new InterpreterError(InterpreterMessages.CHTYPE_To_0_Requires_1, "GVAL or LVAL", "ATOM"); - return new ZilForm(new[] { newType, value.GetPrimitive(this) }) { SourceLine = SourceLines.Chtyped }; + result = new ZilForm(new[] { newType, value.GetPrimitive(this) }); + break; case StdAtom.ATOM when value.StdTypeAtom == StdAtom.FORM: if (value.IsGVAL(out var atom) || value.IsLVAL(out atom)) + { + // don't set SourceLine return atom; + } throw new InterpreterError(InterpreterMessages.CHTYPE_To_0_Requires_1, "ATOM", "ATOM, GVAL, or LVAL"); // special case for TABLE: its primtype is TABLE, but VECTOR can be converted too case StdAtom.TABLE when value.PrimType == PrimType.VECTOR: var vector = (ZilVector)value.GetPrimitive(this); - return ZilTable.Create(1, vector.ToArray(), 0, null); - } + result = ZilTable.Create(1, vector.ToArray(), 0, null); + break; - // look it up in the typemap - if (typeMap.TryGetValue(newType, out var entry)) - { - if (value.PrimType != entry.PrimType) - { - throw new InterpreterError( - InterpreterMessages.CHTYPE_To_0_Requires_1, newType, entry.PrimType); - } + // look it up in the typemap + case var _ when typeMap.TryGetValue(newType, out var entry): + if (value.PrimType != entry.PrimType) + { + throw new InterpreterError( + InterpreterMessages.CHTYPE_To_0_Requires_1, newType, entry.PrimType); + } - return entry.ChtypeMethod(this, value.GetPrimitive(this)); + result = entry.ChtypeMethod(this, value.GetPrimitive(this)); + break; + + // unknown type + default: + throw new InterpreterError(InterpreterMessages.Unrecognized_0_1, "type", newType); } - // unknown type - throw new InterpreterError(InterpreterMessages.Unrecognized_0_1, "type", newType); + result.SourceLine ??= SourceLines.Chtyped; + return result; } void InitTellPatterns() diff --git a/src/Zilf/Interpreter/Subrs.Output.cs b/src/Zilf/Interpreter/Subrs.Output.cs index ada37c8..cab8b27 100644 --- a/src/Zilf/Interpreter/Subrs.Output.cs +++ b/src/Zilf/Interpreter/Subrs.Output.cs @@ -314,5 +314,12 @@ namespace Zilf.Interpreter : (SimplePrefixMacroHandlerWithContext?)null); return ctx.TRUE; } + + [Subr("SET-SOURCE-INFO", ObList = "READER-MACROS!-PACKAGE")] + public static ZilObject SET_SOURCE_INFO(Context ctx, ZilObject dest, ZilObject src) + { + dest.SourceLine = src.SourceLine; + return dest; + } } } diff --git a/zillib/qq.mud b/zillib/qq.mud index 6da194d..fd0d4dc 100644 --- a/zillib/qq.mud +++ b/zillib/qq.mud @@ -27,7 +27,7 @@ ( >> ) (ELSE - > .T>)>> + > .T> .STRUC>)>> Date: Sun, 17 Jan 2021 00:32:41 -0800 Subject: [PATCH 6/6] Change include file searching to try a lowercase version of the filename. --- src/Zilf/Interpreter/Context.cs | 55 +++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/src/Zilf/Interpreter/Context.cs b/src/Zilf/Interpreter/Context.cs index 0193040..ba16e2c 100644 --- a/src/Zilf/Interpreter/Context.cs +++ b/src/Zilf/Interpreter/Context.cs @@ -708,30 +708,59 @@ namespace Zilf.Interpreter public void HandleError(ZilErrorBase ex) => DiagnosticManager.Handle(ex.Diagnostic ?? throw new ArgumentException("Missing diagnostic", nameof(ex))); + private static readonly string[] IncludeFileExtensions = { ".zil", ".mud", ".ZIL", ".MUD" }; + /// The file wasn't found in any include path. public string FindIncludeFile(string name) { - // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator foreach (var path in IncludePaths) { - var combined = Path.Combine(path, name); + foreach (var nameVariant in GetIncludeFileNameVariants(name)) + { + var combined = Path.Combine(path, nameVariant); - if (FileExists(combined)) - return combined; + if (FileExists(combined)) + return combined; + } + } - if (!string.IsNullOrEmpty(Path.GetExtension(combined))) - continue; + throw new FileNotFoundException(); + } + + [SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", + Justification = "This method looks for a lowercase filename when the source specifies an uppercase filename.")] + private static IEnumerable GetIncludeFileNameVariants(string name) + { + // try the name as-is + yield return name; - combined = Path.ChangeExtension(combined, ".zil"); - if (FileExists(combined)) - return combined; + // try adding a .zil or .mud extension + bool hasExtension = !string.IsNullOrEmpty(Path.GetExtension(name)); - combined = Path.ChangeExtension(combined, ".mud"); - if (FileExists(combined)) - return combined; + if (!hasExtension) + { + foreach (var ext in IncludeFileExtensions) + { + yield return Path.ChangeExtension(name, ext); + } } - throw new FileNotFoundException(); + // try lowercasing the name + var lower = name.ToLowerInvariant(); + + if (lower != name) + { + yield return lower; + + // try adding a .zil or .mud extension + if (!hasExtension) + { + foreach (var ext in IncludeFileExtensions) + { + yield return Path.ChangeExtension(lower, ext); + } + } + } } /// -- GitLab