Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
Post History
When parsing a program, a compiler creates a tree of nodes. The operator precedence plays a role in how those nodes get organized. Once the tree is complete and considered valid, it walks the tree ...
Answer
#3: Post edited
- When parsing a program, a compiler creates a tree of nodes. The operator precedence plays a role in how those nodes get organized. Once the tree is complete and considered valid, it walks the tree and outputs the corresponding instructions (in case of a C/C++ compiler, the instructions are assembly code; for other types of languages, such as Java, it may be "byte code"; for JavaScript it used to be kept in a tree of nodes, now we have basic assembly code in most cases).
- So for an operation such as `a + b * c`, you get:
- +
- / \
- / *
- a / \
- b c
- Then we walk the tree from left to right. That means we find a `+` and go toward the left side where we find `a`. We get that value (which in your case means calling the `a()` function).
- Then we walk back up to the `+` and walk down toward the `*`. The `*` has two branches, we first follow the one of the left and find the `b`.
- Once `b` was saved, we come back to the `*` and go down the other side and find `c`. Again, we evaluate `c`.
- Now that we have all the branch of the `*`, it means we have all the values and we can therefore perform the `*` evaluation and save that temporary result (i.e. `temp = b * c`). Save that result and go up again.
- Finally, we are back on the `+` node, we have `a` and `temp` and we can compute the addition and return that as the result of the expression.
- In assembly language, we most often use the stack to save the temporary values. With Intel processor and an ABI that returns the result in register `RAX`, we would get something more or less like this:
- CALL a
- PUSH RAX
- CALL b
- PUSH RAX
- CALL c
- PUSH RAX ; assuming no optimizations
- POP RAX
- POP RCX
- IMUL RAX, RCX
- PUSH RAX ; again, assuming no optimizations
- POP RAX
- POP RCX
- ADD RAX, RCX
- RET
- From all the compilers I know of, none of them would reorder the `CALL` because that's part of the definition of the language.
- There is one weird one, the cl compiler on MS-Windows will compute the parameters to a function call from right to left. This can be a gotcha if you want to make that same code work on other systems (Linux... are there others these days?!) since the convention is to do it left to right.
- call(p1(), p2(), p3(), p4());
- // Microsoft compiler:
- _p4 = p4();
- _p3 = p3();
- _p2 = p2();
- _p1 = p1();
- call(p1, p2, p3, p4);
- // Linux compiler:
- _p1 = p1();
- _p2 = p2();
- _p3 = p3();
- _p4 = p4();
[]() call(p1, p2, p3, p4);- I'd bet there are other exceptions. This is the one I know of.
- When parsing a program, a compiler creates a tree of nodes. The operator precedence plays a role in how those nodes get organized. Once the tree is complete and considered valid, it walks the tree and outputs the corresponding instructions (in case of a C/C++ compiler, the instructions are assembly code; for other types of languages, such as Java, it may be "byte code"; for JavaScript it used to be kept in a tree of nodes, now we have basic assembly code in most cases).
- So for an operation such as `a + b * c`, you get:
- +
- / \
- / *
- a / \
- b c
- Then we walk the tree from left to right. That means we find a `+` and go toward the left side where we find `a`. We get that value (which in your case means calling the `a()` function).
- Then we walk back up to the `+` and walk down toward the `*`. The `*` has two branches, we first follow the one of the left and find the `b`.
- Once `b` was saved, we come back to the `*` and go down the other side and find `c`. Again, we evaluate `c`.
- Now that we have all the branch of the `*`, it means we have all the values and we can therefore perform the `*` evaluation and save that temporary result (i.e. `temp = b * c`). Save that result and go up again.
- Finally, we are back on the `+` node, we have `a` and `temp` and we can compute the addition and return that as the result of the expression.
- In assembly language, we most often use the stack to save the temporary values. With Intel processor and an ABI that returns the result in register `RAX`, we would get something more or less like this:
- CALL a
- PUSH RAX
- CALL b
- PUSH RAX
- CALL c
- PUSH RAX ; assuming no optimizations
- POP RAX
- POP RCX
- IMUL RAX, RCX
- PUSH RAX ; again, assuming no optimizations
- POP RAX
- POP RCX
- ADD RAX, RCX
- RET
- From all the compilers I know of, none of them would reorder the `CALL` because that's part of the definition of the language.
- There is one weird one, the cl compiler on MS-Windows will compute the parameters to a function call from right to left. This can be a gotcha if you want to make that same code work on other systems (Linux... are there others these days?!) since the convention is to do it left to right.
- call(p1(), p2(), p3(), p4());
- // Microsoft compiler:
- _p4 = p4();
- _p3 = p3();
- _p2 = p2();
- _p1 = p1();
- call(p1, p2, p3, p4);
- // Linux compiler:
- _p1 = p1();
- _p2 = p2();
- _p3 = p3();
- _p4 = p4();
- call(p1, p2, p3, p4);
- I'd bet there are other exceptions. This is the one I know of.
#2: Post edited
- When parsing a program, a compiler creates a tree of nodes. The operator precedence plays a role in how those nodes get organized. Once the tree is complete and considered valid, it walks the tree and outputs the corresponding instructions (in case of a C/C++ compiler, the instructions are assembly code; for other types of languages, such as Java, it may be "byte code"; for JavaScript it used to be kept in a tree of nodes, now we have basic assembly code in most cases).
- So for an operation such as `a + b * c`, you get:
- +
- / \
- / *
- a / \
- b c
- Then we walk the tree from left to right. That means we find a `+` and go toward the left side where we find `a`. We get that value (which in your case means calling the `a()` function).
- Then we walk back up to the `+` and walk down toward the `*`. The `*` has two branches, we first follow the one of the left and find the `b`.
- Once `b` was saved, we come back to the `*` and go down the other side and find `c`. Again, we evaluate `c`.
- Now that we have all the branch of the `*`, it means we have all the values and we can therefore perform the `*` evaluation and save that temporary result (i.e. `temp = b * c`). Save that result and go up again.
- Finally, we are back on the `+` node, we have `a` and `temp` and we can compute the addition and return that as the result of the expression.
- In assembly language, we most often use the stack to save the temporary values. With Intel processor and an ABI that returns the result in register `RAX`, we would get something more or less like this:
- CALL a
- PUSH RAX
- CALL b
- PUSH RAX
- CALL c
- PUSH RAX ; assuming no optimizations
- POP RAX
- POP RCX
- IMUL RAX, RCX
- PUSH RAX ; again, assuming no optimizations
- POP RAX
- POP RCX
- ADD RAX, RCX
- RET
- From all the compilers I know of, none of them would reorder the `CALL` because that's part of the definition of the language.
- There is one weird one, the cl compiler on MS-Windows will compute the parameters to a function call from right to left. This can be a gotcha if you want to make that same code work on other systems (Linux... are there others these days?!) since the convention is to do it left to right.
call(p1(), p2(), p3(), p4());// Microsoft compiler:_p4 = p4();_p3 = p3();_p2 = p2();_p1 = p1();call(p1, p2, p3, p4);// Linux compiler:_p1 = p1();_p2 = p2();_p3 = p3();_p4 = p4();call(p1, p2, p3, p4);- I'd bet there are other exceptions. This is the one I know of.
- When parsing a program, a compiler creates a tree of nodes. The operator precedence plays a role in how those nodes get organized. Once the tree is complete and considered valid, it walks the tree and outputs the corresponding instructions (in case of a C/C++ compiler, the instructions are assembly code; for other types of languages, such as Java, it may be "byte code"; for JavaScript it used to be kept in a tree of nodes, now we have basic assembly code in most cases).
- So for an operation such as `a + b * c`, you get:
- +
- / \
- / *
- a / \
- b c
- Then we walk the tree from left to right. That means we find a `+` and go toward the left side where we find `a`. We get that value (which in your case means calling the `a()` function).
- Then we walk back up to the `+` and walk down toward the `*`. The `*` has two branches, we first follow the one of the left and find the `b`.
- Once `b` was saved, we come back to the `*` and go down the other side and find `c`. Again, we evaluate `c`.
- Now that we have all the branch of the `*`, it means we have all the values and we can therefore perform the `*` evaluation and save that temporary result (i.e. `temp = b * c`). Save that result and go up again.
- Finally, we are back on the `+` node, we have `a` and `temp` and we can compute the addition and return that as the result of the expression.
- In assembly language, we most often use the stack to save the temporary values. With Intel processor and an ABI that returns the result in register `RAX`, we would get something more or less like this:
- CALL a
- PUSH RAX
- CALL b
- PUSH RAX
- CALL c
- PUSH RAX ; assuming no optimizations
- POP RAX
- POP RCX
- IMUL RAX, RCX
- PUSH RAX ; again, assuming no optimizations
- POP RAX
- POP RCX
- ADD RAX, RCX
- RET
- From all the compilers I know of, none of them would reorder the `CALL` because that's part of the definition of the language.
- There is one weird one, the cl compiler on MS-Windows will compute the parameters to a function call from right to left. This can be a gotcha if you want to make that same code work on other systems (Linux... are there others these days?!) since the convention is to do it left to right.
- call(p1(), p2(), p3(), p4());
- // Microsoft compiler:
- _p4 = p4();
- _p3 = p3();
- _p2 = p2();
- _p1 = p1();
- call(p1, p2, p3, p4);
- // Linux compiler:
- _p1 = p1();
- _p2 = p2();
- _p3 = p3();
- _p4 = p4();
- []() call(p1, p2, p3, p4);
- I'd bet there are other exceptions. This is the one I know of.
#1: Initial revision
When parsing a program, a compiler creates a tree of nodes. The operator precedence plays a role in how those nodes get organized. Once the tree is complete and considered valid, it walks the tree and outputs the corresponding instructions (in case of a C/C++ compiler, the instructions are assembly code; for other types of languages, such as Java, it may be "byte code"; for JavaScript it used to be kept in a tree of nodes, now we have basic assembly code in most cases). So for an operation such as `a + b * c`, you get: + / \ / * a / \ b c Then we walk the tree from left to right. That means we find a `+` and go toward the left side where we find `a`. We get that value (which in your case means calling the `a()` function). Then we walk back up to the `+` and walk down toward the `*`. The `*` has two branches, we first follow the one of the left and find the `b`. Once `b` was saved, we come back to the `*` and go down the other side and find `c`. Again, we evaluate `c`. Now that we have all the branch of the `*`, it means we have all the values and we can therefore perform the `*` evaluation and save that temporary result (i.e. `temp = b * c`). Save that result and go up again. Finally, we are back on the `+` node, we have `a` and `temp` and we can compute the addition and return that as the result of the expression. In assembly language, we most often use the stack to save the temporary values. With Intel processor and an ABI that returns the result in register `RAX`, we would get something more or less like this: CALL a PUSH RAX CALL b PUSH RAX CALL c PUSH RAX ; assuming no optimizations POP RAX POP RCX IMUL RAX, RCX PUSH RAX ; again, assuming no optimizations POP RAX POP RCX ADD RAX, RCX RET From all the compilers I know of, none of them would reorder the `CALL` because that's part of the definition of the language. There is one weird one, the cl compiler on MS-Windows will compute the parameters to a function call from right to left. This can be a gotcha if you want to make that same code work on other systems (Linux... are there others these days?!) since the convention is to do it left to right. call(p1(), p2(), p3(), p4()); // Microsoft compiler: _p4 = p4(); _p3 = p3(); _p2 = p2(); _p1 = p1(); call(p1, p2, p3, p4); // Linux compiler: _p1 = p1(); _p2 = p2(); _p3 = p3(); _p4 = p4(); call(p1, p2, p3, p4); I'd bet there are other exceptions. This is the one I know of.