void XLInfix::Output(ostream &o) // ---------------------------------------------------------------------------- // Emit an infix operator, including the newline operator // ---------------------------------------------------------------------------- { text seq = "\n"; if (name == seq) { o << *left; XLTree *tail = right; text op; while (tail->Kind() == xlINFIX) { XLInfix *infix = (XLInfix *) tail; if (infix->name != seq) break; o << nl_indent << *infix->left; tail = infix->right; } o << nl_indent << *tail; } else { if (outputDebug) o<< '(' << *left << ' ' << name << ' ' << *right << ')'; else o<< *left << ' ' << name << ' ' << *right; } }
XLTree *XLParser::Parse(text closing_paren) // ---------------------------------------------------------------------------- // Parse input // ---------------------------------------------------------------------------- /* XL parsing is not very difficult, but a bit unusual, because it is based solely on dynamic information and not, for instance, on keywords. Consider the following cases, where p is "prefix-op" and i is "infix-op" Write A Parses as p(Write,A). A and B Parses as i(and,A,B) if 'and' has a priority, as p(A,p(and,B)) otherwise Write -A,B This parses as (Write-A),B since "-" has a priority. I wish I could fix that one... The correct XL syntax is: Write (-A),B We hope that semantic will catch such a case later and let us know... */ { XLTree *result = NULL; XLTree *left = NULL; XLTree *right = NULL; text infix, name, spelling; text prefix = ""; text comment_end; token_t tok; bool done = false; text opening, closing; int default_priority = context->default_priority; int function_priority = context->function_priority; int statement_priority = context->statement_priority; int prefix_priority, infix_priority, paren_priority; int result_priority = default_priority; std::vector<Pending> stack; bool new_statement = true; token_t pendingToken = tokNONE; while (!done) { // If no token pending, scan one if (pendingToken == tokNONE) { right = NULL; prefix_priority = default_priority; infix_priority = default_priority; tok = scanner.NextToken(); } else if (pendingToken == tokNEWLINE) { // We have a pending newline: // skip actual token if it is also a newline tok = scanner.NextToken(); if (tok == tokNEWLINE) // Skip repeated newline { continue; } else if (tok == tokSYMBOL || tok == tokNAME) { name = scanner.NameValue(); if (context->IsComment(name, comment_end)) { // Got a comment with a pending newline: skip it scanner.Comment(comment_end); if (comment_end == "\n") continue; } // Check if we got something like 'else' if (context->InfixPriority(name) >= statement_priority) { // Otherwise, delay current token and process fake newline pendingToken = tok; tok = tokNEWLINE; } right = NULL; prefix_priority = default_priority; infix_priority = default_priority; } else if (tok == tokINDENT || tok == tokEOF) { // Something that is a newline itself is parsed directly pendingToken = tokNONE; right = NULL; prefix_priority = default_priority; infix_priority = default_priority; } else { // Something else is delayed until after we deal with newline pendingToken = tok; tok = tokNEWLINE; right = NULL; prefix_priority = default_priority; infix_priority = default_priority; } } else { // Other pending tokens are processed normally tok = pendingToken; pendingToken = tokNONE; } switch (tok) { case tokEOF: case tokERROR: done = true; break; case tokINTEGER: right = new XLInteger(scanner.IntegerValue()); break; case tokREAL: right = new XLReal(scanner.RealValue()); break; case tokSTRING: case tokQUOTE: right = new XLString(scanner.StringValue(), tok == tokSTRING ? '"' : '\''); break; case tokNAME: case tokSYMBOL: name = scanner.NameValue(); spelling = scanner.TokenText(); if (context->IsComment(name, comment_end)) { scanner.Comment(comment_end); if (comment_end == "\n") pendingToken = tokNEWLINE; continue; } else if (!result) { // If this is the very first thing we see prefix_priority = context->PrefixPriority(name); right = new XLName(spelling); if (prefix_priority == default_priority) prefix_priority = function_priority; } else if (left) { // This is the right of an infix operator // If we have "A and not B", where "not" has higher priority // than "and", we want to parse this as "A and (not B)", not // as "(A and not) B". prefix_priority = context->PrefixPriority(name); right = new XLName(spelling); if (prefix_priority == default_priority) prefix_priority = function_priority; } else { // Complicated case: need to disambiguate infix and prefix infix_priority = context->InfixPriority(name); if (infix_priority != default_priority) { // We got an infix left = result; infix = spelling; } else { // No priority: take this as a prefix-op prefix_priority = context->PrefixPriority(name); right = new XLName(spelling); if (prefix_priority == default_priority) prefix_priority = function_priority; } } break; case tokNEWLINE: if (pendingToken == tokNONE) { tok = scanner.NextToken(); if (tok == tokSYMBOL || tok == tokNAME) { if (context->IsComment(scanner.NameValue(), comment_end)) { // Followed by a comment: // Can't decide just yet what indent we have, // skip comment scanner.Comment(comment_end); continue; } } // Otherwise, we'll deal with this other token next. pendingToken = tok; } // Consider newline as an infix operator infix = "\n"; name = infix; infix_priority = context->InfixPriority(infix); left = result; break; case tokPARCLOSE: // Check for mismatched parenthese here... if (scanner.NameValue() != closing_paren) XLError(E_ParseMismatchParen, scanner.FileName(), scanner.FileLine(), scanner.NameValue(), ErrorNameOf(closing_paren)); done = true; break; case tokUNINDENT: // Check for mismatched unindent here... if (closing_paren != UNINDENT_MARKER) XLError(E_ParseMismatchParen, scanner.FileName(), scanner.FileLine(), text("unindent"), ErrorNameOf(closing_paren)); done = true; break; case tokPAROPEN: case tokINDENT: if (tok == tokINDENT) { opening = INDENT_MARKER; closing = UNINDENT_MARKER; } else { opening = scanner.NameValue()[0]; if (!gContext.IsBlock(opening, closing)) XLError(E_ParseMismatchParen, scanner.FileName(), scanner.FileLine(), text("<internal error>"), ErrorNameOf(closing_paren)); } name = opening; paren_priority = context->InfixPriority(name); // Make sure 'foo.bar(x)' parses as '(foo.bar)(x)' if (result) { while (stack.size()) { Pending &prev = stack.back(); if (prev.priority < paren_priority) break; result_priority = prev.priority; result = new XLInfix(prev.opcode, prev.argument, result); stack.pop_back(); } } // Parse the contents of the parenthese right = Parse(closing); if (!right) right = new XLName(""); right = new XLBlock(right, opening, closing); // Parse 'if (A+B) < C then...' as if ((A+B) < C) then ...' // Parse 'A[B] := C' as '(A[B] := C)' if (result_priority == statement_priority) if (paren_priority > function_priority) result_priority = paren_priority; if (tok == tokINDENT) { // Unindent to be seen as unindent followed by new-line // so that new-line operator combines lines pendingToken = tokNEWLINE; } break; default: MZ_ASSERT(!"Invalid token"); } // Switch // Check what is the current result if (!result) { // First thing we parse... result = right; if (new_statement && prefix_priority == default_priority) prefix_priority = statement_priority; result_priority = prefix_priority; // We are now in the middle of an expression if (result) new_statement = false; } else if (left) { // Check if we had a statement separator new_statement = infix_priority < statement_priority; // We got left and infix-op, we are now looking for right // If we have 'A and not B', where 'not' has higher priority // than 'and', we want to finish parsing 'not B' first, rather // than seeing this as '(A and not) B'. if (prefix_priority != default_priority && prefix_priority > infix_priority) { // Push "A and" in the above example stack.push_back(Pending(infix, left, infix_priority)); left = NULL; // Start over with "not" result = right; result_priority = prefix_priority; } else { while (stack.size()) { Pending &prev = stack.back(); // Check priorities compared to stack // A + B * C, we got '*': keep "A+..." on stack // Odd priorities are made right-associative by // turning the low-bit off in the comparison below if (!done && prev.priority != default_priority && infix_priority>(prev.priority & ~1)) break; if (prev.opcode == prefix) left = new XLPrefix(prev.argument, left); else left = new XLInfix(prev.opcode, prev.argument, left); stack.pop_back(); } // Now, we want to restart with the rightmost operand if (done) { // End of text: the result is what we just got result = left; } else { // Something like A+B+C, just got second + stack.push_back(Pending(infix, left, infix_priority)); result = NULL; } left = NULL; } } else if (right) { // Check priorities for something like "A.B x,y" -> "(A.B) (x,y)" // Odd priorities are made right associative by turning the // low bit off for the previous priority while (stack.size()) { Pending &prev = stack.back(); if (!done && prev.priority != default_priority && result_priority>(prev.priority & ~1)) break; if (prev.opcode == prefix) result = new XLPrefix(prev.argument, result); else result = new XLInfix(prev.opcode, prev.argument, result); stack.pop_back(); } // Check if new statement if (right->Kind() != xlBLOCK) if (stack.size() == 0 || stack.back().priority<statement_priority) result_priority = statement_priority; // Push a recognized prefix op stack.push_back(Pending(prefix, result, result_priority)); result = right; result_priority = prefix_priority; } } // While(!done) if (stack.size()) { if (!result) { Pending &last = stack.back(); if (last.opcode != text("\n")) XLError(E_ParseTrailingOp, scanner.FileName(), scanner.FileLine(), last.opcode); result = last.argument; stack.pop_back(); } while (stack.size()) { // Some stuff remains on stack Pending &prev = stack.back(); if (prev.opcode == prefix) result = new XLPrefix(prev.argument, result); else result = new XLInfix(prev.opcode, prev.argument, result); stack.pop_back(); } } return result; }