From 6874768ffca494cc7a02699e79758a8cf507eb8c Mon Sep 17 00:00:00 2001 From: haven1433 Date: Thu, 15 Dec 2022 08:06:26 -0600 Subject: [PATCH] additional macros support for out-of-order macro commands also allows for arguments to be applied multiple times --- .../Models/Code/ScriptParser.cs | 78 +++++++++++++++++-- .../Models/Code/scriptReference.txt | 2 + 2 files changed, 72 insertions(+), 8 deletions(-) diff --git a/src/HexManiac.Core/Models/Code/ScriptParser.cs b/src/HexManiac.Core/Models/Code/ScriptParser.cs index 64bba906..2480c102 100644 --- a/src/HexManiac.Core/Models/Code/ScriptParser.cs +++ b/src/HexManiac.Core/Models/Code/ScriptParser.cs @@ -518,13 +518,16 @@ namespace HavenSoft.HexManiac.Core.Models.Code { private static readonly IReadOnlyList emptyByteList = new byte[0]; private readonly List documentation = new List(); + private bool hasShortForm; + private readonly Dictionary shortIndexFromLongIndex = new(); + public IReadOnlyList Args { get; } public IReadOnlyList LineCode => emptyByteList; public IReadOnlyList Documentation => documentation; public string LineCommand { get; } public bool IsEndingCommand => false; public bool IsValid { get; } = true; - public string Usage { get; } + public string Usage { get; private set; } public static bool IsMacroLine(string engineLine) { engineLine = engineLine.Trim(); @@ -539,7 +542,10 @@ namespace HavenSoft.HexManiac.Core.Models.Code { var docSplit = engineLine.Split(new[] { '#' }, 2); if (docSplit.Length > 1) documentation.Add('#' + docSplit[1]); engineLine = docSplit[0].Trim(); - Usage = " ".Join(engineLine.Split(' ').Where(token => token.Length != 2 || !token.TryParseHex(out _))); + ExtractShortformInfo(ref engineLine); + if (!hasShortForm) { + Usage = " ".Join(engineLine.Split(' ').Where(token => token.Length != 2 || !token.TryParseHex(out _))); + } var tokens = engineLine.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); var args = new List(); @@ -559,6 +565,29 @@ namespace HavenSoft.HexManiac.Core.Models.Code { Args = args; } + private void ExtractShortformInfo(ref string engineLine) { + if (!engineLine.Contains("->")) return; + var parts = engineLine.Split("->"); + if (parts.Length != 2) return; + engineLine = parts[1]; + var shortTokens = parts[0].Split(' ', StringSplitOptions.RemoveEmptyEntries); + var longTokens = parts[1].Split(' ', StringSplitOptions.RemoveEmptyEntries); + if (shortTokens[0] != longTokens[0]) return; + shortTokens = shortTokens.Skip(1).ToArray(); + longTokens = longTokens.Skip(1).ToArray(); + + // for each entry in long, it shows up somewhere in short + // entries in long can appear multiple times + for (int i = 0; i < longTokens.Length; i++) { + var index = shortTokens.IndexOf(longTokens[i]); + if (index == -1) continue; + shortIndexFromLongIndex.Add(i, index); + } + + hasShortForm = true; + Usage = parts[0]; + } + public bool MatchesGame(string game) => true; public int CompiledByteLength(IDataModel model, int start) { var length = LineCode.Count; @@ -593,13 +622,19 @@ namespace HavenSoft.HexManiac.Core.Models.Code { public string Decompile(IDataModel data, int start) { var builder = new StringBuilder(LineCommand); var streamContent = new List(); + var args = new List(); foreach (var arg in Args) { if (arg is ScriptArg sarg) { - builder.Append(" "); - sarg.Build(false, data, start, builder, streamContent); + var tempBuilder = new StringBuilder(); + sarg.Build(false, data, start, tempBuilder, streamContent); + args.Add(tempBuilder.ToString()); } start += arg.Length(data, start); } + if (args.Count > 0) { + builder.Append(" "); + builder.Append(" ".Join(ConvertLongFormToShortForm(args.ToArray()))); + } foreach (var content in streamContent) { builder.AppendLine(); builder.AppendLine("{"); @@ -612,23 +647,27 @@ namespace HavenSoft.HexManiac.Core.Models.Code { public bool CanCompile(string line) { var tokens = ScriptLine.Tokenize(line); if (tokens[0] != LineCommand) return false; - return tokens.Length == Args.Where(arg => arg is ScriptArg).Count() + 1; + var args = tokens.Skip(1).ToArray(); + args = ConvertShortFormToLongForm(args); + return args.Length == Args.Where(arg => arg is ScriptArg).Count(); } public string Compile(IDataModel model, int start, string scriptLine, LabelLibrary labels, out byte[] result) { result = null; var tokens = ScriptLine.Tokenize(scriptLine); if (tokens[0] != LineCommand) throw new ArgumentException($"Command {LineCommand} was expected, but received {tokens[0]} instead."); + var args = tokens.Skip(1).ToArray(); + args = ConvertShortFormToLongForm(args); var commandText = LineCommand; var specifiedArgs = Args.Where(arg => arg is ScriptArg).Count(); - if (specifiedArgs != tokens.Length - 1) { - return $"Command {commandText} expects {specifiedArgs} arguments, but received {tokens.Length - 1} instead."; + if (specifiedArgs != args.Length) { + return $"Command {commandText} expects {specifiedArgs} arguments, but received {args.Length} instead."; } var results = new List(); var specifiedArgIndex = 0; for (int i = 0; i < Args.Count; i++) { if (Args[i] is ScriptArg scriptArg) { - var token = tokens[1 + specifiedArgIndex]; + var token = args[specifiedArgIndex]; var message = scriptArg.Build(model, token, results, labels); if (message != null) return message; specifiedArgIndex += 1; @@ -641,6 +680,29 @@ namespace HavenSoft.HexManiac.Core.Models.Code { } public void AddDocumentation(string doc) => documentation.Add(doc); + + private string[] ConvertShortFormToLongForm(string[] args) { + if (!hasShortForm) return args; + // build long-form args from this short form + var longForm = new List(); + for (int i = 0; i < Args.Count; i++) { + if (Args[i] is SilentMatchArg) continue; + var shortIndex = shortIndexFromLongIndex[i]; + if (shortIndex < args.Length) longForm.Add(args[shortIndex]); + } + return longForm.ToArray(); + } + + private string[] ConvertLongFormToShortForm(string[] args) { + if (!hasShortForm) return args; + var shortForm = new Dictionary(); + for (int i = 0; i < Args.Count; i++) { + if (Args[i] is SilentMatchArg) continue; + var shortIndex = shortIndexFromLongIndex[i]; + shortForm[shortIndex] = args[shortForm.Count]; + } + return shortForm.Count.Range(i => shortForm[i]).ToArray(); + } } public enum ExpectedPointerType { diff --git a/src/HexManiac.Core/Models/Code/scriptReference.txt b/src/HexManiac.Core/Models/Code/scriptReference.txt index 4e083ed4..caff1389 100644 --- a/src/HexManiac.Core/Models/Code/scriptReference.txt +++ b/src/HexManiac.Core/Models/Code/scriptReference.txt @@ -31,6 +31,8 @@ wild.battle B6 species:data.pokemon.names level. item:data.items.stats B7 give.item 1A 00 80 item:data.items.stats 1A 01 80 count: 09 01 msgbox.item 0F 00 msg<""> 1A 00 80 item:data.items.stats 1A 01 80 count: 1A 02 80 song:songnames 09 09 # shows a message about a received item, # followed by a standard 'put away' message. +msgbox.fanfare 31 song:songnames 67 ptr<""> 66 32 +register.matchcall trainer:data.trainers.stats -> register.matchcall 16 04 80 trainer:data.trainers.stats 25 EA 01 1A 00 80 trainer:data.trainers.stats 09 08 00 nop # does nothing