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()); } }
string DoGoto(RandomAccessIterator <SourceLine> lines) { var line = lines.Current; if (line.Operands.Count == 0) { throw new SyntaxException(line.Instruction.Position, "Destination not specified for \".goto\" directive."); } var gotoExp = line.Operands[0].Name; if ((!char.IsLetter(gotoExp[0]) && gotoExp[0] != '_') || line.Operands.Count > 1) { Services.Log.LogEntry(line.Operands[0], "\".goto\" operand must be a label."); } else if (line.Label != null && gotoExp.Equals(line.Label.Name, Services.StringViewComparer)) { Services.Log.LogEntry(line.Instruction, "Destination cannot be the same line as \".goto\" directive."); } else { var iterCopy = new RandomAccessIterator <SourceLine>(lines, true); SourceLine currLine; if ((currLine = iterCopy.FirstOrDefault(l => { if (l.Instruction != null && _openClosures.ContainsKey(l.Instruction.Name)) { if (l.Instruction.Name.Equals(".function", Services.StringComparison)) { throw new SyntaxException(l.Instruction, "Function block cannot be inside another block."); } // leap over any blocks we find along the way we are not currently in. if (!_blocks.Any(b => b.Index == iterCopy.Index)) { GetProcessor(l, iterCopy.Index).SeekBlockEnd(iterCopy); } return(false); } return(l.Label != null && l.Label.Name.Equals(gotoExp, Services.StringViewComparer)); })) != null) { if (currLine.Instruction != null && (currLine.Instruction.Name.Contains('=') || currLine.Instruction.Name.Equals(".equ", Services.StringComparison) || currLine.Instruction.Name.Equals(".global", Services.StringComparison) ) ) { Services.Log.LogEntry(line.Instruction, $"\"{gotoExp}\" is not a valid destination."); } else { while (_currentBlock != null) { // if where we landed lies outside of the current block scope // we need to pop out of that scope. _currentBlock.SeekBlockEnd(lines); if (iterCopy.Index > _currentBlock.Index) { // did we land in a place still within the block scope? if (iterCopy.Index > lines.Index) { // no, pop out DoPop(lines); } else { // we're still within the current block, don't pop it break; } } else { // we went backwards, pop out of current scope DoPop(lines); } } if (iterCopy.Index >= lines.Index) { lines.FastForward(iterCopy.Index); } else if (iterCopy.Index == 0) { lines.Reset(); } else { lines.Rewind(iterCopy.Index - 1); } } } else { Services.Log.LogEntry(line.Instruction, $"Could not find destination \"{gotoExp}\"."); } } return(string.Empty); }
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); }