diff --git a/Core6502DotNet.sln b/Core6502DotNet.sln index c6fb68d..f535774 100644 --- a/Core6502DotNet.sln +++ b/Core6502DotNet.sln @@ -33,7 +33,7 @@ Global SolutionGuid = {9BE7575F-6E99-4243-AEAA-4D1E1064DA97} EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution - version = 2.8.0.1 + version = 2.8.1.1 Policies = $0 $0.VersionControlPolicy = $1 $1.CommitMessageStyle = $2 diff --git a/Core6502DotNet/Core6502DotNet.csproj b/Core6502DotNet/Core6502DotNet.csproj index 872de6e..07254d5 100644 --- a/Core6502DotNet/Core6502DotNet.csproj +++ b/Core6502DotNet/Core6502DotNet.csproj @@ -4,7 +4,7 @@ Exe net5.0 6502.Net - 2.8.0.1 + 2.8.1.1 informedcitizenry informedcitizenry 6502.Net @@ -13,8 +13,8 @@ 2.4.1 Core6502DotNet.Program 6502.Net - 2.8.0.1 - 2.8.0.1 + 2.8.1.1 + 2.8.1.1 false https://github.com/informedcitizenry/6502.Net diff --git a/Core6502DotNet/src/Core/AssembleErrorHandler.cs b/Core6502DotNet/src/Core/AssembleErrorHandler.cs index 4b3718f..b777416 100644 --- a/Core6502DotNet/src/Core/AssembleErrorHandler.cs +++ b/Core6502DotNet/src/Core/AssembleErrorHandler.cs @@ -75,7 +75,10 @@ public void HandleError(object sender, AssemblyErrorEventArgs args) } else if (args.Exception is BlockClosureException blockEx) { - _services.Log.LogEntry(blockEx.LineExpectingClosure.Instruction, blockEx.Message); + if (blockEx.LineExpectingClosure.Instruction != null) + _services.Log.LogEntry(blockEx.LineExpectingClosure.Instruction, blockEx.Message); + else + _services.Log.LogEntry(blockEx.LineExpectingClosure, 1, blockEx.Message); } else { diff --git a/Core6502DotNet/src/Core/Assembler.cs b/Core6502DotNet/src/Core/Assembler.cs index b94b212..d318d39 100644 --- a/Core6502DotNet/src/Core/Assembler.cs +++ b/Core6502DotNet/src/Core/Assembler.cs @@ -18,8 +18,7 @@ public static class Assembler #region Properties /// - /// Gets the version of the assembler. This can and should be set - /// by the client code. + /// Gets the version of the assembler. /// public static string AssemblerVersion { @@ -31,8 +30,20 @@ public static string AssemblerVersion } /// - /// Gets the assembler (product) name. This can and should be set - /// by the client code. + /// Gets the assembly product summary, including simple name and version. + /// + public static string ProductSummary + { + get + { + var product = Assembly.GetEntryAssembly().GetName(); + return $"{product.Name} Version {product.Version}"; + } + } + + + /// + /// Gets the assembler (product) name. /// public static string AssemblerName { diff --git a/Core6502DotNet/src/Core/AssemblerBase.cs b/Core6502DotNet/src/Core/AssemblerBase.cs index 86b9a8f..ac06db9 100644 --- a/Core6502DotNet/src/Core/AssemblerBase.cs +++ b/Core6502DotNet/src/Core/AssemblerBase.cs @@ -87,6 +87,7 @@ public string Assemble(RandomAccessIterator lines) var first = lines.Current; bool isSpecial = first.Label != null && first.Label.IsSpecialOperator(); LogicalPCOnAssemble = Services.Output.LogicalPC; + LongLogicalPCOnAssemble = Services.Output.LongLogicalPC; PCOnAssemble = Services.Output.ProgramCounter; LongPCOnAssemble = Services.Output.LongProgramCounter; if (first.Label != null && !first.Label.Name.Equals("*")) @@ -193,6 +194,12 @@ protected void DefineLabel(Token label, double address, bool setLocalScope) /// protected int LogicalPCOnAssemble { get; private set; } + /// + /// Gets the state of the long Logical Program Counter for the 's + /// object when OnAssemble was invoked. + /// + protected int LongLogicalPCOnAssemble { get; private set; } + /// /// Gets the state of the real Program Counter for the 's /// object when OnAssemble was invoked. diff --git a/Core6502DotNet/src/Core/AssemblyController.cs b/Core6502DotNet/src/Core/AssemblyController.cs index 0a19798..0a38c2a 100644 --- a/Core6502DotNet/src/Core/AssemblyController.cs +++ b/Core6502DotNet/src/Core/AssemblyController.cs @@ -150,11 +150,10 @@ public double Assemble() else { var passedArgs = _services.Options.GetPassedArgs(); - var exec = Process.GetCurrentProcess().MainModule.ModuleName; var inputFiles = string.Join("\n// ", preprocessor.GetInputFiles()); - var fullDisasm = $"// {Assembler.AssemblerNameSimple}\n" + - $"// {exec} {string.Join(' ', passedArgs)}\n" + - $"// {DateTime.Now:f}\n\n// Input files:\n\n" + + var fullDisasm = $"// {Assembler.ProductSummary}\n" + + $"// Options: {string.Join(' ', passedArgs)}\n" + + $"// {DateTime.Now:f}\n// Input files:\n\n" + $"// {inputFiles}\n\n" + disassembly; byteCount = WriteOutput(fullDisasm); if (!_services.Options.NoWarnings && _services.Log.HasWarnings) diff --git a/Core6502DotNet/src/Core/AssemblyServices.cs b/Core6502DotNet/src/Core/AssemblyServices.cs index d8861e7..c3b0f04 100644 --- a/Core6502DotNet/src/Core/AssemblyServices.cs +++ b/Core6502DotNet/src/Core/AssemblyServices.cs @@ -33,7 +33,7 @@ public AssemblyServices(Options options) OutputFormat = Options.Format ?? string.Empty; CPU = Options.CPU; Encoding = new AsmEncoding(Options.CaseSensitive); - Evaluator = new Evaluator(Options.CaseSensitive) { SymbolEvaluator = EvaluateSymbol }; + Evaluator = new Evaluator(Options.CaseSensitive) { UnknownValueEvaluator = EvaluateUnknownOperand }; SymbolManager = new SymbolManager(options.CaseSensitive, Evaluator); Log = new ErrorLog(Options.WarningsAsErrors); Output = new BinaryOutput(true, Options.CaseSensitive, Options.LongAddressing); @@ -43,17 +43,20 @@ public AssemblyServices(Options options) #region Methods - double EvaluateSymbol(RandomAccessIterator tokens) + double EvaluateUnknownOperand(RandomAccessIterator tokens) { var token = tokens.Current; - var subscript = -1; + var subscript = -1.0; var converted = double.NaN; var isString = token.IsDoubleQuote(); if (char.IsLetter(token.Name[0]) || token.Name[0] == '_') { - var next = tokens.GetNext(); - if (next != null && next.IsOpen() && next.Name.Equals("[")) - subscript = (int)Evaluator.Evaluate(tokens, 0, int.MaxValue); + var next = tokens.PeekNext(); + if (next?.IsOpen() == true && next.Name.Equals("[")) + { + tokens.MoveNext(); + subscript = Evaluator.Evaluate(tokens, 0, int.MaxValue); + } var symbol = SymbolManager.GetSymbol(token, CurrentPass > 0); if (symbol == null) { @@ -71,15 +74,16 @@ public AssemblyServices(Options options) } if (subscript >= 0) { - if (symbol.StorageType != StorageType.Vector) - throw new SyntaxException(token.Position, "Type mismatch."); - if ((symbol.IsNumeric && subscript >= symbol.NumericVector.Count) || - (!symbol.IsNumeric && subscript >= symbol.StringVector.Count)) - throw new SyntaxException(token.Position, "Index was out of range."); - if (symbol.IsNumeric) - return symbol.NumericVector[subscript]; - token = new Token(symbol.StringVector[subscript], TokenType.Operand); - isString = true; + StringView tokenStringValue = ""; + double tokenValue = double.NaN; + isString = symbol.DataType == DataType.String && symbol.StorageType == StorageType.Vector; + Symbol.AccessResult result = isString ? symbol.TryGetElementAt(subscript, out tokenStringValue) : + symbol.TryGetElementAt(subscript, out tokenValue); + if (result != Symbol.AccessResult.Success) + throw new ExpressionException(next, SymbolManager.GetErrorMessageFromGetResult(result)); + if (!isString) + return tokenValue; + token = new Token(tokenStringValue, TokenType.Operand); } else if (symbol.IsNumeric) { diff --git a/Core6502DotNet/src/Core/BinaryOutput.cs b/Core6502DotNet/src/Core/BinaryOutput.cs index fdb1fd3..03994ba 100644 --- a/Core6502DotNet/src/Core/BinaryOutput.cs +++ b/Core6502DotNet/src/Core/BinaryOutput.cs @@ -166,7 +166,7 @@ public static int GetAlignmentSize(int pc, int amount) /// The byte string converted. public IEnumerable ConvertToBytes(double value) { - var bytes = BitConverter.GetBytes(Convert.ToInt64(value)).ToList(); + var bytes = BitConverter.GetBytes((long)value).ToList(); int nonZero; if (value < 0) nonZero = bytes.FindLastIndex(b => b != 255); @@ -420,8 +420,8 @@ public void AddBytes(IEnumerable bytes, int size, bool ignoreEndian) } _logicalPc = (_logicalPc + size) % BufferSize; - if (ProgramEnd < ((_blocks << 16) | ProgramCounter)) - ProgramEnd = (_blocks << 16) | ProgramCounter; + if (ProgramEnd < LongProgramCounter) + ProgramEnd = LongProgramCounter; if (_sectionCollection.SectionSelected) _sectionCollection.SetOutputCount(_pc - _sectionCollection.SelectedStartAddress); } @@ -502,12 +502,12 @@ public int GetRelativeOffset(int address1, int offsetfromPc) /// The set of bytes from the specified start address to Program End. public ReadOnlyCollection GetBytesFrom(int start) { - if (!_compilingStarted || ((_blocks << 16) | ProgramCounter) < ProgramStart) + if (!_compilingStarted || LongProgramCounter < ProgramStart) return new List().AsReadOnly(); - if ((ProgramCounter & MaxAddress) != _logicalPc) + if (LongProgramCounter != LongLogicalPC) { - var diff = _logicalPc - start; - start = ProgramCounter - diff; + var diff = LongLogicalPC - start; + start = LongProgramCounter - diff; } if (start < ProgramStart || start >= ProgramEnd) return new List().AsReadOnly(); @@ -755,6 +755,12 @@ private set /// public int LongProgramCounter => ProgramCounter | (_blocks << 16); + /// + /// Gets the long logical Program Counter. + /// + public int LongLogicalPC => _logicalPc | (_blocks << 16); + + /// /// Gets the names of any defined sections not used during /// assembly. diff --git a/Core6502DotNet/src/Core/Evaluator.cs b/Core6502DotNet/src/Core/Evaluator.cs index 9cbad47..aa4df84 100644 --- a/Core6502DotNet/src/Core/Evaluator.cs +++ b/Core6502DotNet/src/Core/Evaluator.cs @@ -7,8 +7,8 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; -using System.Text.RegularExpressions; using ConversionDef = System.Func; using OperationDef = System.Tuple, double>, int>; @@ -35,6 +35,19 @@ public IllegalQuantityException(Token token, double quantity) public double Quantity { get; } } + /// + /// An enumeration of possible value types returned in an successful evaluation. + /// + public enum ValueType + { + Unknown = 0, + Boolean, + Binary, + Double, + Integer + } + + /// /// A class that can evaluate strings as mathematical expressions. /// @@ -58,8 +71,6 @@ public class Evaluator #region Members - static readonly Regex s_separators = new Regex(@"\d_\d", RegexOptions.Compiled); - static readonly HashSet s_conditionals = new HashSet { "||", "&&", "<", "<=", ">", ">=", "==", "!=" @@ -74,37 +85,39 @@ public class Evaluator static readonly IReadOnlyDictionary s_operators = new Dictionary { { new Token(",", TokenType.Separator), new OperationDef(parms => parms[0], 0) }, - { new Token(">", TokenType.Unary), new OperationDef(parms => ((long)parms[0] & 65535) / 0x100, 1) }, - { new Token("<", TokenType.Unary), new OperationDef(parms => (long)parms[0] & 255, 1) }, - { new Token("&", TokenType.Unary), new OperationDef(parms => (long)parms[0] & 65535, 1) }, - { new Token("^", TokenType.Unary), new OperationDef(parms => ((long)parms[0] & UInt24.MaxValue) / 0x10000, 1) }, - { new Token("||", TokenType.Binary), new OperationDef(parms => ((long)parms[1]!=0?1:0) | ((long)parms[0]!=0?1:0), 2) }, - { new Token("&&", TokenType.Binary), new OperationDef(parms => ((long)parms[1]!=0?1:0) & ((long)parms[0]!=0?1:0), 3) }, - { new Token("|", TokenType.Binary), new OperationDef(parms => (long)parms[1] | (long)parms[0], 4) }, - { new Token("^", TokenType.Binary), new OperationDef(parms => (long)parms[1] ^ (long)parms[0], 5) }, - { new Token("&", TokenType.Binary), new OperationDef(parms => (long)parms[1] & (long)parms[0], 6) }, - { new Token("!=", TokenType.Binary), new OperationDef(parms => !parms[1].AlmostEquals(parms[0])?1:0, 7) }, - { new Token("==", TokenType.Binary), new OperationDef(parms => parms[1].AlmostEquals(parms[0])?1:0, 7) }, - { new Token("<", TokenType.Binary), new OperationDef(parms => parms[1] < parms[0] ? 1 : 0, 8) }, - { new Token("<=", TokenType.Binary), new OperationDef(parms => parms[1] <= parms[0] ? 1 : 0, 8) }, - { new Token(">=", TokenType.Binary), new OperationDef(parms => parms[1] >= parms[0] ? 1 : 0, 8) }, - { new Token(">", TokenType.Binary), new OperationDef(parms => parms[1] > parms[0] ? 1 : 0, 8) }, - { new Token("<<", TokenType.Binary), new OperationDef(parms => (int)parms[1] << (int)parms[0], 9) }, - { new Token(">>", TokenType.Binary), new OperationDef(parms => (int)parms[1] >> (int)parms[0], 9) }, - { new Token("-", TokenType.Binary), new OperationDef(parms => parms[1] - parms[0], 10) }, - { new Token("+", TokenType.Binary), new OperationDef(parms => parms[1] + parms[0], 10) }, - { new Token("/", TokenType.Binary), new OperationDef(parms => parms[0]==0?throw new DivideByZeroException():parms[1]/parms[0],11) }, - { new Token("*", TokenType.Binary), new OperationDef(parms => parms[1] * parms[0], 11) }, - { new Token("%", TokenType.Binary), new OperationDef(parms => (long)parms[1] % (long)parms[0], 11) }, - { new Token("^^", TokenType.Binary), new OperationDef(parms => Math.Pow(parms[1], parms[0]), 12) }, - { new Token("~", TokenType.Unary), new OperationDef(parms => ~((long)parms[0]), 13) }, - { new Token("!", TokenType.Unary), new OperationDef(parms => parms[0].AlmostEquals(0) ? 1 : 0, 13) }, - { new Token("-", TokenType.Unary), new OperationDef(parms => -parms[0], 13) }, - { new Token("+", TokenType.Unary), new OperationDef(parms => +parms[0], 13) }, - { new Token("$", TokenType.Radix), new OperationDef(parms => parms[0], 13) }, - { new Token("%", TokenType.Radix), new OperationDef(parms => parms[0], 13) } + { new Token("?", TokenType.Ternary), new OperationDef(parms => parms[2] == 1 ? parms[0] : parms[1], 1) }, + { new Token(":", TokenType.Ternary), new OperationDef(parms => parms[0], 1) }, + { new Token(">", TokenType.Unary), new OperationDef(parms => ((long)parms[0] & 65535) / 0x100, 2) }, + { new Token("<", TokenType.Unary), new OperationDef(parms => (long)parms[0] & 255, 2) }, + { new Token("&", TokenType.Unary), new OperationDef(parms => (long)parms[0] & 65535, 2) }, + { new Token("^", TokenType.Unary), new OperationDef(parms => ((long)parms[0] & UInt24.MaxValue) / 0x10000, 2) }, + { new Token("||", TokenType.Binary), new OperationDef(parms => ((long)parms[1]!=0?1:0) | ((long)parms[0]!=0?1:0), 3) }, + { new Token("&&", TokenType.Binary), new OperationDef(parms => ((long)parms[1]!=0?1:0) & ((long)parms[0]!=0?1:0), 4) }, + { new Token("|", TokenType.Binary), new OperationDef(parms => (long)parms[1] | (long)parms[0], 5) }, + { new Token("^", TokenType.Binary), new OperationDef(parms => (long)parms[1] ^ (long)parms[0], 6) }, + { new Token("&", TokenType.Binary), new OperationDef(parms => (long)parms[1] & (long)parms[0], 7) }, + { new Token("<=>",TokenType.Binary), new OperationDef(parms => Comparer.Default.Compare(parms[1], parms[0]), 8) }, + { new Token("!=", TokenType.Binary), new OperationDef(parms => !parms[1].AlmostEquals(parms[0])?1:0, 9) }, + { new Token("==", TokenType.Binary), new OperationDef(parms => parms[1].AlmostEquals(parms[0])?1:0, 9) }, + { new Token("<", TokenType.Binary), new OperationDef(parms => parms[1] < parms[0] ? 1 : 0, 10) }, + { new Token("<=", TokenType.Binary), new OperationDef(parms => parms[1] <= parms[0] ? 1 : 0, 10) }, + { new Token(">=", TokenType.Binary), new OperationDef(parms => parms[1] >= parms[0] ? 1 : 0, 10) }, + { new Token(">", TokenType.Binary), new OperationDef(parms => parms[1] > parms[0] ? 1 : 0, 10) }, + { new Token("<<", TokenType.Binary), new OperationDef(parms => (int)parms[1] << (int)parms[0], 11) }, + { new Token(">>", TokenType.Binary), new OperationDef(parms => (int)parms[1] >> (int)parms[0], 11) }, + { new Token("-", TokenType.Binary), new OperationDef(parms => parms[1] - parms[0], 12) }, + { new Token("+", TokenType.Binary), new OperationDef(parms => parms[1] + parms[0], 12) }, + { new Token("/", TokenType.Binary), new OperationDef(parms => parms[0]==0?throw new DivideByZeroException():parms[1]/parms[0],13) }, + { new Token("*", TokenType.Binary), new OperationDef(parms => parms[1] * parms[0], 13) }, + { new Token("%", TokenType.Binary), new OperationDef(parms => (long)parms[1] % (long)parms[0], 13) }, + { new Token("^^", TokenType.Binary), new OperationDef(parms => Math.Pow(parms[1], parms[0]), 14) }, + { new Token("~", TokenType.Unary), new OperationDef(parms => ~((long)parms[0]), 15) }, + { new Token("!", TokenType.Unary), new OperationDef(parms => parms[0].AlmostEquals(0) ? 1 : 0, 15) }, + { new Token("-", TokenType.Unary), new OperationDef(parms => -parms[0], 15) }, + { new Token("+", TokenType.Unary), new OperationDef(parms => +parms[0], 15) } }; - + static readonly CultureInfo s_info; + const NumberStyles AsmNumberStyle = NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands | NumberStyles.AllowExponent; static readonly Random s_rng = new Random(); readonly IReadOnlyDictionary _functions; readonly List _functionEvaluators; @@ -114,6 +127,13 @@ public class Evaluator #region Constructors + static Evaluator() + { + s_info = CultureInfo.CreateSpecificCulture("en-US"); + var nfi = s_info.NumberFormat; + nfi.NumberGroupSeparator = "_"; + } + /// /// Creates a new instance of the expression evaluator. /// @@ -213,73 +233,78 @@ public bool ExpressionIsCondition(RandomAccessIterator tokens) /// true if the expression is conditional, false otherwise. public bool ExpressionIsCondition(RandomAccessIterator tokens, bool restoreIterator) { + var firstIndex = tokens.Index; bool booleanNeeded = true; - bool comparerFound = false, comparerNeeded = false; + if (tokens.Current?.Type == TokenType.Open) + { + tokens.MoveNext(); + booleanNeeded = !ExpressionIsCondition(tokens, false); + } Token token = tokens.GetNext(); - var firstIndex = tokens.Index; - if (!Token.IsTerminal(token) && Token.IsTerminal(tokens.PeekNext())) + if ((!booleanNeeded && (Token.IsTerminal(token) || token.Type == TokenType.Ternary)) || + (!Token.IsTerminal(token) && token.ValueType == ValueType.Boolean && (Token.IsTerminal(tokens.PeekNext()) || tokens.PeekNext().Type == TokenType.Ternary))) { - booleanNeeded = !_constants.TryGetValue(token.Name, out var val); + if (restoreIterator) + tokens.SetIndex(firstIndex); + return true; } - else + bool comparerFound = false, comparerNeeded = false; + while (!Token.IsTerminal(token) && token.Type != TokenType.Ternary) { - while (!Token.IsTerminal(token)) + if (token.Type == TokenType.Operand && booleanNeeded && !comparerNeeded) { - if (token.Type == TokenType.Operand && booleanNeeded && !comparerNeeded) + if (token.ValueType == ValueType.Boolean) { - if (_constants.TryGetValue(token.Name, out var val)) - { - booleanNeeded = false; - } - else if (token.Name[0] == '_' || char.IsLetter(token.Name[0])) - { - var operands = new List { token }; - if (tokens.PeekNext() != null && tokens.PeekNext().Name.Equals("[")) - { - var ix = tokens.Index; - tokens.MoveNext(); - operands.AddRange(Token.GetGroup(tokens)); - tokens.SetIndex(ix + operands.Count - 1); - } - var eval = Evaluate(operands.GetIterator()); - if (eval == 0 || eval == 1) - booleanNeeded = false; - else - comparerNeeded = true; - } - else - { - comparerNeeded = true; - } + booleanNeeded = false; } - else if ((token.Type.HasFlag(TokenType.Unary) && token.Name.Equals("!")) || - token.Name.Equals("(")) + else if (token.Name[0] == '_' || char.IsLetter(token.Name[0])) { - var isSubCondition = ExpressionIsCondition(tokens, false); - if (booleanNeeded && !comparerNeeded && isSubCondition) + var operands = new List { token }; + if (tokens.PeekNext() != null && tokens.PeekNext().Name.Equals("[")) + { + var ix = tokens.Index; + tokens.MoveNext(); + operands.AddRange(Token.GetGroup(tokens)); + tokens.SetIndex(ix + operands.Count - 1); + } + var eval = Evaluate(operands.GetIterator()); + if (eval == 0 || eval == 1) booleanNeeded = false; else comparerNeeded = true; } - else if (token.Type == TokenType.Radix || token.Type == TokenType.Unary || token.Type == TokenType.Function) + else { comparerNeeded = true; - if (token.Type == TokenType.Radix || token.Type == TokenType.Function) - { - tokens.MoveNext(); - if (token.Type == TokenType.Function) - _ = Token.GetGroup(tokens); - } } - else if (token.Type == TokenType.Binary) + } + else if ((token.Type.HasFlag(TokenType.Unary) && token.Name.Equals("!")) || + token.Name.Equals("(")) + { + var isSubCondition = ExpressionIsCondition(tokens, false); + if (booleanNeeded && !comparerNeeded && isSubCondition) + booleanNeeded = false; + else + comparerNeeded = true; + } + else if (token.Type == TokenType.Radix || token.Type == TokenType.Unary || token.Type == TokenType.Function) + { + comparerNeeded = true; + if (token.Type == TokenType.Radix || token.Type == TokenType.Function) { - if (s_conditionals.Contains(token.Name)) - comparerFound = true; - else - comparerNeeded = true; + tokens.MoveNext(); + if (token.Type == TokenType.Function) + _ = Token.GetGroup(tokens); } - token = tokens.GetNext(); } + else if (token.Type == TokenType.Binary) + { + if (s_conditionals.Contains(token.Name)) + comparerFound = true; + else + comparerNeeded = true; + } + token = tokens.GetNext(); } if (restoreIterator) tokens.SetIndex(firstIndex); @@ -292,30 +317,25 @@ Stack EvaluateInternal(RandomAccessIterator iterator, bool fromGr var operators = new Stack(); var lastType = TokenType.None; StringView lastToken = ""; - StringView nonBase10 = ""; + var index = iterator.Index; // in case we need to backtrack Token token; var end = TokenType.Terminal; while ((token = iterator.GetNext()) != null && !(token.Type == TokenType.Closed || (!fromGroup && end.HasFlag(token.Type)))) { - if (token.Type == TokenType.Operand) + if (token.Type == TokenType.Operand || token.Type == TokenType.Radix) { - if (lastType == TokenType.Radix) + if (token.Type == TokenType.Operand && token.ValueType == ValueType.Unknown) { - if (!nonBase10.Equals("")) - throw new ExpressionException(token, "Unexpected token."); - nonBase10 = token.Name; + results.Push(UnknownValueEvaluator(iterator)); + continue; } - else if (iterator.PeekNext() != null && iterator.PeekNext().Name.Equals("[")) + if (token.Type == TokenType.Radix) { - var value = SymbolEvaluator(iterator); - if (double.IsNegativeInfinity(value)) - throw new ExpressionException(token, "Index is out of range."); - results.Push(value); - } - else - { - results.Push(Evaluate(token, double.MinValue, double.MaxValue)); + if (Token.IsTerminal(iterator.PeekNext()) || iterator.PeekNext().ValueType == ValueType.Unknown) + throw new ExpressionException(token, "Expected value."); + token = iterator.GetNext(); } + results.Push((double)token.Value); } else { @@ -346,7 +366,22 @@ Stack EvaluateInternal(RandomAccessIterator iterator, bool fromGr throw new SyntaxException(token, "Unexpected expression."); if (!TokenType.Evaluation.HasFlag(token.Type)) return new Stack(); - if (operators.Count > 0) + if (token.Type == TokenType.Ternary && token.Name.Equals("?")) + { + if (iterator.PeekNext() == null) + throw new SyntaxException(token, "Invalid ternary expression."); + var iterCopy = new RandomAccessIterator(iterator, index); + if (!ExpressionIsCondition(iterCopy)) + throw new SyntaxException(token, "Invalid ternary expression."); + while (operators.Count > 0) + DoOperation(operators.Pop(), results); + var subExpressions = EvaluateInternal(iterator, false); + foreach (var subEx in subExpressions) + results.Push(subEx); + iterator.Rewind(iterator.Index - 1); + DoOperation(token, results); + } + else if (operators.Count > 0) { var top = operators.Peek(); if (top.Name.Equals("!") && !token.Name.Equals("||") && !token.Name.Equals("&&")) @@ -355,65 +390,54 @@ Stack EvaluateInternal(RandomAccessIterator iterator, bool fromGr while ((top.Type == TokenType.Function || s_operators[top].Item2 >= opOrder) && operators.Count > 0) { operators.Pop(); - DoOperation(top, ref nonBase10, results); + DoOperation(top, results); if (operators.Count > 0) top = operators.Peek(); } } - if (token.Type != TokenType.Separator) + if (token.Type == TokenType.Separator) + index = iterator.Index; + else if (token.Type != TokenType.Ternary) operators.Push(token); } } - } + } while (operators.Count > 0) - DoOperation(operators.Pop(), ref nonBase10, results); + DoOperation(operators.Pop(), results); return results; } - void DoOperation(Token op, ref StringView nonBase10, Stack output) + void DoOperation(Token op, Stack output) { - if (nonBase10.Length > 0) + OperationDef operation; + var parms = new List(); + var parmCount = 1; + if (op.Type == TokenType.Function) { - try - { - output.Push(s_radixOperators[op.Name](nonBase10)); - nonBase10 = ""; - } - catch - { - throw new SyntaxException(op, $"\"{op.Name}{nonBase10}\" is not a valid expression."); - } + operation = _functions[op.Name]; + parmCount = operation.Item2; } else { - OperationDef operation; - var parms = new List(); - var parmCount = 1; - if (op.Type == TokenType.Function) - { - operation = _functions[op.Name]; - parmCount = operation.Item2; - } - else + operation = s_operators[op]; + if (op.Type == TokenType.Binary) + parmCount++; + else if (op.Type == TokenType.Ternary) + parmCount += 2; + } + while (--parmCount >= 0) + { + if (output.Count == 0) { - operation = s_operators[op]; - if (op.Type == TokenType.Binary) - parmCount++; + var opType = op.Type == TokenType.Function ? "function" : "operator"; + throw new SyntaxException(op, $"Missing arguments for {opType} \"{op}\"."); } - while (parmCount-- >= parms.Count) - { - if (output.Count == 0) - { - var opType = op.Type == TokenType.Function ? "function" : "operator"; - throw new SyntaxException(op, $"Missing operand argument for {opType} \"{op}\"."); - } - parms.Add(output.Pop()); - } - if ((op.Name.Equals("||") || op.Name.Equals("&&") || op.Name.Equals("!")) && parms.Any(p => p != 1 && p != 0)) - throw new ExpressionException(op, "Invalid conditional expression."); - output.Push(operation.Item1(parms)); + parms.Add(output.Pop()); } + if ((op.Name.Equals("||") || op.Name.Equals("&&") || op.Name.Equals("!")) && parms.Any(p => p != 1 && p != 0)) + throw new ExpressionException(op, "Invalid conditional expression."); + output.Push(operation.Item1(parms)); } double DoEvaluation(RandomAccessIterator tokens, bool advanceIterator, double minValue, double maxValue, bool isMath) @@ -443,10 +467,7 @@ void DoOperation(Token op, ref StringView nonBase10, Stack output) if (!isMath) { - var ienumtokens = new RandomAccessIterator(tokens, true) - .Skip(ix + 1) - .Take(tokens.Index - ix - 1); - if (!ExpressionIsCondition(ienumtokens.GetIterator())) + if (!ExpressionIsCondition(new RandomAccessIterator(tokens, ix))) throw new SyntaxException(first, "Invalid conditional expression."); } return r; @@ -510,74 +531,92 @@ public double Evaluate(Token token) /// - /// Evaluates a parsed tokens as a mathematical expression. + /// Get the value from the given or pair of values. /// - /// The parsed token representing the expression. - /// The minimum value allowed in the evaluation. - /// The maximum value allowed in the evaluation. - /// Allow symbol evaluation to occur if - /// the token is not successfully parsed. - /// The result of the evaluation. - /// - /// - /// - /// - public double Evaluate(Token token, double minValue, double maxValue) + /// A radix expression preceding the name. + /// The name. + /// A comparer. + /// A tuple containing the value type (if known) and the boxed value. + public static (ValueType valueType, double value) GetValue(StringView radix, StringView name, StringViewComparer comparer) { - if (!double.TryParse(RemoveSeparators(token.Name.ToString()), out var converted)) + if (radix != null) + return (ValueType.Binary, s_radixOperators[radix](name)); + if (!double.TryParse(name.ToString(), AsmNumberStyle, s_info, out var converted)) { - if (token.Name[0] == '0' && token.Name.Length > 2) + if (name[0] == '0' && name.Length > 2) { // try to convert non-base 10 literals in 0x/0b/0o notation. try { - if (token.Name[1] == 'b' || token.Name[1] == 'B') - converted = Convert.ToInt64(GetBinaryString(token.Name.Substring(2)), 2); - else if (token.Name[1] == 'o' || token.Name[1] == 'O') - converted = Convert.ToInt64(token.Name.Substring(2).Replace("_", string.Empty), 8); - else if (token.Name[1] == 'x' || token.Name[1] == 'X') - converted = Convert.ToInt64(token.ToString().Replace("_", string.Empty), 16); + if (name[1] == 'b' || name[1] == 'B') + converted = Convert.ToInt64(GetBinaryString(name.Substring(2)), 2); + else if (name[1] == 'o' || name[1] == 'O') + converted = Convert.ToInt64(name.Substring(2).Replace("_", string.Empty), 8); + else if (name[1] == 'x' || name[1] == 'X') + converted = Convert.ToInt64(name.Substring(2).Replace("_", string.Empty), 16); else - throw new SyntaxException(token, "Not a valid numeric constant."); + throw new ArgumentException("Not a valid numeric constant."); + return (ValueType.Binary, converted); } catch { - throw new SyntaxException(token, "Not a valid numeric constant."); + throw new ArgumentException("Not a valid numeric constant."); } } - else if (!_constants.TryGetValue(token.Name, out converted)) - { - var tokens = new List { token }; - var it = tokens.GetIterator(); - it.MoveNext(); - converted = SymbolEvaluator(it); - } + if (name.Equals("false", comparer) || name.Equals("true", comparer)) + return (ValueType.Boolean, name.Equals("true", comparer) ? 1 : 0); + return (ValueType.Unknown, double.NaN); } - else if (token.Name[0] == '0' && - token.Name.Length > 1 && - (token.Name[1] >= '0' && token.Name[1] <= '7') || - (token.Name.Length > 3 && token.Name[1] == '_' && token.Name[2] >= '0' && token.Name[2] <= '7')) + if (name[0] == '0' && + name.Length > 1 && + (name[1] >= '0' && name[1] <= '7') || + (name.Length > 3 && name[1] == '_' && name[2] >= '0' && name[2] <= '7')) { // all leading zeros treated as octal try { - converted = Convert.ToInt64(RemoveSeparators(token.ToString()), 8); + return (ValueType.Boolean, Convert.ToInt64(name.ToString().Replace("_", string.Empty), 8)); } catch { - throw new SyntaxException(token, "Not a valid numeric constant."); + throw new ArgumentException("Not a valid numeric constant."); } } - if (converted < minValue || converted > maxValue) - throw new IllegalQuantityException(token, converted); - return converted; + if (converted.IsInteger()) + return (ValueType.Integer, converted); + return (ValueType.Double, converted); } - static string RemoveSeparators(string number) + /// + /// Evaluates a parsed tokens as a mathematical expression. + /// + /// The parsed token representing the expression. + /// The minimum value allowed in the evaluation. + /// The maximum value allowed in the evaluation. + /// Allow symbol evaluation to occur if + /// the token is not successfully parsed. + /// The result of the evaluation. + /// + /// + /// + /// + public double Evaluate(Token token, double minValue, double maxValue) { - if (s_separators.IsMatch(number)) - return number.Replace("_", string.Empty); - return number; + double converted; + if (token.ValueType == ValueType.Unknown) + { + var tokens = new List { token }; + var it = tokens.GetIterator(); + it.MoveNext(); + converted = UnknownValueEvaluator(it); + } + else + { + converted = (double)token.Value; + } + if (converted < minValue || converted > maxValue) + throw new IllegalQuantityException(token, converted); + return converted; } /// @@ -672,7 +711,7 @@ public static string GetBinaryString(string binary) /// /// An evaluator of symbols the evaluator does not recognize /// - public Func, double> SymbolEvaluator { get; set; } + public Func, double> UnknownValueEvaluator { get; set; } /// /// Add a custom function evaluator to the evaluator. diff --git a/Core6502DotNet/src/Core/StringView.cs b/Core6502DotNet/src/Core/StringView.cs index bab1c20..3f17e4e 100644 --- a/Core6502DotNet/src/Core/StringView.cs +++ b/Core6502DotNet/src/Core/StringView.cs @@ -49,6 +49,7 @@ public StringView(string str) } + /// /// Constructs a new instance of a string view. /// @@ -61,7 +62,7 @@ public StringView(string str, int position, int length) String = str; Position = position; Length = length; - _endIndex = Position + Length; + _endIndex = Position + Length; _enumerator = -1; } diff --git a/Core6502DotNet/src/Core/Symbol.cs b/Core6502DotNet/src/Core/Symbol.cs index c5e1d47..4543577 100644 --- a/Core6502DotNet/src/Core/Symbol.cs +++ b/Core6502DotNet/src/Core/Symbol.cs @@ -177,6 +177,100 @@ public Symbol(RandomAccessIterator tokens, Evaluator eval) #region Methods + public enum AccessResult + { + Success, + InvalidType, + SubscriptOutOfRange, + SubscriptNotIntegral + } + + /// + /// Get the element at the specified index in the string array symbol. + /// + /// The index subscript. + /// The string to get. + /// The value. + public AccessResult TryGetElementAt(double subscript, out StringView str) + { + str = null; + var result = Validate(subscript, true); + if (result == AccessResult.Success) + str = StringVector[(int)subscript]; + return result; + } + /// + /// Update the element at the specified index of the numeric array symbol. + /// + /// The index subscript. + /// The updated value. + /// The value. + public AccessResult TryUpdateElementAt(double subscript, double val) + { + var result = Validate(subscript, false); + if (result == AccessResult.Success) + NumericVector[(int)subscript] = val; + return result; + } + + /// + /// Update the element at the specified index of the string array symbol. + /// + /// The index subscript. + /// The updated string vaule. + /// The value. + public AccessResult TryUpdateElementAt(double subscript, StringView str) + { + var result = Validate(subscript, false); + if (result == AccessResult.Success) + StringVector[(int)subscript] = str; + return result; + } + + AccessResult Validate(double subscript, bool forString) + { + var result = AccessResult.SubscriptNotIntegral; + if (subscript.IsInteger()) + { + result = AccessResult.InvalidType; + if (StorageType == StorageType.Vector || (forString && DataType == DataType.String && StorageType == StorageType.Scalar)) + { + result = AccessResult.SubscriptOutOfRange; + int upperBounds; + if (StorageType == StorageType.Vector) + upperBounds = DataType == DataType.String ? StringVector.Count : NumericVector.Count; + else + upperBounds = StringValue.Length; + var index = (int)subscript; + if (index >= 0 && index < upperBounds) + return AccessResult.Success; + } + } + return result; + } + + /// + /// Get the element at the specified index in the numeric array if the symbol is a number, or the + /// at the specified index in the string if the symbol is a string. + /// + /// The index subscript. + /// The value to fetch. + /// The value. + public AccessResult TryGetElementAt(double subscript, out double val) + { + val = double.NaN; + var result = Validate(subscript, DataType == DataType.String); + if (result == AccessResult.Success) + { + var index = (int)subscript; + if (DataType == DataType.String) + val = StringValue[index]; + else + val = NumericVector[index]; + } + return result; + } + /// /// Determine of the symbol is equal in type (both data and storage). /// diff --git a/Core6502DotNet/src/Core/SymbolManager.cs b/Core6502DotNet/src/Core/SymbolManager.cs index e67c229..0d7455a 100644 --- a/Core6502DotNet/src/Core/SymbolManager.cs +++ b/Core6502DotNet/src/Core/SymbolManager.cs @@ -376,6 +376,41 @@ public bool SymbolExists(StringView name, bool searchUp) return null; } + public static string GetErrorMessageFromGetResult(Symbol.AccessResult result) + { + return result switch + { + Symbol.AccessResult.SubscriptNotIntegral => "Subscript was not an integer.", + Symbol.AccessResult.SubscriptOutOfRange => "Index out of range.", + _ => "Type mismatch." + }; + } + + public Symbol GetSymbol(RandomAccessIterator expression, bool raiseExceptionIfNotFound) + { + var searchnotFound = SearchedNotFound; + if (!expression.MoveNext()) + throw new ArgumentException("Cannot evaluate the expression."); + var symbolToken = expression.Current; + if (symbolToken.Type == TokenType.Operand) + { + var fqdn = GetFullyQualifiedName(symbolToken.Name); + if (_symbolTable.TryGetValue(fqdn, out var sym)) + { + SearchedNotFound = searchnotFound; + if (sym == null) + throw new SymbolException(symbolToken, SymbolException.ExceptionReason.IllegalReference); + if (expression.MoveNext() && expression.Current.Name.Equals("[")) + { + var subscript = _evaluator.Evaluate(expression, 0, int.MaxValue); + } + } + } + + return null; + + } + /// /// Define a symbol as representing the current state of the program counter. /// @@ -418,7 +453,7 @@ void DefineFromExpression(RandomAccessIterator tokens, bool isGlobal, boo sym = null; } var isIndexed = false; - var subscript = -1; + double subscript = -1; var expectedStorageType = StorageType.Scalar; var assign = tokens.GetNext(); if (sym != null) @@ -426,7 +461,7 @@ void DefineFromExpression(RandomAccessIterator tokens, bool isGlobal, boo if (assign?.Name.Equals("[") == true) { isIndexed = true; - subscript = (int)_evaluator.Evaluate(tokens, 0, sym.Length); + subscript = _evaluator.Evaluate(tokens, 0, sym.Length); assign = tokens.GetNext(); } else @@ -436,7 +471,7 @@ void DefineFromExpression(RandomAccessIterator tokens, bool isGlobal, boo expectedStorageType = sym.StorageType; } } - if (Token.IsTerminal(assign) || !(assign.Name.Equals("=") || assign.Name.Equals(":=") || assign.IsCompoundAssignment())) + if (Token.IsTerminal(assign) || !(assign.Name.Equals("=") || assign.Name.Equals(":=") || assign.IsCompoundAssignment)) throw new SyntaxException(assign ?? lhs, "Assignment operator expected."); var rhsIndex = tokens.Index; @@ -444,10 +479,11 @@ void DefineFromExpression(RandomAccessIterator tokens, bool isGlobal, boo if (Token.IsTerminal(rhs)) throw new SyntaxException(assign, "rhs expression is missing."); var rhsNext = tokens.PeekNext(); - var rhsSubscript = -1; + double rhsSubscript = -1; var storageType = StorageType.Scalar; DataType dataType; Symbol rhsSym; + var gettingStrChar = false; if (rhs.Name.Equals("[")) { if (isIndexed) @@ -464,35 +500,30 @@ void DefineFromExpression(RandomAccessIterator tokens, bool isGlobal, boo rhsSym = SymbolExists(rhs.Name, true) ? GetSymbol(rhs, false) : null; if (rhsNext?.Name.Equals("[") == true) { - if (rhsSym?.StorageType != StorageType.Vector) - throw new SyntaxException(rhsNext, "Subscript operation invalid."); - if (Token.IsTerminal(tokens.GetNext())) - throw new SyntaxException(rhsNext, "Index expression expected for subscript."); - rhsSubscript = (int)_evaluator.Evaluate(tokens, 0, rhsSym.Length); + gettingStrChar = rhsSym?.StorageType == StorageType.Scalar && rhsSym?.DataType == DataType.String; + tokens.MoveNext(); + rhsSubscript = _evaluator.Evaluate(tokens, 0, rhsSym.Length); rhsNext = tokens.GetNext(); } else if (rhsSym != null) { - if (Token.IsTerminal(tokens.GetNext())) - { - storageType = rhsSym.StorageType; - if (sym == null) - expectedStorageType = storageType; - } - else if (rhsSym.StorageType == StorageType.Vector) // ex: .let x = some_array + 2 - { - throw new SyntaxException(rhsNext, "Unexpected expression."); - } + // ex: .let x = some_array + 2 + if (rhsSym.StorageType == StorageType.Vector && !Token.IsTerminal(tokens.GetNext())) + throw new SyntaxException(tokens.Current, "Unexpected expression."); + + storageType = rhsSym.StorageType; + if (sym == null) + expectedStorageType = storageType; } if (rhsSym != null && Token.IsTerminal(rhsNext)) - dataType = rhsSym.DataType; + dataType = gettingStrChar ? DataType.Numeric : rhsSym.DataType; else - dataType = !assign.IsCompoundAssignment() && rhs.IsDoubleQuote() && Token.IsTerminal(rhsNext) ? DataType.String : DataType.Numeric; + dataType = !assign.IsCompoundAssignment && rhs.IsDoubleQuote() && Token.IsTerminal(rhsNext) ? DataType.String : DataType.Numeric; } if (expectedStorageType != storageType || (sym != null && sym.DataType != dataType)) throw new SyntaxException(rhs, "Type mismatch."); - if (assign.IsCompoundAssignment() && (sym == null || !sym.IsMutable || expectedStorageType == StorageType.Vector)) + if (assign.IsCompoundAssignment && (sym == null || !sym.IsMutable || expectedStorageType == StorageType.Vector)) throw new SyntaxException(assign, "Invalid use of compound assignment operator."); if (expectedStorageType == StorageType.Vector) @@ -506,13 +537,13 @@ void DefineFromExpression(RandomAccessIterator tokens, bool isGlobal, boo { StringView stringValue = null; var numericValue = double.NaN; - if (rhsSym?.DataType == DataType.String && Token.IsTerminal(rhsNext)) - { - stringValue = rhsSubscript > -1 ? rhsSym.StringVector[rhsSubscript] : rhsSym.StringValue; - } - else if (rhsSym?.DataType == DataType.Numeric && Token.IsTerminal(rhsNext)) + if (Token.IsTerminal(rhsNext) && (rhsSym?.StorageType == StorageType.Vector || gettingStrChar)) { - numericValue = rhsSubscript > -1 ? rhsSym.NumericVector[rhsSubscript] : rhsSym.NumericValue; + var result = (rhsSym.DataType == DataType.Numeric || gettingStrChar) ? + rhsSym.TryGetElementAt(rhsSubscript, out numericValue) : + rhsSym.TryGetElementAt(rhsSubscript, out stringValue); + if (result != Symbol.AccessResult.Success) + throw new SyntaxException(rhs, GetErrorMessageFromGetResult(result)); } else if (dataType == DataType.String) { @@ -523,17 +554,32 @@ void DefineFromExpression(RandomAccessIterator tokens, bool isGlobal, boo // reset the token iterator back to the beginning of the rhs expression tokens.SetIndex(rhsIndex); numericValue = _evaluator.Evaluate(tokens); - if (assign.IsCompoundAssignment()) + if (assign.IsCompoundAssignment) { // break compound expression = into: // - var compoundExpression = new List(); + var symValue = sym.NumericValue; if (isIndexed) - compoundExpression.Add(new Token(sym.NumericVector[subscript].ToString(), TokenType.Operand)); - else - compoundExpression.Add(new Token(sym.NumericValue.ToString(), TokenType.Operand)); - compoundExpression.Add(new Token(assign.Name[0..^2], TokenType.Binary)); - compoundExpression.Add(new Token(numericValue.ToString(), TokenType.Operand)); + { + var result = sym.TryGetElementAt(subscript, out symValue); + if (result != Symbol.AccessResult.Success) + throw new SyntaxException(lhs, GetErrorMessageFromGetResult(result)); + } + var symToken = new Token(symValue.ToString(), TokenType.Operand) + { + Value = symValue + }; + var valueToken = new Token(numericValue.ToString(), TokenType.Operand) + { + Value = numericValue, + ValueType = symToken.ValueType = ValueType.Double + }; + var compoundExpression = new List + { + symToken, + new Token(assign.Name[0..^2], TokenType.Binary), + valueToken + }; numericValue = _evaluator.Evaluate(compoundExpression.GetIterator()); } } @@ -541,10 +587,11 @@ void DefineFromExpression(RandomAccessIterator tokens, bool isGlobal, boo { if (isIndexed) { - if (dataType == DataType.String) - sym.StringVector[subscript] = stringValue; - else - sym.NumericVector[subscript] = numericValue; + var result = dataType == DataType.Numeric ? + sym.TryUpdateElementAt(subscript, numericValue) : + sym.TryUpdateElementAt(subscript, stringValue); + if (result != Symbol.AccessResult.Success) + throw new SyntaxException(rhs, GetErrorMessageFromGetResult(result)); } else if (dataType == DataType.String) sym.StringValue = stringValue; @@ -771,7 +818,7 @@ public void Reset() /// List all labels, including non-addresses. /// The string listing. public string ListLabels(bool listAll) - { //ADD2PLYSTAT = 4783 // $12af + { var listBuilder = new StringBuilder( "/*************************************************************/\n" + "/* Symbol Value */\n"+ diff --git a/Core6502DotNet/src/Core/Token.cs b/Core6502DotNet/src/Core/Token.cs index bd5e070..7f65989 100644 --- a/Core6502DotNet/src/Core/Token.cs +++ b/Core6502DotNet/src/Core/Token.cs @@ -19,24 +19,25 @@ namespace Core6502DotNet public enum TokenType : uint { None = 0, - Start = 0b000000000001, - Open = 0b000000000010, - Operand = 0b000000000100, - Unary = 0b000000001000, - Radix = 0b000000010000, - StartOrOperand = 0b000000011111, - Closed = 0b000000100000, - Separator = 0b000001000000, - Terminal = 0b000001100000, - Binary = 0b000010000000, - Function = 0b000100000000, - EndOrBinary = 0b000111100000, - Instruction = 0b001000000000, - Label = 0b010000000000, - LabelInstr = 0b011000000000, - Misc = 0b100000000000, - MoreTokens = Binary | Separator | Open, - Evaluation = Binary | Separator | Function + Start = 0b0000000000001, + Open = 0b0000000000010, + Operand = 0b0000000000100, + Unary = 0b0000000001000, + Radix = 0b0000000010000, + StartOrOperand = 0b0000000011111, + Closed = 0b0000000100000, + Separator = 0b0000001000000, + Terminal = 0b0000001100000, + Ternary = 0b0000010000000, + Binary = 0b0000100000000, + Function = 0b0001000000000, + EndOrBinary = 0b0001111100000, + Instruction = 0b0010000000000, + Label = 0b0100000000000, + LabelInstr = 0b0110000000000, + Misc = 0b1000000000000, + MoreTokens = Binary | Ternary | Separator | Open, + Evaluation = Binary | Ternary | Separator | Function } /// @@ -47,16 +48,18 @@ public class Token : IEquatable, IComparable { #region Members - /// - /// Gets the key-value pairs of opens and closures. - /// - public static readonly Dictionary s_openClose = new Dictionary + static readonly Dictionary s_openClose = new Dictionary { { "(", ")" }, { "{", "}" }, { "[", "]" } }; + static readonly HashSet s_compoundAssignments = new HashSet + { + "+=","-=","*=","/=","%=",">>=","<<=","&=","^=","|=","^^=" + }; + #endregion #region Constructors @@ -80,6 +83,8 @@ public Token(StringView name, TokenType type, int position) Type = type; Name = name; Position = position; + IsCompoundAssignment = s_compoundAssignments.Contains(name); + IsAssignment = Name?[^1] == '=' && (Name.Length == 1 || Name[0] == ':' || IsCompoundAssignment); } /// @@ -88,11 +93,7 @@ public Token(StringView name, TokenType type, int position) /// The token's (parsed) name. /// The token's . public Token(StringView name, TokenType type) - { - Name = name; - Type = type; - Position = 1; - } + : this(name, type, 1) { } #endregion @@ -128,23 +129,6 @@ public Token(StringView name, TokenType type) /// true if the token is an opening, false otherwise. public bool IsOpen() => Type.HasFlag(TokenType.Open); - /// - /// Determines whether the token represents an assignment operator. - /// - /// true if the token name is an assignment operator, - /// false otherwise. - public bool IsAssignment() - => Name?[^1] == '=' && (Name.Length == 1 || Name[0] == ':' || IsCompoundAssignment()); - - /// - /// Determines whether the token represents a compound assignment operator. - /// - /// true if the token name is a compound assignment operator, - /// false otherwise. - public bool IsCompoundAssignment() - => Name.Length > 1 && Name[^1] == '=' && ",+=,-=,*=,/=,%=,>>=,<<=,&=,^=,|=".Contains($",{Name}"); - - /// /// Indicates whether the current token name is equal to the given string. /// @@ -234,9 +218,10 @@ internal static void GatherIntoExpressions(List tokens) var brackets = 0; foreach (var token in tokens) { - if ((!IsTerminal(token) && !token.IsAssignment() && + if ((!IsTerminal(token) && !token.IsAssignment && (token.Type != TokenType.Open || lastToken?.Type != TokenType.Closed))|| + token.Type == TokenType.Ternary || inFunction || token.Type == TokenType.Closed) { @@ -248,7 +233,7 @@ internal static void GatherIntoExpressions(List tokens) if (--brackets == 0) inFunction = false; } - if (lastToken != null && !lastToken.IsAssignment()) + if (lastToken != null && !lastToken.IsAssignment) lastToken.NextInExpression = token; if (firstToken == null) firstToken = token; @@ -280,14 +265,15 @@ public static string GetExpression(Token token, bool startAtToken = false, bool return token.Name.ToString(); if (token.FirstInExpression != null && !startAtToken) token = token.FirstInExpression; - var len = token.Name.Length; + var len = 0; var current = token.NextInExpression; var previous = token; + var firstInLine = token; var lineSourceIndex = token.LineSourceIndex; var startOffset = token.Position - 1; + var sourceLines = token.Line.FullSource.Split('\n'); if (startAtToken && lineSourceIndex > 0) { - var sourceLines = token.Line.FullSource.Split('\n'); for (var i = 0; i < lineSourceIndex; i++) startOffset += sourceLines[i].Length + 1; } @@ -297,17 +283,14 @@ public static string GetExpression(Token token, bool startAtToken = false, bool { if (toNewLine) break; - len += current.Position; + len += sourceLines[lineSourceIndex].Length - firstInLine.Position + current.Position + 1; + firstInLine = current; lineSourceIndex++; } - else - { - len += current.Position - (previous.Position + previous.Name.Length); - } - len += current.Name.Length; previous = current; current = current.NextInExpression; } + len += previous.Position - firstInLine.Position + previous.Name.Length; return token.Line.FullSource.Substring(startOffset, len); } @@ -322,6 +305,10 @@ public static string GetExpression(Token token, bool startAtToken = false, bool /// public TokenType Type { get; } + public ValueType ValueType { get; set; } + + public object Value { get; set; } + /// /// Gets the token's position in its source line. /// @@ -336,7 +323,16 @@ public static string GetExpression(Token token, bool startAtToken = false, bool /// Gets or sets the token's parsed in which it appears. /// public SourceLine Line { get; internal set; } - + + /// + /// Gets whether the token represents an assignment operator. + /// + public bool IsAssignment { get; } + + /// + /// Gets whether the token represents a compound assignment operator. + /// + public bool IsCompoundAssignment { get; } /// /// Gets or sets the source index of the line in which the token appears. diff --git a/Core6502DotNet/src/Line Assemblers/AssignmentAssembler.cs b/Core6502DotNet/src/Line Assemblers/AssignmentAssembler.cs index a5a7398..996fae1 100644 --- a/Core6502DotNet/src/Line Assemblers/AssignmentAssembler.cs +++ b/Core6502DotNet/src/Line Assemblers/AssignmentAssembler.cs @@ -5,6 +5,7 @@ // //----------------------------------------------------------------------------- +using System; using System.Collections.Generic; using System.Linq; @@ -155,7 +156,6 @@ protected override string OnAssemble(RandomAccessIterator lines) Services.Output.SynchPC(); break; } - SetLabel(line); } if (iterator?.Current != null) throw new SyntaxException(iterator.Current, "Unexpected expression."); @@ -168,6 +168,26 @@ protected override string OnAssemble(RandomAccessIterator lines) { return $".{Services.Output.LogicalPC,-41:x4}{unparsedSource}"; } + var valueTokenIndex = instruction.Equals(".let") ? 2 : 0; + if ((line.Operands.Count - valueTokenIndex) == 1) + { + var valueToken = line.Operands[valueTokenIndex]; + if (valueToken.ValueType != ValueType.Unknown) + { + var value = valueToken.Value; + return line.Operands[valueTokenIndex].ValueType switch + { + ValueType.Binary => $"=${Convert.ToInt64(value),-41:x}{unparsedSource}", + ValueType.Boolean => $"={((double)value == 0 ? "false" : "true"),-42}{unparsedSource}", + _ => $"={(double)value,-41}{unparsedSource}" + }; + } + if (valueToken.IsQuote()) + { + var value = valueToken.ToString().Elliptical(38); + return $"={value,-42}{unparsedSource}"; + } + } var tokenSymbol = instruction.Equals(".let") ? line.Operands[0] : line.Label; var symbol = Services.SymbolManager.GetSymbol(tokenSymbol, false); if (symbol != null && symbol.StorageType == StorageType.Scalar) @@ -181,7 +201,8 @@ protected override string OnAssemble(RandomAccessIterator lines) condition = Services.Evaluator.ExpressionIsCondition(line.Operands.GetIterator()); if (condition) return $"={(symbol.NumericValue == 0 ? "false" : "true"),-42}{unparsedSource}"; - if (symbol.NumericValue.IsInteger()) + if (symbol.DataType == DataType.Address || + line.Operands.Any(t => t.Type == TokenType.Operand && t.IsSpecialOperator())) return $"=${(int)symbol.NumericValue,-41:x}{unparsedSource}"; return $"={symbol.NumericValue,-41}{unparsedSource}"; } @@ -192,17 +213,6 @@ protected override string OnAssemble(RandomAccessIterator lines) return string.Empty; } - void SetLabel(SourceLine line) - { - if (line.Label != null && !line.Label.Name.Equals("*")) - { - if (line.Label.IsSpecialOperator()) - Services.SymbolManager.DefineLineReference(line.Label, Services.Output.LogicalPC); - else - DefineLabel(line.Label, Services.Output.LogicalPC, true); - } - } - public override bool AssemblesLine(SourceLine line) => line.Instruction != null && (Reserved.IsOneOf("Assignments", line.Instruction.Name) || @@ -232,22 +242,24 @@ public double EvaluateFunction(RandomAccessIterator tokens) if (!param.Name.Equals(")")) { param = tokens.GetNext(); - int subscript = -1; + double subscript = -1; if (param.Name.Equals("[")) - subscript = (int)Services.Evaluator.Evaluate(tokens, 0, int.MaxValue); + subscript = Services.Evaluator.Evaluate(tokens, 0, int.MaxValue); if (subscript < 0 || !tokens.PeekNext().Equals(")")) - throw new SyntaxException(param.Position, "Unexpected argument."); + throw new SyntaxException(param, "Unexpected argument."); + if (!subscript.IsInteger()) + throw new ExpressionException(param, "Subscript must be an integer."); if (symbolLookup.StorageType != StorageType.Vector) - throw new SyntaxException(param.Position, "Type mismatch."); + throw new SyntaxException(param, "Type mismatch."); if (symbolLookup.DataType == DataType.String) { if (subscript >= symbolLookup.StringVector.Count) throw new SyntaxException(param.Position, "Index out of range."); - return symbolLookup.StringVector[subscript].Length; + return symbolLookup.StringVector[(int)subscript].Length; } if (subscript >= symbolLookup.NumericVector.Count) throw new SyntaxException(param.Position, "Index out of range."); - return symbolLookup.NumericVector[subscript].Size(); + return symbolLookup.NumericVector[(int)subscript].Size(); } return symbolLookup.Length; } diff --git a/Core6502DotNet/src/Preprocessing/Preprocessor.cs b/Core6502DotNet/src/Preprocessing/Preprocessor.cs index 984db36..0c3f67a 100644 --- a/Core6502DotNet/src/Preprocessing/Preprocessor.cs +++ b/Core6502DotNet/src/Preprocessing/Preprocessor.cs @@ -290,6 +290,7 @@ List ProcessFromStream(string fileName, int lineNumber, StreamReader var expected = TokenType.LabelInstr; var previousType = TokenType.None; var opens = new Stack(); + var ternaryExpressions = 0; Macro definingMacro = null; string nextLine, lineSource; blockComment = stopProcessing = previousWasPlus = false; readyForNewLine = true; @@ -354,7 +355,7 @@ List ProcessFromStream(string fileName, int lineNumber, StreamReader if (c == EOS) break; } - if (c == ':' && it.PeekNext() != '=') + if (c == ':' && it.PeekNext() != '=' && ternaryExpressions == 0) { if ((expected != TokenType.Instruction && expected != TokenType.EndOrBinary && (tokens.Count == 0 || tokens[^1].Type != TokenType.Instruction)) || !LineTerminates()) @@ -546,6 +547,13 @@ List ProcessFromStream(string fileName, int lineNumber, StreamReader it.MoveNext(); peek = it.PeekNext(); size++; + if (c == '<' && peek == '>') + { + // spaceship operator + it.MoveNext(); + peek = it.PeekNext(); + size++; + } } c = it.Current; } @@ -566,7 +574,7 @@ List ProcessFromStream(string fileName, int lineNumber, StreamReader type = TokenType.Label; expected = TokenType.Instruction; if (c != '*' && position > 0 && _options.WarnOnLabelLeft) - _options.Log?.LogEntry(fileName, lineNumber, position + 1, "Label is not at the beginning of the line.", nextLine.Substring(0, position), nextLine, false); + _options.Log?.LogEntry(fileName, lineNumber, position + 1, "Label is not at the beginning of the line.", nextLine.Substring(0, position), nextLine, false); } } else if (c == '\\' && definingMacro != null) @@ -616,6 +624,10 @@ List ProcessFromStream(string fileName, int lineNumber, StreamReader { expected = TokenType.EndOrBinary; } + if (previousType == TokenType.Ternary && ternaryExpressions > 0 && tokens[^1].Name.Equals(":")) + { + ternaryExpressions--; + } } else if (c == '*' || c == '?') { @@ -692,11 +704,22 @@ List ProcessFromStream(string fileName, int lineNumber, StreamReader isWidth = false; } } + else if (c == '?') + { + ternaryExpressions++; + type = TokenType.Ternary; + expected = TokenType.StartOrOperand; + } + else if (c == ':' && ternaryExpressions > 0) + { + type = TokenType.Ternary; + expected = TokenType.StartOrOperand; + } else if (TokenType.MoreTokens.HasFlag(type)) { previousWasPlus = type == TokenType.Binary && c == '+'; if (type == TokenType.Open && c != '[') - LogError(fileName, lineNumber, position + 1, "Unexpected token.", it.Index + 1, lineSource); + LogError(fileName, lineNumber, position + 1, "Unexpected token.", it.Index + 1, lineSource); else expected = TokenType.StartOrOperand; } @@ -712,11 +735,25 @@ List ProcessFromStream(string fileName, int lineNumber, StreamReader break; } - previous = it.Current; - previousType = type; var token = new Token(new StringView(nextLine, position, size), type, position + 1); token.LineSourceIndex = lineSources.Count - 1; + if (token.Type == TokenType.Operand && !token.IsSpecialOperator()) + { + try + { + var radix = previousType == TokenType.Radix ? tokens[^1].Name : null; + var (valueType, value) = Evaluator.GetValue(radix, token.Name, _options.CaseSensitive ? StringViewComparer.Ordinal : StringViewComparer.IgnoreCase); + token.ValueType = valueType; + token.Value = value; + } + catch (Exception ex) + { + LogError(fileName, lineNumber, token.Position, ex.Message, token.Name.ToString(), lineSource); + } + } tokens.Add(token); + previous = it.Current; + previousType = type; } } if (blockComment && tokens.Count == 0) diff --git a/Core6502DotNet/src/Utility/Json/JsonValidator.cs b/Core6502DotNet/src/Utility/Json/JsonValidator.cs index dd08e81..c4f3cb3 100644 --- a/Core6502DotNet/src/Utility/Json/JsonValidator.cs +++ b/Core6502DotNet/src/Utility/Json/JsonValidator.cs @@ -102,7 +102,7 @@ AnnotationCollection ValidateInstance(Schema schema, JToken token) default: if (token.Type == JTokenType.String) annotations.AddAnnotations(ValidateString(schema, token)); - else + else if (token.Type == JTokenType.Integer || token.Type == JTokenType.Float) annotations.AddAnnotations(ValidateNumber(schema, token)); annotations.AddAnnotations(ValidateInSubschemas(schema, token, AnnotationAddType.None)); break; @@ -114,7 +114,7 @@ AnnotationCollection ValidateInstance(Schema schema, JToken token) static AnnotationCollection ValidateNumber(Schema schema, JToken token) { var annotations = new AnnotationCollection(); - var value = (long)token; + var value = (double)token; if (!(!schema.ExclusiveMinimum.HasValue || value > schema.ExclusiveMinimum)) annotations.AddError($"{value} is not greater than {schema.ExclusiveMinimum}.", schema, "exclusiveMinimum", token); @@ -201,7 +201,7 @@ AnnotationCollection ValidateObject(Schema schema, JToken token) annotations.AddAnnotations(dependencyCollection, AnnotationAddType.ErrorsAndProperties); } if (schema.PropertyNames != null) - annotations.AddAnnotations(ValidateInstance(schema.PropertyNames, JToken.Parse($"\"{prop}\""))); + annotations.AddAnnotations(ValidateInstance(schema.PropertyNames, JToken.FromObject(prop))); var jProp = jObject[prop]; if (schema.Properties?.ContainsKey(prop) == true) { @@ -426,9 +426,9 @@ AnnotationCollection ValidateInSubschemas(Schema schema, JToken token, Annotatio if (schema.If != null) { var ifCollection = ValidateInstance(schema.If, token); - annotations.AddAnnotations(ifCollection, ifCollection.Valid ? addType : AnnotationAddType.Errors); if (ifCollection.Valid) { + annotations.AddAnnotations(ifCollection, addType); if (schema.Then != null) annotations.AddAnnotations(ValidateInstance(schema.Then, token), addType | AnnotationAddType.Errors); } @@ -436,6 +436,10 @@ AnnotationCollection ValidateInSubschemas(Schema schema, JToken token, Annotatio { annotations.AddAnnotations(ValidateInstance(schema.Else, token), addType | AnnotationAddType.Errors); } + else + { + annotations.AddAnnotations(ifCollection, addType | AnnotationAddType.Errors); + } } if (!string.IsNullOrEmpty(schema.Ref)) { diff --git a/Core6502DotNet/src/Utility/Json/Schema.cs b/Core6502DotNet/src/Utility/Json/Schema.cs index a64687d..ee9932d 100644 --- a/Core6502DotNet/src/Utility/Json/Schema.cs +++ b/Core6502DotNet/src/Utility/Json/Schema.cs @@ -108,7 +108,7 @@ static Schema() Maximum = GetProperty(obj, "maximum"); ExclusiveMaximum = GetProperty(obj, "exclusiveMaximum"); ExclusiveMinimum = GetProperty(obj, "exclusiveMinimum"); - MultipleOf = GetProperty(obj, "multipleOf"); + MultipleOf = GetProperty(obj, "multipleOf"); MinLength = GetProperty(obj, "minLength"); MaxLength = GetProperty(obj, "maxLength"); MinItems = GetProperty(obj, "minItems"); @@ -520,7 +520,7 @@ public string GetPath(bool absolute) /// /// Validates the instance number is a multiple of the given value. /// - public long? MultipleOf { get; } + public double? MultipleOf { get; } /// /// Validates the instance number is greater than or equal to the given value. diff --git a/Core6502DotNet/src/Utility/Json/SchemaBuilder.cs b/Core6502DotNet/src/Utility/Json/SchemaBuilder.cs index 96d9cf2..2e78ce4 100644 --- a/Core6502DotNet/src/Utility/Json/SchemaBuilder.cs +++ b/Core6502DotNet/src/Utility/Json/SchemaBuilder.cs @@ -274,6 +274,7 @@ KeywordMapper AllDraftsPost4() .Map("const", JTokenType.Raw); KeywordMapper AllDraftsPost3() => new KeywordMapper().Map("multipleOf", JTokenType.Integer) + .Map("multipleOf", JTokenType.Float) .Map("not", JTokenType.Object, MapToSchema) .Map("type", JTokenType.Array); diff --git a/Core6502DotNet/src/Utility/RandomAccessIterator.cs b/Core6502DotNet/src/Utility/RandomAccessIterator.cs index d685ca5..a4970d5 100644 --- a/Core6502DotNet/src/Utility/RandomAccessIterator.cs +++ b/Core6502DotNet/src/Utility/RandomAccessIterator.cs @@ -46,11 +46,11 @@ public RandomAccessIterator(IEnumerable collection) public RandomAccessIterator(IEnumerable collection, int firstIndex) { if (collection == null) - throw new ArgumentNullException(); + throw new ArgumentNullException(nameof(collection), "Argument cannot be null."); _list = collection.ToArray(); _length = _list.Length; if (_length > 0 && (firstIndex < -1 || firstIndex >= _length)) - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(firstIndex), "Index is out of range."); _firstIndex = firstIndex; Index = firstIndex; } @@ -62,13 +62,25 @@ public RandomAccessIterator(IEnumerable collection, int firstIndex) /// Reset the copied indicator. /// public RandomAccessIterator(RandomAccessIterator iterator, bool reset) + : this(iterator, reset ? iterator._firstIndex : iterator.Index) {} + + /// + /// Constructs a new instance of a class. + /// + /// An iterator from which to copy. + /// Point the copy to the desired index. + /// + /// + public RandomAccessIterator(RandomAccessIterator iterator, int index) { if (iterator == null) - throw new ArgumentNullException(); + throw new ArgumentNullException(nameof(iterator), "Argument cannot be null."); + if (index < -1 || index >= iterator._length) + throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range."); _firstIndex = iterator._firstIndex; _list = iterator._list; _length = iterator._length; - Index = reset ? _firstIndex : iterator.Index; + Index = index; } #endregion @@ -84,10 +96,10 @@ public RandomAccessIterator(RandomAccessIterator iterator, bool reset) public void FastForward(int amount) { if (amount == 0) - throw new ArgumentException(); + throw new ArgumentException("Amount cannot be zero."); if (amount < Index || amount >= _length) - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(amount), "Amount is out of range."); Index = amount - 1; } @@ -146,7 +158,7 @@ public T PeekNextSkipping(Predicate predicate) public void Rewind(int index) { if (index < _firstIndex || index >= Index) - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range."); Index = index; } @@ -168,7 +180,7 @@ public void SetIndex(int index) if (index < Index) Rewind(index); else if (index >= _length) - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range."); Index = index; } diff --git a/Core6502DotNet/src/Utility/StringHelper.cs b/Core6502DotNet/src/Utility/StringHelper.cs index 2a56126..8462da8 100644 --- a/Core6502DotNet/src/Utility/StringHelper.cs +++ b/Core6502DotNet/src/Utility/StringHelper.cs @@ -169,6 +169,10 @@ public static string GetFormatted(RandomAccessIterator iterator, Assembly { parms.Add(GetString(iterator, services)); } + else if (services.Evaluator.ExpressionIsCondition(iterator)) + { + parms.Add(services.Evaluator.EvaluateCondition(iterator, false)); + } else { var parmVal = services.Evaluator.Evaluate(iterator, false); diff --git a/Core6502DotNet/src/m680x/M6809Asm.cs b/Core6502DotNet/src/m680x/M6809Asm.cs index 1e9bb76..21e940c 100644 --- a/Core6502DotNet/src/m680x/M6809Asm.cs +++ b/Core6502DotNet/src/m680x/M6809Asm.cs @@ -353,8 +353,7 @@ string AssembleIndexed(SourceLine line) } else if (modes.HasFlag(IndexModes.Indir)) { - Services.Log.LogEntry(secondparam, "Addressing mode not supported for selected CPU."); - return string.Empty; + return Services.Log.LogEntry(secondparam, "Addressing mode not supported for selected CPU."); } secondparam = operand.Current; } diff --git a/Core6502DotNet/src/m680x/MotorolaBase.cs b/Core6502DotNet/src/m680x/MotorolaBase.cs index a15ce11..2bfe9fe 100644 --- a/Core6502DotNet/src/m680x/MotorolaBase.cs +++ b/Core6502DotNet/src/m680x/MotorolaBase.cs @@ -423,7 +423,7 @@ protected override string AssembleCpuInstruction(SourceLine line) var sb = new StringBuilder(); if (!Services.Options.NoAssembly) { - var byteString = Services.Output.GetBytesFrom(LongPCOnAssemble).ToString(LongPCOnAssemble, '.'); + var byteString = Services.Output.GetBytesFrom(LongLogicalPCOnAssemble).ToString(LogicalPCOnAssemble, '.'); sb.Append(byteString.PadRight(Padding)); } else diff --git a/Core6502DotNet/src/z80/Z80Asm.cs b/Core6502DotNet/src/z80/Z80Asm.cs index 44506eb..113929a 100644 --- a/Core6502DotNet/src/z80/Z80Asm.cs +++ b/Core6502DotNet/src/z80/Z80Asm.cs @@ -316,7 +316,7 @@ protected override string AssembleCpuInstruction(SourceLine line) var disasmBuilder = new StringBuilder(); if (!Services.Options.NoAssembly) { - var byteString = Services.Output.GetBytesFrom(LogicalPCOnAssemble).ToString(LogicalPCOnAssemble, '.', true); + var byteString = Services.Output.GetBytesFrom(LongLogicalPCOnAssemble).ToString(LogicalPCOnAssemble, '.', true); disasmBuilder.Append(byteString.PadRight(25)); } else diff --git a/README.md b/README.md index f2bdd54..31ef046 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ 6502.Net, A .Net-Based Cross-Assembler for Several 8-Bit Microprocessors. -Version 2.8.0.1 +Version 2.8.1.1 ## Overview