static string ScanTo(char previousChar, RandomAccessIterator <char> iterator, Func <char, char, char, bool> terminal) { var tokenNameBuilder = new StringBuilder(); char c = iterator.Current; tokenNameBuilder.Append(c); if (!terminal(previousChar, c, iterator.PeekNext())) { previousChar = c; c = iterator.GetNext(); while (!terminal(previousChar, c, iterator.PeekNext())) { tokenNameBuilder.Append(c); if (char.IsWhiteSpace(c)) { break; } previousChar = c; c = iterator.GetNext(); } } return(tokenNameBuilder.ToString()); }
public double EvaluateFunction(RandomAccessIterator <Token> tokens) { tokens.MoveNext(); var param = tokens.GetNext(); if (param.Equals(")")) { throw new SyntaxException(param.Position, "Expected argument not provided."); } var symbolLookup = Services.SymbolManager.GetSymbol(param, false); if (symbolLookup == null) { if (param.Type != TokenType.Operand || !char.IsLetter(param.Name[0]) || param.Name[0] != '_') { throw new SyntaxException(param.Position, "Function \"len\" expects a symbol."); } if (Services.CurrentPass > 0) { throw new SymbolException(param, SymbolException.ExceptionReason.NotDefined); } Services.PassNeeded = true; return(0); } param = tokens.GetNext(); if (!param.Name.Equals(")")) { param = tokens.GetNext(); int subscript = -1; if (param.Name.Equals("[")) { subscript = (int)Services.Evaluator.Evaluate(tokens, 0, int.MaxValue); } if (subscript < 0 || !tokens.PeekNext().Equals(")")) { throw new SyntaxException(param.Position, "Unexpected argument."); } if (symbolLookup.StorageType != StorageType.Vector) { throw new SyntaxException(param.Position, "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); } if (subscript >= symbolLookup.NumericVector.Count) { throw new SyntaxException(param.Position, "Index out of range."); } return(symbolLookup.NumericVector[subscript].Size()); } return(symbolLookup.Length); }
/// <summary> /// Gets the formatted string from the tokenized expression. /// </summary> /// <param name="iterator">The iterator to the tokenized expression.</param> /// <param name="services">The shared assembly services.</param> /// <returns></returns> public static string GetFormatted(RandomAccessIterator <Token> iterator, AssemblyServices services) { iterator.MoveNext(); var format = iterator.GetNext(); if (Token.IsEnd(format)) { return(null); } string fmt; if (!format.IsDoubleQuote()) { if (format.Type != TokenType.Function && !format.Name.Equals("format", services.StringComparison)) { return(null); } fmt = GetFormatted(iterator, services); } else { fmt = Regex.Unescape(format.Name.TrimOnce('"').ToString()); } var parms = new List <object>(); if (iterator.MoveNext()) { while (!Token.IsEnd(iterator.GetNext())) { if (ExpressionIsAString(iterator, services)) { parms.Add(GetString(iterator, services)); } else { var parmVal = services.Evaluator.Evaluate(iterator, false); if (Regex.IsMatch(fmt, $"\\{{{parms.Count}(,-?\\d+)?:(d|D|x|X)\\d*\\}}")) { parms.Add((int)parmVal); } else { parms.Add(parmVal); } } } } if (parms.Count == 0) { return(fmt); } return(string.Format(fmt, parms.ToArray())); }
/// <summary> /// Creates a new instance of the Function class. /// </summary> /// <param name="name">The function's name.</param> /// <param name="parameterList">The list of parameters for the function.</param> /// <param name="iterator">The <see cref="SourceLine"/> iterator to traverse to define the function block.</param> /// <param name="services">The shared <see cref="AssemblyServices"/> object.</param> /// <param name="caseSensitive">Determines whether to compare the passed parameters /// to the source block's own defined parameters should be case-sensitive.</param> /// <exception cref="SyntaxException"></exception> public Function(StringView name, List <Token> parameterList, RandomAccessIterator <SourceLine> iterator, AssemblyServices services, bool caseSensitive) : base(parameterList, caseSensitive) { Name = name; _services = services; _definedLines = new List <SourceLine>(); SourceLine line; while ((line = iterator.GetNext()) != null) { if (line.Label != null && line.Label.Name.Equals("+")) { _services.Log.LogEntry(line.Label, "Anonymous labels are not supported inside functions.", false); } if (line.Instruction != null) { if (line.Instruction.Name.Equals(".global", _services.StringViewComparer)) { throw new SyntaxException(line.Instruction, $"Directive \".global\" not allowed inside a function block."); } if (line.Instruction.Name.Equals(".endfunction", _services.StringViewComparer)) { if (line.Operands.Count > 0) { throw new SyntaxException(line.Operands[0], "Unexpected expression found after \".endfunction\" directive."); } break; } } _definedLines.Add(line); } if (line == null) { throw new SyntaxException(iterator.Current.Instruction, "Function definition does not have a closing \".endfunction\" directive."); } }
double CallFunction(RandomAccessIterator <Token> tokens, bool returnValueExpected) { var functionToken = tokens.Current; var functionName = functionToken.Name; tokens.MoveNext(); var evalParms = new List <object>(); Token token = tokens.GetNext(); while (!token.Name.Equals(")")) { if (token.IsSeparator()) { tokens.MoveNext(); } if (StringHelper.ExpressionIsAString(tokens, Services)) { evalParms.Add(StringHelper.GetString(tokens, Services)); } else { evalParms.Add(Services.Evaluator.Evaluate(tokens, false)); } token = tokens.Current; } Services.SymbolManager.PushScopeEphemeral(); var value = _functionDefs[functionName].Invoke(evalParms); Services.SymbolManager.PopScopeEphemeral(); if (double.IsNaN(value) && returnValueExpected) { throw new ReturnException(functionToken.Position, $"Function name \"{functionName}\" did not return a value."); } return(value); }
public override void ExecuteDirective(RandomAccessIterator <SourceLine> lines) { var line = lines.Current; if (line.Instruction.Name.Equals(".endswitch", Services.StringComparison)) { return; } CaseBlock <string> stringBlock = null; CaseBlock <double> numericBlock = null; SwitchContext context = null; var it = line.Operands.GetIterator(); if (it.MoveNext()) { if (StringHelper.ExpressionIsAString(it, Services)) { context = new SwitchContext(StringHelper.GetString(it, Services)); } else { context = new SwitchContext(Services.Evaluator.Evaluate(it, false)); } if (it.Current != null) { throw new SyntaxException(it.Current, "Unexpected expression."); } } if (context == null) { string error; if (line.Operands.Count == 0) { error = "Expression must follow \".switch\" directive."; } else { error = "Expression must be a valid symbol or an expression."; } Services.Log.LogEntry(line.Filename, line.LineNumber, line.Instruction.Position, error); return; } var defaultIndex = -1; if (!string.IsNullOrEmpty(context.StringValue)) { stringBlock = new CaseBlock <string>(); } else { numericBlock = new CaseBlock <double>(); } while ((line = lines.GetNext()) != null && (line.Instruction == null || !line.Instruction.Name.Equals(".endswitch", Services.StringComparison))) { if (line.Instruction != null) { if (line.Instruction.Name.Equals(".case", Services.StringComparison)) { if (defaultIndex > -1) { Services.Log.LogEntry(line.Filename, line.LineNumber, line.Instruction.Position, "\".case\" directive cannot follow a \".default\" directive."); } else if (stringBlock?.FallthroughIndex > -1 || numericBlock?.FallthroughIndex > -1) { Services.Log.LogEntry(line.Filename, line.LineNumber, line.Instruction.Position, "\".case\" does not fall through."); } else if (line.Operands.Count == 0) { Services.Log.LogEntry(line.Filename, line.LineNumber, line.Instruction.Position, "Expression expected."); } else { var iterator = line.Operands.GetIterator(); if (stringBlock != null) { if (!StringHelper.ExpressionIsAString(iterator, Services)) { Services.Log.LogEntry(line.Filename, line.LineNumber, line.Operands[0].Position, "String expression expected."); } else { stringBlock.Cases.Add(StringHelper.GetString(iterator, Services)); } } else { numericBlock?.Cases.Add(Services.Evaluator.Evaluate(iterator)); } if (iterator.Current != null) { throw new SyntaxException(iterator.Current, "Unexpected expression."); } } } else if (Reserved.IsOneOf("BreakContReturn", line.Instruction.Name)) { if ((stringBlock?.Cases.Count == 0 || numericBlock?.Cases.Count == 0) && defaultIndex < 0) { Services.Log.LogEntry(line.Filename, line.LineNumber, line.Instruction.Position, $"\"{line.Instruction}\" directive must follow a \".case\" or \".default\" directive."); } else { if (line.Instruction.Name.Equals(".return", Services.StringComparison) && (stringBlock?.FallthroughIndex < 0 || numericBlock?.FallthroughIndex < 0)) { if (stringBlock != null) { stringBlock.FallthroughIndex = lines.Index; } if (numericBlock != null) { numericBlock.FallthroughIndex = lines.Index; } } else if (!line.Instruction.Name.Equals(".return", Services.StringComparison) && line.Operands.Count > 0) { throw new SyntaxException(line.Operands[0], "Unexpected expression."); } context.AddBlock(stringBlock); context.AddBlock(numericBlock); Services.SymbolManager.PopScope(); if (stringBlock != null) { stringBlock = new CaseBlock <string>(); } else { numericBlock = new CaseBlock <double>(); } } } else if (line.Instruction.Name.Equals(".default", Services.StringComparison)) { if (line.Operands.Count > 0) { throw new SyntaxException(line.Operands[0], "Unexpected expression."); } if (defaultIndex > -1) { Services.Log.LogEntry(line.Filename, line.LineNumber, line.Instruction.Position, "There can only be one \".default\" directive in a switch block."); } else { defaultIndex = lines.Index + 1; } } else if (line.Label != null) { Services.Log.LogEntry(line.Filename, line.LineNumber, line.Label.Position, "Label cannot be defined inside a switch block."); } else { if ((stringBlock?.Cases.Count == 0 || numericBlock?.Cases.Count == 0) && defaultIndex < 0) { Services.Log.LogEntry(line.Filename, line.LineNumber, line.Instruction.Position, "\".case\" or \".default\" directive expected"); } else if (stringBlock?.FallthroughIndex < 0 || numericBlock?.FallthroughIndex < 0) { if (stringBlock != null) { stringBlock.FallthroughIndex = lines.Index; } if (numericBlock != null) { numericBlock.FallthroughIndex = lines.Index; } } } } } if (line != null) { if (defaultIndex < 0 || !context.AnyCaseDefined()) { if (defaultIndex >= 0) { Services.Log.LogEntry(line.Filename, line.LineNumber, line.Instruction.Position, "Only a default case was specified.", false); } else if (!context.AnyCaseDefined()) { Services.Log.LogEntry(line.Filename, line.LineNumber, line.Instruction.Position, "Switch statement did not encounter any cases to evaluate."); return; } else { Services.Log.LogEntry(line.Filename, line.LineNumber, line.Instruction.Position, "Switch statement does not have a default case.", false); } } var fallthroughIndex = context.GetFallthroughIndex(); if (fallthroughIndex < 0) { fallthroughIndex = defaultIndex; } if (fallthroughIndex > -1) { lines.Rewind(fallthroughIndex - 1); } Services.SymbolManager.PushScope(lines.Index.ToString()); } }
/// <summary> /// Construct a new instance of a symbol class. /// </summary> /// <param name="tokens">The tokenized expression of the symbol definition.</param> /// <param name="eval">The <see cref="Evaluator"/> to evaluate the expression.</param> /// <param name="isMutable">The symbol's mutability flag.</param> public Symbol(RandomAccessIterator <Token> tokens, Evaluator eval, bool isMutable) : this() { IsMutable = isMutable; StorageType = StorageType.Vector; var opens = 1; var token = tokens.GetNext(); if (TokenType.End.HasFlag(token.Type)) { throw new SyntaxException(token.Position, "Expression expected."); } if (StringHelper.IsStringLiteral(tokens)) { DataType = DataType.String; } else { DataType = DataType.Numeric; } int index = 0; while (opens > 0) { if (token.Type == TokenType.Open && token.Name.Equals("[")) { opens++; } else if (token.Name.Equals("]")) { opens--; } else { if (DataType == DataType.String) { if (!StringHelper.IsStringLiteral(tokens)) { throw new SyntaxException(token.Position, "Type mismatch."); } StringVector.Add(index++, token.Name.TrimOnce('"')); token = tokens.GetNext(); if (token.Name.Equals("]")) { opens--; } } else { NumericVector.Add(index++, eval.Evaluate(tokens, false)); token = tokens.Current; if (token.Name.Equals("]")) { continue; } } } token = tokens.GetNext(); } }
double EvaluateSymbol(RandomAccessIterator <Token> tokens) { var token = tokens.Current; var subscript = -1; 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 symbol = SymbolManager.GetSymbol(token, CurrentPass > 0); if (symbol == null) { if (token.Line.Label != null && token.Line.Label.Name.Equals(token.Name, StringViewComparer)) { throw new SymbolException(token, SymbolException.ExceptionReason.NotDefined); } PassNeeded = true; return(0x100); } 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; } else if (symbol.IsNumeric) { if (symbol.DataType == DataType.Address && symbol.Bank != Output.CurrentBank) { return((int)symbol.NumericValue | (symbol.Bank * 0x10000)); } return(symbol.NumericValue); } else { token = new Token(symbol.StringValue, TokenType.Operand); isString = true; } } if (isString || token.IsQuote()) { // is it a string literal? var literal = token.IsQuote() ? token.Name.TrimOnce(token.Name[0]).ToString() : token.Name.ToString(); if (string.IsNullOrEmpty(literal)) { throw new SyntaxException(token.Position, "Cannot evaluate empty string."); } literal = Regex.Unescape(literal); if (!isString) { var charsize = 1; if (char.IsSurrogate(literal[0])) { charsize++; } if (literal.Length > charsize) { throw new SyntaxException(token.Position, "Invalid char literal."); } } // get the integral equivalent from the code points in the string converted = Encoding.GetEncodedValue(literal); } else if (token.Name.Equals("*")) { // get the program counter converted = Output.LogicalPC; } else if (token.Name[0].IsSpecialOperator()) { // get the special character value if (token.Name[0] == '+' && CurrentPass == 0) { converted = Output.LogicalPC; PassNeeded = true; } else { converted = SymbolManager.GetLineReference(token.Name, token); if (double.IsNaN(converted)) { var reason = token.Name[0] == '+' ? SymbolException.ExceptionReason.InvalidForwardReference : SymbolException.ExceptionReason.InvalidBackReference; throw new SymbolException(token, reason); } } } if (double.IsNaN(converted)) { throw new ExpressionException(token.Position, $"\"{token.Name}\" is not a expression."); } return(converted); }
/// <summary> /// Parses the source string into a tokenized <see cref="SourceLine"/> collection. /// </summary> /// <param name="fileName">The source file's path/name.</param> /// <param name="source">The source string.</param> /// <returns>A collection of <see cref="SourceLine"/>s whose components are /// properly tokenized for further evaluation and assembly.</returns> /// <exception cref="ExpressionException"/> public static IEnumerable <SourceLine> Parse(string fileName, string source) { var iterator = new RandomAccessIterator <char>(source.ToCharArray()); Token rootParent, currentParent; Token token = null; Reset(); Token currentOpen = null; int currentLine = 1, lineNumber = currentLine; // lineIndex is the iterator index at the start of each line for purposes of calculating token // positions. sourceLindeIndex is the iterator index at the start of each new line // of source. Usually lineIndex and sourceLindeIndex are the same, but for those source lines // whose source code span multiple lines, they will be different. int lineIndex = -1, opens = 0, sourceLineIndex = lineIndex; var lines = new List <SourceLine>(); char previousChar = iterator.Current; while (iterator.GetNext() != EOF) { if (iterator.Current != NewLine && iterator.Current != ':' && iterator.Current != ';') { try { token = ParseToken(previousChar, token, iterator); if (token != null) { previousChar = iterator.Current; token.Parent = currentParent; token.Position = iterator.Index - lineIndex - token.Name.Length + 1; if (token.OperatorType == OperatorType.Open || token.OperatorType == OperatorType.Closed || token.OperatorType == OperatorType.Separator) { if (token.OperatorType == OperatorType.Open) { opens++; currentParent.AddChild(token); currentOpen = currentParent = token; AddBlankSeparator(); } else if (token.OperatorType == OperatorType.Closed) { if (currentOpen == null) { throw new ExpressionException(token, $"Missing opening for closure \"{token.Name}\""); } // check if matching ( to ) if (!Groups[currentOpen.Name].Equals(token.Name)) { throw new ExpressionException(token, $"Mismatch between \"{currentOpen.Name}\" in column {currentOpen.Position} and \"{token.Name}\""); } // go up the ladder currentOpen = currentParent = token.Parent = currentOpen.Parent; while (currentOpen != null && currentOpen.OperatorType != OperatorType.Open) { currentOpen = currentOpen.Parent; } opens--; } else { currentParent = currentParent.Parent; currentParent.AddChild(token); currentParent = token; } } else if (token.Type == TokenType.Instruction) { while (currentParent.Parent != rootParent) { currentParent = currentParent.Parent; } currentParent.AddChild(token); AddBlankSeparator(); AddBlankSeparator(); } else { currentParent.AddChild(token); } } } catch (ExpressionException ex) { Assembler.Log.LogEntry(fileName, lineNumber, ex.Position, ex.Message); } if (iterator.PeekNext() == NewLine) { iterator.MoveNext(); } } if (iterator.Current == ';') { _ = iterator.Skip(c => c != NewLine && (c != ':' || Assembler.Options.IgnoreColons) && c != EOF); } if (iterator.Current == NewLine || iterator.Current == ':' || iterator.Current == EOF) { previousChar = iterator.Current; /* A new source line is when: * 1. A line termination character (New Line, colon, EOF) is encountered * 2. And either there are no more characters left or the most recent token created * 3. Is not a binary operator nor it is a comma separator. */ var newLine = iterator.Current == EOF || (opens == 0 && (token == null || (token.OperatorType != OperatorType.Binary && token.OperatorType != OperatorType.Open && !token.Name.Equals(",") ) ) ); if (iterator.Current == NewLine) { currentLine++; } if (newLine) { var newSourceLine = new SourceLine(fileName, lineNumber, GetSourceLineSource(), rootParent.Children[0]); lines.Add(newSourceLine); if (Assembler.Options.WarnLeft && newSourceLine.Label != null && newSourceLine.Label.Position != 1) { Assembler.Log.LogEntry(newSourceLine, newSourceLine.Label, "Label is not at the beginning of the line.", false); } Reset(); lineNumber = currentLine; } else { token = null; } lineIndex = iterator.Index; if (newLine) { sourceLineIndex = iterator.Index; } } } if (currentOpen != null && currentOpen.OperatorType == OperatorType.Open) { Assembler.Log.LogEntry(fileName, 1, currentOpen.LastChild.Position, $"End of source reached without finding closing \"{Groups[currentOpen.Name]}\"."); } if (token != null) { lines.Add(new SourceLine(fileName, lineNumber, GetSourceLineSource(), rootParent.Children[0])); } return(lines); void AddBlankSeparator() { var sepToken = new Token() { Type = TokenType.Operator, OperatorType = OperatorType.Separator, Name = string.Empty, Position = token == null ? 1 : token.Position, Children = new List <Token>() }; currentParent.AddChild(sepToken); currentParent = sepToken; } string GetSourceLineSource() { if (iterator.Index > sourceLineIndex + 1) { return(source.Substring(sourceLineIndex + 1, iterator.Index - sourceLineIndex - 1)); } return(string.Empty); } void Reset() { currentParent = rootParent = new Token(); currentParent.Children = new List <Token>(); AddBlankSeparator(); AddBlankSeparator(); token = null; } }
static Token ParseToken(char previousChar, Token previousToken, RandomAccessIterator <char> iterator, bool parsingAssembly = true) { char c = iterator.Current; while (char.IsWhiteSpace(c)) { if (c == NewLine && parsingAssembly) { iterator.Rewind(iterator.Index - 1); return(null); } c = iterator.GetNext(); } if ((c == ';' && parsingAssembly) || c == EOF) { return(null); } var token = new Token(); //first case, simplest var nextChar = iterator.PeekNext(); if (char.IsDigit(c) || char.IsLetter(c) || c == '_' || c == '?' || (c == '.' && char.IsLetterOrDigit(nextChar)) || (c == '\\' && char.IsLetterOrDigit(nextChar))) { token.Type = TokenType.Operand; if (char.IsDigit(c) || (c == '.' && char.IsDigit(nextChar))) { if (char.IsDigit(c) && previousChar == '$') { token.Name = ScanTo(previousChar, iterator, FirstNonHex); } else if (c == '0' && (nextChar == 'b' || nextChar == 'B' || nextChar == 'o' || nextChar == 'O' || nextChar == 'x' || nextChar == 'X')) { token.Name = ScanTo(previousChar, iterator, FirstNonNonBase10); } else { token.Name = ScanTo(previousChar, iterator, FirstNonNumeric); } } else if (c == '\\') { iterator.MoveNext(); token.Name = c + ScanTo(previousChar, iterator, FirstNonLetterOrDigit); } else if (c == '?') { token.UnparsedName = token.Name = "?"; return(token); } else { token.UnparsedName = token.Name = ScanTo(previousChar, iterator, FirstNonSymbol); if (parsingAssembly && !Assembler.Options.CaseSensitive) { token.Name = token.Name.ToLower(); } if (parsingAssembly && Assembler.InstructionLookupRules.Any(rule => rule(token.Name))) { token.Type = TokenType.Instruction; } else if (iterator.Current == '(' || (iterator.Current != NewLine && char.IsWhiteSpace(iterator.Current) && iterator.PeekNextSkipping(NonNewLineWhiteSpace) == '(')) { token.Type = TokenType.Operator; token.OperatorType = OperatorType.Function; } else { token.Type = TokenType.Operand; } } } else if (previousToken != null && previousToken.Name.Equals("%") && previousToken.OperatorType == OperatorType.Unary && (c == '.' || c == '#')) { // alternative binary string parsing token.Type = TokenType.Operand; token.Name = ScanTo(previousChar, iterator, FirstNonAltBin).Replace('.', '0') .Replace('#', '1'); } else if (c == '"' || c == SingleQuote) { var open = c; var quoteBuilder = new StringBuilder(c.ToString()); var escaped = false; while ((c = iterator.GetNext()) != open && c != char.MinValue) { quoteBuilder.Append(c); if (c == '\\') { escaped = true; quoteBuilder.Append(iterator.GetNext()); } } if (c == char.MinValue) { throw new ExpressionException(iterator.Index, $"Quote string not enclosed."); } quoteBuilder.Append(c); var unescaped = escaped ? Regex.Unescape(quoteBuilder.ToString()) : quoteBuilder.ToString(); if (c == '\'' && unescaped.Length > 3) { throw new ExpressionException(iterator.Index, "Too many characters in character literal."); } token.Name = unescaped; token.Type = TokenType.Operand; } else { if (c == '+' || c == '-') { /* * Scenarios for parsing '+' or '-', since they can function as different things * in an expression. * 1. The binary operator: * a. OPERAND+3 / ...)+(... => single '+' sandwiched between two operands/groupings * b. OPERAND++3 / ...)++(... => the first '+' is a binary operator since it is to the * right of an operand/grouping. We need to split off the single '++' to two * separate '+' tokens. What kind of token is the second '+'? We worry about that later. * c. OPERAND+++3 / ...)+++(... => again, the first '+' is a binary operator. We need to split * it off from the rest of the string of '+' characters, and we worry about later. * 2. The unary operator: * a. +3 / +(... => single '+' immediately preceding an operand/grouping. * b. ++3 / ++(... => parser doesn't accept C-style prefix (or postfix) operators, so one of these is an * anonymous label. Which one? Easy, the first. Split the '+' string. * 3. A full expression mixing both: * a. OPERAND+++3 / ...)+++(... => From scenario 1.c, we know the first '+' is a binary operator, * which leaves us with => '++3' left, which from scenario 2.b. we know the first '+' * has to be an operand. So we split the string again, so that the next scan leaves us with * '+3', so the third and final plus is a unary operator. * OPERAND => operand * + => binary operator * + => operand * + => unary operator * 3/( => operand/grouping * 4. A line reference: * a. + => Simplest scenario. * b. ++, +++, ++++, etc. => Treat as one. */ // Get the full string token.Name = ScanTo(previousChar, iterator, FirstNonPlusMinus); if (previousToken != null && (previousToken.Type == TokenType.Operand || previousToken.Name.Equals(")"))) { // looking backward at the previous token, if it's an operand or grouping then we // know this is a binary token.Type = TokenType.Operator; token.OperatorType = OperatorType.Binary; if (token.Name.Length > 1) // we need to split off the rest of the string so we have a single char '+' { token.Name = c.ToString(); iterator.Rewind(iterator.Index - token.Position - 1); } } else if (!IsNotOperand(nextChar) || nextChar == '(') { // looking at the very next character in the input stream, if it's an operand or grouping // then we know this is a unary if (token.Name.Length > 1) { // If the string is greater than one character, // then it's not a unary, it's an operand AND a unary. So we split off the // rest of the string. token.Name = c.ToString(); iterator.Rewind(iterator.Index - token.Position - 1); token.Type = TokenType.Operand; } else { token.Type = TokenType.Operator; token.OperatorType = OperatorType.Unary; } } else { token.Type = TokenType.Operand; } } else if (c == '*') { // Same as +/- scenario above, if the previous token is an operand or grouping, // we need to treat the splat as a binary operator. if (previousToken != null && (previousToken.Type == TokenType.Operand || previousToken.Name.Equals(")"))) { token.Type = TokenType.Operator; token.OperatorType = OperatorType.Binary; } else { // but since there is no unary version of this we will treat as an operand, and let the evaluator // deal with any problems like *OPERAND /*( token.Type = TokenType.Operand; } token.Name = c.ToString(); } else { // not a number, symbol, string, or special (+, -, *) character. So we just treat as an operator token.Type = TokenType.Operator; if (c.IsSeparator() || c.IsOpenOperator() || c.IsClosedOperator()) { token.Name = c.ToString(); if (c.IsSeparator()) { token.OperatorType = OperatorType.Separator; } else if (c.IsOpenOperator()) { token.OperatorType = OperatorType.Open; } else { token.OperatorType = OperatorType.Closed; } } else { token.Name = ScanTo(previousChar, iterator, FirstNonMatchingOperator); token.UnparsedName = token.Name; /* The general strategy to determine whether an operator is unary or binary: * 1. Is it actually one of the defined unary types? * 2. Peek at the next character. Is it a group or operand, or not? * 3. Look behind at the previous token. Is it also a group or operand, or not? * 4. If the token does NOT follow an operand or group, AND it precedes a group character, * or operand character, then it is a unary. * 5. All other cases, binary. * */ if ( ( ( c.IsUnaryOperator() && ( !IsNotOperand(nextChar) || nextChar == '(' || nextChar.IsRadixOperator() || nextChar.IsUnaryOperator() ) ) || ( c.IsRadixOperator() && char.IsLetterOrDigit(nextChar) ) || ( c == '%' && (nextChar == '.' || nextChar == '#') ) ) && (previousToken == null || (previousToken.Type != TokenType.Operand && !previousToken.Name.Equals(")") ) ) ) { token.OperatorType = OperatorType.Unary; } else { token.OperatorType = OperatorType.Binary; } } } } if (string.IsNullOrEmpty(token.UnparsedName)) { token.UnparsedName = token.Name; } if (iterator.Current != token.Name[^ 1]) { iterator.Rewind(iterator.Index - 1); } return(token); }
IEnumerable <SourceLine> ProcessMacros(IEnumerable <SourceLine> uncommented) { var macroProcessed = new List <SourceLine>(); RandomAccessIterator <SourceLine> lineIterator = uncommented.GetIterator(); SourceLine line = null; while ((line = lineIterator.GetNext()) != null) { try { if (string.IsNullOrWhiteSpace(line.ParsedSource)) { macroProcessed.Add(line); continue; } if (line.InstructionName.Equals(".macro")) { if (string.IsNullOrEmpty(line.LabelName)) { Assembler.Log.LogEntry(line, line.Instruction, "Macro name not specified."); continue; } var macroName = "." + line.LabelName; if (_macros.ContainsKey(macroName)) { Assembler.Log.LogEntry(line, line.Label, $"Macro named \"{line.LabelName}\" already defined."); continue; } if (Assembler.IsReserved.Any(i => i.Invoke(macroName)) || !char.IsLetter(line.LabelName[0])) { Assembler.Log.LogEntry(line, line.Label, $"Macro name \"{line.LabelName}\" is not valid."); continue; } Reserved.AddWord("MacroNames", macroName); var compare = Assembler.Options.CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; var macro = new Macro(line.Operand, line.ParsedSource, compare); _macros[macroName] = macro; var instr = line; while ((line = lineIterator.GetNext()) != null && !line.InstructionName.Equals(".endmacro")) { if (macroName.Equals(line.InstructionName)) { Assembler.Log.LogEntry(line, line.Instruction, "Recursive macro call not allowed."); continue; } if (line.InstructionName.Equals(".macro")) { Assembler.Log.LogEntry(line, line.Instruction, "Nested macro definitions not allowed."); continue; } if (line.InstructionName.Equals(".include") || line.InstructionName.Equals(".binclude")) { var includes = ExpandInclude(line); foreach (var incl in includes) { if (macroName.Equals(incl.InstructionName)) { Assembler.Log.LogEntry(incl, incl.Instruction, "Recursive macro call not allowed."); continue; } macro.AddSource(incl); } } else { macro.AddSource(line); } } if (!string.IsNullOrEmpty(line.LabelName)) { if (line.OperandHasToken) { Assembler.Log.LogEntry(line, line.Operand, "Unexpected argument found for macro definition closure."); continue; } line.Instruction = null; line.ParsedSource = line.ParsedSource.Replace(".endmacro", string.Empty); macro.AddSource(line); } else if (line == null) { line = instr; Assembler.Log.LogEntry(instr, instr.Instruction, "Missing closure for macro definition."); continue; } } else if (line.InstructionName.Equals(".include") || line.InstructionName.Equals(".binclude")) { macroProcessed.AddRange(ExpandInclude(line)); } else if (_macros.ContainsKey(line.InstructionName)) { if (!string.IsNullOrEmpty(line.LabelName)) { SourceLine clone = line.Clone(); clone.Operand = clone.Instruction = null; clone.UnparsedSource = clone.ParsedSource = line.LabelName; macroProcessed.Add(clone); } Macro macro = _macros[line.InstructionName]; macroProcessed.AddRange(ProcessExpansion(macro.Expand(line.Operand))); } else if (line.InstructionName.Equals(".endmacro")) { Assembler.Log.LogEntry(line, line.Instruction, "Directive \".endmacro\" does not close a macro definition."); continue; } else { macroProcessed.Add(line); } } catch (ExpressionException ex) { Assembler.Log.LogEntry(line, ex.Position, ex.Message); } } return(macroProcessed); }