Search code examples
parsingcompiler-optimizationabstract-syntax-tree

How does expressions work in one pass compiler?


I have seen a lot of posts regarding one pass and multi pass compilers and I think the basic parts of a One Pass Compiler as follows.

  1. Lexical Analysis
  2. Parse and Syntax Check
  3. Symbol Table
  4. Code Generation

But I have a question, that I cannot understand. How does Expression work in one pass compiler ( Without AST )

Let's take 1 + 2 / 3 as an example. Suppose you need to generate assembly code for this. After create a parser, how does generate assembly from it? ( Directly ) How does assembly code generate without any errors?

Can you explain this with examples? Please tell me if there is a problem in this question. I am new to Stackoverflow :)


Solution

  • The simplest way for a single-shot compiler process something like a=b*c[i]+d*e; would be to process each token as it is encountered:

    • a Generate code to push the address of a onto the stack
    • = Record the fact that the top item on the stack will be used as the target of an assignment.
    • b Generate code to push the address of b onto the stack
    • * Generate code to pop the address (of b), fetch the object there, and push it.
    • Also record the fact that the top item on the stack will be used as the left operand of a multiply.
    • c Generate code to push the address of c onto the stack
    • [ Record the fact that the top item on the stack will be the left operand of [ and evaluate the following expression
    • i Generate code to push the address of i onto the stack
    • ] Generate code to pop the address (of i), fetch the object there, and push it.
    • Also pop the two items on the stack (the value of i and the address of c, add them, and push that result
    • + Generate code to pop the address (of c[i]), fetch the object there, and push it
    • Also generate code to pop the top two values (c[i] and b), multiply them, and push the result
    • Also record the fact that the top item on the stack will be the left operand of +
    • d Generate code to push the address of d onto the stack
    • * Generate code to pop the address, fetch the object there, and push it.
    • Also record the fact that the top item on the stack will be used as the left operand of a multiply.
    • e Generate code to push the address of e onto the stack
    • (end of statement): Generate code to pop the address (of e), fetch the object there, and push it
    • Also generate code to pop the top two values (e and d), multiply them, and push the result
    • Also generate code to pop the top two values (d*e and b*c[i]), add them, and push the result
    • Also generate code to pop a value (b*c[i]+d*e) and address a and store the value to the address

    Note that on many platform, performance may be enormously improved if code generation for operations which end with a push is deferred until the next operation is generated, and if pops that immediately follow pushes are consolidated with them; further, it may be useful to have the compiler record the fact that the left hand side of the assignment operator is a, rather than generating code to push that address, and then have the assignment operator perform a store directly to a rather that computing the address first and then using it for the store. On the other hand, the approach as described will be able to generate machine code for everything up to the exact byte of machine code being processed, without having to keep track of much beyond pending operations that will need to be unwound.