├── .gitignore
├── LICENSE
├── README.md
├── hello.c
├── tutorial
├── en
│ ├── 0-Preface.md
│ ├── 1-Skeleton.md
│ ├── 2-Virtual-Machine.md
│ ├── 3-Lexer.md
│ ├── 4-Top-down-Parsing.md
│ ├── 5-Variables.md
│ ├── 6-Functions.md
│ ├── 7-Statements.md
│ └── 8-Expressions.md
├── es
│ └── 0-Prefacio.md
├── ko
│ └── 0-머리말.md
└── pt-br
│ ├── 0-prefacio.md
│ ├── 1-Esqueleto.md
│ ├── 2-Maquina-Virtual.md
│ ├── 3-Lexer.md
│ ├── 4-Top-down-Parsing.md
│ ├── 5-variaveis.md
│ ├── 6-Funcoes.md
│ └── 7-Statements.md
├── xc-tutor.c
└── xc.c
/.gitignore:
--------------------------------------------------------------------------------
1 | # Object files
2 | *.o
3 | *.ko
4 | *.obj
5 | *.elf
6 |
7 | # Precompiled Headers
8 | *.gch
9 | *.pch
10 |
11 | # Libraries
12 | *.lib
13 | *.a
14 | *.la
15 | *.lo
16 |
17 | # Shared objects (inc. Windows DLLs)
18 | *.dll
19 | *.so
20 | *.so.*
21 | *.dylib
22 |
23 | # Executables
24 | *.exe
25 | *.out
26 | *.app
27 | *.i*86
28 | *.x86_64
29 | *.hex
30 |
31 | # Debug files
32 | *.dSYM/
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 | {description}
294 | Copyright (C) {year} {fullname}
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | {signature of Ty Coon}, 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
341 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | C interpreter that interprets itself.
2 |
3 | # How to Run the Code
4 |
5 | File `xc.c` is the original one and `xc-tutor.c` is the one that I make for
6 | the tutorial step by step.
7 |
8 | ```
9 | gcc -o xc xc.c
10 | ./xc hello.c
11 | ./xc -s hello.c
12 |
13 | ./xc xc.c hello.c
14 | ./xc xc.c xc.c hello.c
15 | ```
16 |
17 | # About
18 |
19 | This project is inspired by [c4](https://github.com/rswier/c4) and is largely
20 | based on it.
21 |
22 | However, I rewrote them all to make it more understandable and help myself to
23 | understand it.
24 |
25 | Despite the complexity we saw in books about compiler design, writing one is
26 | not that hard. You don't need that much theory though they will help for
27 | better understanding the logic behind the code.
28 |
29 | Also I write a series of article about how this compiler is built under directory `tutorial/en`.
30 |
31 | There is also a chinese version in my blog.
32 |
33 | 1. [手把手教你构建 C 语言编译器(0)——前言](http://lotabout.me/2015/write-a-C-interpreter-0/)
34 | 2. [手把手教你构建 C 语言编译器(1)——设计](http://lotabout.me/2015/write-a-C-interpreter-1/)
35 | 3. [手把手教你构建 C 语言编译器(2)——虚拟机](http://lotabout.me/2015/write-a-C-interpreter-2/)
36 | 4. [手把手教你构建 C 语言编译器(3)——词法分析器](http://lotabout.me/2015/write-a-C-interpreter-3/)
37 | 4. [手把手教你构建 C 语言编译器(4)——递归下降](http://lotabout.me/2016/write-a-C-interpreter-4/)
38 | 5. [手把手教你构建 C 语言编译器(5)——变量定义](http://lotabout.me/2016/write-a-C-interpreter-5/)
39 | 6. [手把手教你构建 C 语言编译器(6)——函数定义](http://lotabout.me/2016/write-a-C-interpreter-6/)
40 | 7. [手把手教你构建 C 语言编译器(7)——语句](http://lotabout.me/2016/write-a-C-interpreter-7/)
41 | 8. [手把手教你构建 C 语言编译器(8)——表达式](http://lotabout.me/2016/write-a-C-interpreter-8/)
42 | 0. [手把手教你构建 C 语言编译器(9)——总结](http://lotabout.me/2016/write-a-C-interpreter-9/)
43 |
44 | # Resources
45 |
46 | Further Reading:
47 |
48 | - [Let's Build a Compiler](http://compilers.iecc.com/crenshaw/): An excellent
49 | starting material for building compiler.
50 |
51 |
52 | Forks:
53 |
54 | - [A fork that implement debugger for xc.c](https://github.com/descent/write-a-C-interpreter)
55 |
56 |
57 | # Licence
58 |
59 | The original code is licenced with GPL2, so this code will use the same
60 | licence.
61 |
--------------------------------------------------------------------------------
/hello.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | int fibonacci(int i) {
4 | if (i <= 1) {
5 | return 1;
6 | }
7 | return fibonacci(i-1) + fibonacci(i-2);
8 | }
9 |
10 | int main()
11 | {
12 | int i;
13 | i = 0;
14 | while (i <= 10) {
15 | printf("fibonacci(%2d) = %d\n", i, fibonacci(i));
16 | i = i + 1;
17 | }
18 | return 0;
19 | }
20 |
--------------------------------------------------------------------------------
/tutorial/en/0-Preface.md:
--------------------------------------------------------------------------------
1 | This series of articles is a tutorial for building a C compiler from scratch.
2 |
3 | I lied a little in the above sentence: it is actually an _interpreter_ instead
4 | of a _compiler_. I lied because what the hell is a "C interpreter"? You will
5 | however, understand compilers better by building an interpreter.
6 |
7 | Yeah, I wish you can get a basic understanding of how a compiler is
8 | constructed, and realize it is not that hard to build one. Good Luck!
9 |
10 | Finally, this series is written in Chinese in the first place, feel free to
11 | correct me if you are confused by my English. And I would like it very much if
12 | you could teach me some "native" English :)
13 |
14 | We won't write any code in this chapter, so feel free to skip it if you are
15 | desperate to see some code...
16 |
17 | ## Why you should care about compiler theory?
18 |
19 | Because it is **COOL**!
20 |
21 | And it is very useful. Programs are built to do things for us, and when they translate some forms of data into another form, we can call them compilers. Thus, by learning some compiler theory, we are mastering a very
22 | powerful technique of solving problems. Isn't that cool?
23 |
24 | People used to say that understanding how a compiler works would help you write
25 | better code. Some would argue that modern compilers are so good at
26 | optimization that you should not care any more. Well, that's true, since most people
27 | don't need to learn compiler theory only to improve the efficency of the code.
28 | And by most people, I mean you!
29 |
30 | ## We Don't Like Theory Either
31 |
32 | I have always been in awe of compiler theory because that's what makes
33 | programming easy. Can you imagine building a web browser only in Assembly? When I got a chance to learn compiler theory in college,
34 | I was so excited! And then... I quit, not understanding it...
35 |
36 | Normally a compiler course will cover:
37 |
38 | 1. How to represent syntax (such as BNF, etc.)
39 | 2. Lexers, with some NFA (Nondeterministic Finite Automata),
40 | DFA (Deterministic Finite Automata).
41 | 3. Parsers, such as recursive descent, LL(k), LALR, etc.
42 | 4. Intermediate Languages.
43 | 5. Code generation.
44 | 6. Code optimization.
45 |
46 | Perhaps more than 90% of students will not care about anything beyond the parser, and
47 | what's more, we still don't know how to build a compiler! Even after all the
48 | effort learning the theories. The main reason is that what "Compiler
49 | Theoy" tries to teach is "How to build a parser generator", namely a tool that
50 | consumes syntax grammer and generates a compiler for you, like lex/yacc or
51 | flex/bison and other things like that.
52 |
53 | These theories try to teach us how to solve the general problems of generating
54 | compilers automatically. That means once you've mastered them, you are able to
55 | deal with all kinds of grammars. They are indeed useful in industry.
56 | Nevertheless they are too powerful and too complicated for students and most
57 | programmers. You will understand that if you try to read lex/yacc's source
58 | code.
59 |
60 | The good news is building a compiler is much simpler than you can imagine.
61 | I won't lie, it's not easy, but definitely not hard.
62 |
63 | ## Birth of this project
64 |
65 | One day I came across the project [c4](https://github.com/rswier/c4) on
66 | Github. It is a small C interpreter which is claimed to be implemented by only
67 | 4 functions. The most amazing part is that it is bootstrapping (it interprets
68 | itself), and it is only done with about 500 lines!
69 |
70 | Meanwhile I've read a lot of tutorials about compiler, they are either too
71 | simple (like implementing a simple calculator) or using automation
72 | tools (like flex/bison). C4, however, is implemented from scratch. The
73 | sad thing is that it tries to be minimal, making the code quite a mess, and hard
74 | to understand. So I started a new project that would:
75 |
76 | 1. Implement a working C compiler(interpreter actually)
77 | 2. Write a tutorial of how it is built.
78 |
79 | It took me 1 week to re-write it, resulting in 1400 lines including comments. The
80 | project is hosted on Github: [Write a C Interpreter](https://github.com/lotabout/write-a-C-interpreter).
81 |
82 | Thanks rswier for bringing us a wonderful project!
83 |
84 | ## Before you go
85 |
86 | Implementing a compiler can be boring and it is hard to debug. I hope you
87 | can spare enough time studying, as well as type the code. I am sure that you
88 | will feel a great sense of accomplishment just like I did.
89 |
90 | ## Good Resources
91 |
92 | 1. [Let’s Build a Compiler](http://compilers.iecc.com/crenshaw/): a very good
93 | tutorial of building a compiler for fresh starters.
94 | 2. [Lemon Parser Generator](http://www.hwaci.com/sw/lemon/): the parser
95 | generator that is used in SQLite. Good to read if you want to understand
96 | compiler theory with code.
97 |
98 | In the end, I am human at a general level, so there will inevitably be errors
99 | with the articles and code (also my English). Feel free to correct me!
100 |
101 | Hope you enjoy.
102 |
--------------------------------------------------------------------------------
/tutorial/en/1-Skeleton.md:
--------------------------------------------------------------------------------
1 | In this chapter we will have an overview of the compiler's structure.
2 |
3 | Before we start, I'd like to restress that it is **interpreter** that we want
4 | to build. That means we can run a C source file just like a script. It is
5 | chosen mainly for two reasons:
6 |
7 | 1. Interpreter differs from Compiler only in code generation phase, thus we'll
8 | still learn all the core techniques of building a compiler (such as lexical
9 | analyzing and parsing).
10 | 2. We will build our own virtual machine and assembly instructions, that would
11 | help us to understand how computers work.
12 |
13 | ## Three Phases
14 |
15 | Given a source file, normally the compiler will cast three phases of
16 | processing:
17 |
18 | 1. Lexical Analysis: converts source strings into internal token stream.
19 | 2. Parsing: consumes token stream and constructs syntax tree.
20 | 3. Code Generation: walk through the syntax tree and generate code for target
21 | platform.
22 |
23 | Compiler Construction had been so mature that part 1 & 2 can be done by
24 | automation tools. For example, flex can be used for lexical analysis, bison
25 | for parsing. They are powerful but do thousands of things behind the scene. In
26 | order to fully understand how to build a compiler, we are going to build them
27 | all from scratch.
28 |
29 | Thus we will build our interpreter in the following steps:
30 |
31 | 1. Build our own virtual machine and instruction set. This is the target
32 | platform that will be using in our code generation phase.
33 | 2. Build our own lexer for the C compiler.
34 | 3. Write a recursive descent parser on our own.
35 |
36 | ## Skeleton of our compiler
37 |
38 |
39 | Modeling after c4, our compiler includes 4 main functions:
40 |
41 | 1. `next()` for lexical analysis; get the next token; it will ignore spaces, tabs
42 | etc.
43 | 2. `program()` main entrance for parser.
44 | 3. `expression(level)`: parser expression; level will be explained in later
45 | chapter.
46 | 4. `eval()`: the entrance for virtual machine; used to interpret target
47 | instructions.
48 |
49 | Why would `expression` exist when we have `program` for parser? That's because
50 | the parser for expressions is relatively independent and complex, so we put it
51 | into a single module(function).
52 |
53 | The code is as following:
54 |
55 | ```c
56 | #include
57 | #include
58 | #include
59 | #include
60 | #define int long long // work with 64bit target
61 |
62 | int token; // current token
63 | char *src, *old_src; // pointer to source code string;
64 | int poolsize; // default size of text/data/stack
65 | int line; // line number
66 |
67 | void next() {
68 | token = *src++;
69 | return;
70 | }
71 |
72 | void expression(int level) {
73 | // do nothing
74 | }
75 |
76 | void program() {
77 | next(); // get next token
78 | while (token > 0) {
79 | printf("token is: %c\n", token);
80 | next();
81 | }
82 | }
83 |
84 | int eval() { // do nothing yet
85 | return 0;
86 | }
87 |
88 | int main(int argc, char **argv)
89 | {
90 | int i, fd;
91 |
92 | argc--;
93 | argv++;
94 |
95 | poolsize = 256 * 1024; // arbitrary size
96 | line = 1;
97 |
98 | if ((fd = open(*argv, 0)) < 0) {
99 | printf("could not open(%s)\n", *argv);
100 | return -1;
101 | }
102 |
103 | if (!(src = old_src = malloc(poolsize))) {
104 | printf("could not malloc(%d) for source area\n", poolsize);
105 | return -1;
106 | }
107 |
108 | // read the source file
109 | if ((i = read(fd, src, poolsize-1)) <= 0) {
110 | printf("read() returned %d\n", i);
111 | return -1;
112 | }
113 |
114 | src[i] = 0; // add EOF character
115 | close(fd);
116 |
117 | program();
118 | return eval();
119 | }
120 | ```
121 |
122 | That's quite some code for the first chapter of the article. Nevertheless it
123 | is actually simple enough. The code tries to reads in a source file, character
124 | by character and print them out.
125 |
126 | Currently the lexer `next()` does nothing but returning the characters as they
127 | are in the source file. The parser `program()` doesn't take care of its job
128 | either, no syntax trees are generated, no target codes are generated.
129 |
130 | The important thing here is to understand the meaning of these functions and
131 | how they are hooked together as they are the skeleton of our interpreter.
132 | We'll fill them out step by step in later chapters.
133 |
134 | ## Code
135 |
136 | The code for this chapter can be downloaded from
137 | [Github](https://github.com/lotabout/write-a-C-interpreter/tree/step-0), or
138 | be cloned by:
139 |
140 | ```
141 | git clone -b step-0 https://github.com/lotabout/write-a-C-interpreter
142 | ```
143 |
144 | Note that I might fix bugs later, and if there is any incosistance between the
145 | article and the code branches, follow the article. I would only update code in
146 | the master branch.
147 |
148 | ## Summary
149 |
150 | After some boring typing, we have the simplest compiler: a do-nothing
151 | compiler. In next chapter, we will implement the `eval` function, i.e. our own
152 | virtual machine. See you then.
153 |
--------------------------------------------------------------------------------
/tutorial/en/2-Virtual-Machine.md:
--------------------------------------------------------------------------------
1 | In this chapter, we are going to build a virtual machine and design our own
2 | instruction set that runs on the VM. This VM will be the target platform of
3 | the interpreter's code generation phase.
4 |
5 | If you've heard of JVM and bytecode, that's what we are trying to build, but a
6 | way way simpler one.
7 |
8 | ## How computer works internally
9 |
10 | There are three components we need to care about: CPU, registers and memory.
11 | Code(or assembly instruction) are stored in the memory as binary data; CPU
12 | will retrieve the instruction one by one and execute them; the running states
13 | of the machine is stored in registers.
14 |
15 | ### Memory
16 |
17 | Memory can be used to store data. By data I mean code(or called assembly
18 | instructions) or other data such as the message you want to print out. All of
19 | them are stored as binary.
20 |
21 | Modern operating system introduced *Virtual Memory* which maps memory
22 | addresses used by a program called *virtual address* into physical addresses
23 | in computer memory. It can hide the physical details of memory from the
24 | program.
25 |
26 | The benefit of virtual memory is that it can hide the details of a physical
27 | memory from the programs. For example, in 32bit machine, all the available
28 | memory address is `2^32 = 4G` while the actaul physical memory may be only
29 | `256M`. The program will still think that it can have `4G` memory to use, the
30 | OS will map them to physical ones.
31 |
32 | Of course, you don't need to understand the details about that. But what you
33 | should understand that a program's usable memory is partioned into several
34 | segments:
35 |
36 | 1. `text` segment: for storing code(instructions).
37 | 2. `data` segment: for storing initialized data. For example `int i = 10;`
38 | will need to utilize this segment.
39 | 3. `bss` segment: for storing un-initialized data. For example `int i[1000];`
40 | does't need to occupy `1000*4` bytes, because the actual values in the
41 | array don't matter, thus we can store them in `bss` to save some space.
42 | 4. `stack` segment: used to handling the states of function calls, such as
43 | calling frames and local variables of a function.
44 | 5. `heap` segment: use to allocate memory dynamically for program.
45 |
46 | An example of the layout of these segments here:
47 |
48 | ```
49 | +------------------+
50 | | stack | | high address
51 | | ... v |
52 | | |
53 | | |
54 | | |
55 | | |
56 | | ... ^ |
57 | | heap | |
58 | +------------------+
59 | | bss segment |
60 | +------------------+
61 | | data segment |
62 | +------------------+
63 | | text segment | low address
64 | +------------------+
65 | ```
66 |
67 | Our virtual machine tends to be as simple as possible, thus we don't care
68 | about the `bss` and `heap`. Our interperter don't support the initialization
69 | of data, thus we'll merge the `data` and `bss` segment. More over, we only use
70 | `data` segment for storing string literals.
71 |
72 | We'll drop `heap` as well. This might sounds insane because theoretically the
73 | VM should maintain a `heap` for allocation memories. But hey, an interpreter
74 | itself is also a program which had its heap allocated by our computer. We can
75 | tell the program that we want to interpret to utilize the interpreter's heap
76 | by introducing an instruction `MSET`. I won't say it is cheating because it
77 | reduces the VM's complexity without reducing the knowledge we want to learn
78 | about compiler.
79 |
80 | Thus we adds the following codes in the global area:
81 |
82 | ```c
83 | int *text, // text segment
84 | *old_text, // for dump text segment
85 | *stack; // stack
86 | char *data; // data segment
87 | ```
88 |
89 | Note the `int` here. What we should write is actually `unsigned` because we'll
90 | store unsigned data(such as pointers/memory address) in the `text` segment.
91 | Note we want our interpreter to be bootstraping (interpret itself), thus we
92 | don't want to introduce `unsigned`. Finally the `data` is `char *` because
93 | we'll use it to store string literals only.
94 |
95 | Finally, add the code in the main function to actually allocate the segments:
96 |
97 | ```c
98 | int main() {
99 | close(fd);
100 | ...
101 |
102 | // allocate memory for virtual machine
103 | if (!(text = old_text = malloc(poolsize))) {
104 | printf("could not malloc(%d) for text area\n", poolsize);
105 | return -1;
106 | }
107 | if (!(data = malloc(poolsize))) {
108 | printf("could not malloc(%d) for data area\n", poolsize);
109 | return -1;
110 | }
111 | if (!(stack = malloc(poolsize))) {
112 | printf("could not malloc(%d) for stack area\n", poolsize);
113 | return -1;
114 | }
115 |
116 | memset(text, 0, poolsize);
117 | memset(data, 0, poolsize);
118 | memset(stack, 0, poolsize);
119 |
120 | ...
121 | program();
122 | }
123 | ```
124 |
125 | ### Registers
126 |
127 | Registers are used to store the running states of computers. There are several
128 | of them in real computers while our VM uses only 4:
129 |
130 | 1. `PC`: program counter, it stores an memory address in which stores the
131 | **next** instruction to be run.
132 | 2. `SP`: stack pointer, which always points to the *top* of the stack. Notice
133 | the stack grows from high address to low address so that when we push a new
134 | element to the stack, `SP` decreases.
135 | 3. `BP`: base pointer, points to some elements on the stack. It is used in
136 | function calls.
137 | 4. `AX`: a general register that we used to store the result of an
138 | instruction.
139 |
140 | In order to fully understand why we need these registers, you need to
141 | understand what states will a computer need to store during computation. They
142 | are just a place to store value. You will get a better understanding after
143 | finished this chapter.
144 |
145 | Well, add some code into the global area:
146 |
147 | ```c
148 | int *pc, *bp, *sp, ax, cycle; // virtual machine registers
149 | ```
150 |
151 | And add the initialization code in the `main` function. Note that `pc` should
152 | points to the `main` function of the program to be interpreted. But we don't
153 | have any code generation yet, thus skip for now.
154 |
155 | ```c
156 | memset(stack, 0, poolsize);
157 | ...
158 |
159 | bp = sp = (int *)((int)stack + poolsize);
160 | ax = 0;
161 |
162 | ...
163 | program();
164 | ```
165 |
166 | What's left is the CPU part, what we should actually do is implementing the
167 | instruction sets. We'll save that for a new section.
168 |
169 | ## Instruction Set
170 |
171 | Instruction set is a set of instruction that CPU can understand, it is the
172 | language we need to master in order to talk to CPU. We are going to design a
173 | language for our VM, it is based on x86 instruction set yet much simpler.
174 |
175 | We'll start by adding an `enum` type listing all the instructions that our VM
176 | would understand:
177 |
178 | ```c
179 | // instructions
180 | enum { LEA ,IMM ,JMP ,CALL,JZ ,JNZ ,ENT ,ADJ ,LEV ,LI ,LC ,SI ,SC ,PUSH,
181 | OR ,XOR ,AND ,EQ ,NE ,LT ,GT ,LE ,GE ,SHL ,SHR ,ADD ,SUB ,MUL ,DIV ,MOD ,
182 | OPEN,READ,CLOS,PRTF,MALC,MSET,MCMP,EXIT };
183 | ```
184 |
185 | These instruction are ordered intentionally as you will find out later that
186 | instructions with arguments comes first while those without arguments comes
187 | after. The only benefit here is for printing debug info. However we will not
188 | rely on this order to introduce them.
189 |
190 | ### MOV
191 |
192 | `MOV` is one of the most fundamental instructions you'll met. Its job is to
193 | move data into registers or the memory, kind of like the assignment expression
194 | in C. There are two arguments in `x86`'s `MOV` instruction: `MOV dest,
195 | source`(Intel style), `source` can be a number, a register or a memory
196 | address.
197 |
198 | But we won't follow `x86`. On one hand our VM has only one general
199 | register(`AX`), on the other hand it is difficult to determine the type of the
200 | arguments(wheter it is number, register or adddress). Thus we tear `MOV` apart
201 | into 5 pieces:
202 |
203 | 1. `IMM ` to put immediate `` into register `AX`.
204 | 2. `LC` to load a character into `AX` from a memory address which is stored in
205 | `AX` before execution.
206 | 3. `LI` just like `LC` but dealing with integer instead of character.
207 | 4. `SC` to store the character in `AX` into the memory whose address is stored
208 | on the top of the stack.
209 | 5. `SI` just like `SC` but dealing with integer instead of character.
210 |
211 | What? I want one `MOV`, not 5 instruction just to replace it! Don't panic!
212 | You should know that `MOV` is actually a set of instruction that depends on
213 | the `type` of its arguments, so you got `MOVB` for bytes and `MOVW` for words,
214 | etc. Now `LC/SC` and `LI/SI` don't seems that bad, uha?
215 |
216 | Well the most important reason is that by turning `MOV` into 5 sub
217 | instructions, we reduce the complexity a lot! Only `IMM` will accept an
218 | argument now yet no need to worry about its type.
219 |
220 | Let's implement it in the `eval` function:
221 |
222 | ```c
223 | void eval() {
224 | int op, *tmp;
225 | while (1) {
226 | op = *pc++; // get next operation code
227 | if (op == IMM) {ax = *pc++;} // load immediate value to ax
228 | else if (op == LC) {ax = *(char *)ax;} // load character to ax, address in ax
229 | else if (op == LI) {ax = *(int *)ax;} // load integer to ax, address in ax
230 | else if (op == SC) {ax = *(char *)*sp++ = ax;} // save character to address, value in ax, address on stack
231 | else if (op == SI) {*(int *)*sp++ = ax;} // save integer to address, value in ax, address on stack
232 | }
233 |
234 | ...
235 | return 0;
236 | }
237 | ```
238 |
239 | `*sp++` is used to `POP` out one stack element.
240 |
241 | You might wonder why we store the address in `AX` register for `LI/LC` while
242 | storing them on top of the stack segment for `SI/SC`. The reason is that the
243 | result of an instruction is stored in `AX` by default. The memory address is also
244 | calculate by an instruction, thus it is more convenient for `LI/LC` to fetch
245 | it directly from `AX`. Also `PUSH` can only push the value of `AX` onto the
246 | stack. So if we want to put an address onto the stack, we'll have to store it
247 | in `AX` anyway, why not skip that?
248 |
249 | ### PUSH
250 |
251 | `PUSH` in `x86` can push an immediate value or a register's value onto the
252 | stack. Here in our VM, `PUSH` will push the value in `AX` onto the stack,
253 | only.
254 |
255 | ```c
256 | else if (op == PUSH) {*--sp = ax;} // push the value of ax onto the stack
257 | ```
258 |
259 | ### JMP
260 |
261 | `JMP ` will unconditionally set the value `PC` register to ``.
262 |
263 | ```c
264 | else if (op == JMP) {pc = (int *)*pc;} // jump to the address
265 | ```
266 |
267 | Notice that `PC` points to the **NEXT** instruction to be executed. Thus `*pc`
268 | stores the argument of `JMP` instruction, i.e. the ``.
269 |
270 | ### JZ/JNZ
271 |
272 | We'll need conditional jump so as to implement `if` statement. Only two
273 | are needed here to jump when `AX` is `0` or not.
274 |
275 | ```c
276 | else if (op == JZ) {pc = ax ? pc + 1 : (int *)*pc;} // jump if ax is zero
277 | else if (op == JNZ) {pc = ax ? (int *)*pc : pc + 1;} // jump if ax is not zero
278 | ```
279 |
280 | ### Function Call
281 |
282 | It will introduce the calling frame which is hard to understand, so we put it
283 | together to give you an overview. We'll add `CALL`, `ENT`, `ADJ` and `LEV` in
284 | order to support function calls.
285 |
286 | A function is a block of code, it may be physically far form the instruction
287 | we are currently executing. So we'll need to `JMP` to the starting point of a
288 | function. Then why introduce a new instruction `CALL`? Because we'll need to
289 | do some bookkeeping: store the current execution position so that the program
290 | can resume after function call returns.
291 |
292 | So we'll need `CALL ` to call the function whose starting point is
293 | `` and `RET` to fetch the bookkeeping information to resume previous
294 | execution.
295 |
296 | ```c
297 | else if (op == CALL) {*--sp = (int)(pc+1); pc = (int *)*pc;} // call subroutine
298 | //else if (op == RET) {pc = (int *)*sp++;} // return from subroutine;
299 | ```
300 |
301 | We've commented out `RET` because we'll replace it with `LEV` later.
302 |
303 | In practice the compiler should deal with more: how to pass the arguments to
304 | a function? How to return the data from the function?
305 |
306 | Our convention here about returning value is to store it into `AX` no matter
307 | you're returning a value or a memory address. Then how about argument?
308 |
309 | Different language has different convension, here is the standard for C:
310 |
311 | 1. It is the caller's duty to push the arguments onto stack.
312 | 2. After the function call returns, caller need to pop out the arguments.
313 | 3. The arguments are pushed in the reversed order.
314 |
315 | Note that we won't follow rule 3. Now let's check how C standard works(from
316 | [Wikipedia](https://en.wikipedia.org/wiki/X86_calling_conventions)):
317 |
318 | ```c
319 | int callee(int, int, int);
320 |
321 | int caller(void)
322 | {
323 | int i, ret;
324 |
325 | ret = callee(1, 2, 3);
326 | ret += 5;
327 | return ret;
328 | }
329 | ```
330 |
331 | The compiler will generate the following assembly instructions:
332 |
333 | ```
334 | caller:
335 | ; make new call frame
336 | push ebp
337 | mov ebp, esp
338 | sub 1, esp ; save stack for variable: i
339 | ; push call arguments
340 | push 3
341 | push 2
342 | push 1
343 | ; call subroutine 'callee'
344 | call callee
345 | ; remove arguments from frame
346 | add esp, 12
347 | ; use subroutine result
348 | add eax, 5
349 | ; restore old call frame
350 | mov esp, ebp
351 | pop ebp
352 | ; return
353 | ret
354 | ```
355 |
356 | The above assembly instructions cannot be achieved in our VM due to several
357 | reasons:
358 |
359 | 1. `push ebp`, while our `PUSH` doesn't accept arguments at all.
360 | 2. `move ebp, esp`, our `MOV` instruction cannot do this.
361 | 3. `add esp, 12`, well, still cannot do this(as you'll find out later).
362 |
363 | Our instruction set is too simply that we cannot not support function calls!
364 | But we will not surrender to change our design cause it will be too complex
365 | for us. So we add more instructions! It might cost a lot in real computers to
366 | add a new instruction, but not for virtual machine.
367 |
368 | ### ENT
369 |
370 | `ENT ` is called when we are about to enter the function call to "make a
371 | new calling frame". It will store the current `PC` value onto the stack, and
372 | save some space(`` bytes) to store the local variables for function.
373 |
374 | ```
375 | ; make new call frame
376 | push ebp
377 | mov ebp, esp
378 | sub 1, esp ; save stack for variable: i
379 | ```
380 |
381 | Will be translated into:
382 |
383 | ```c
384 | else if (op == ENT) {*--sp = (int)bp; bp = sp; sp = sp - *pc++;} // make new stack frame
385 | ```
386 |
387 | ### ADJ
388 |
389 | `ADJ ` is to adjust the stack, to "remove arguments from frame". We need
390 | this instruction mainly because our `ADD` don't have enough power. So, treat
391 | it as a special add instruction.
392 |
393 | ```
394 | ; remove arguments from frame
395 | add esp, 12
396 | ```
397 |
398 | Is implemented as:
399 |
400 | ```c
401 | else if (op == ADJ) {sp = sp + *pc++;} // add esp,
402 | ```
403 |
404 | ### LEV
405 |
406 | In case you don't notice, our instruction set don't have `POP`. `POP` in our
407 | compiler would only be used when function call returns. Which is like this:
408 |
409 | ```
410 | ; restore old call frame
411 | mov esp, ebp
412 | pop ebp
413 | ; return
414 | ret
415 | ```
416 |
417 | Thus we'll make another instruction `LEV` to accomplish the work of `MOV`,
418 | `POP` and `RET`:
419 |
420 | ```c
421 | else if (op == LEV) {sp = bp; bp = (int *)*sp++; pc = (int *)*sp++;} // restore call frame and PC
422 | ```
423 |
424 | ### LEA
425 |
426 | The instructions introduced above try to solve the problem of
427 | creating/destructing calling frames, one thing left here is how to fetch the
428 | arguments *inside* sub function.
429 |
430 | But we'll check out what a calling frame looks like before learning how to
431 | fetch arguments (Note that arguments are pushed in its calling order):
432 |
433 | ```
434 | sub_function(arg1, arg2, arg3);
435 |
436 | | .... | high address
437 | +---------------+
438 | | arg: 1 | new_bp + 4
439 | +---------------+
440 | | arg: 2 | new_bp + 3
441 | +---------------+
442 | | arg: 3 | new_bp + 2
443 | +---------------+
444 | |return address | new_bp + 1
445 | +---------------+
446 | | old BP | <- new BP
447 | +---------------+
448 | | local var 1 | new_bp - 1
449 | +---------------+
450 | | local var 2 | new_bp - 2
451 | +---------------+
452 | | .... | low address
453 | ```
454 |
455 | So if we need to refer to `arg1`, we need to fetch `new_bp + 4`, which however
456 | cannot be achieved by our poor `ADD` instruction. Thus we will make yet
457 | another special `ADD` to do this: `LEA `.
458 |
459 | ```c
460 | else if (op == LEA) {ax = (int)(bp + *pc++);} // load address for arguments.
461 | ```
462 |
463 | Together with the instructions above, we are able to make function calls.
464 |
465 | ### Mathmetical Instructions
466 |
467 | Our VM will provide an instruction for each operators in C language. Each
468 | operator has two arguments: the first one is stored on the top of the stack
469 | while the second is stored in `AX`. The order matters especially in operators
470 | like `-`, `/`. After the calculation is done, the argument on the stack will
471 | be poped out and the result will be stored in `AX`. So you are not able to
472 | fetch the first argument from the stack after the calculation, please note
473 | that.
474 |
475 | ```c
476 | else if (op == OR) ax = *sp++ | ax;
477 | else if (op == XOR) ax = *sp++ ^ ax;
478 | else if (op == AND) ax = *sp++ & ax;
479 | else if (op == EQ) ax = *sp++ == ax;
480 | else if (op == NE) ax = *sp++ != ax;
481 | else if (op == LT) ax = *sp++ < ax;
482 | else if (op == LE) ax = *sp++ <= ax;
483 | else if (op == GT) ax = *sp++ > ax;
484 | else if (op == GE) ax = *sp++ >= ax;
485 | else if (op == SHL) ax = *sp++ << ax;
486 | else if (op == SHR) ax = *sp++ >> ax;
487 | else if (op == ADD) ax = *sp++ + ax;
488 | else if (op == SUB) ax = *sp++ - ax;
489 | else if (op == MUL) ax = *sp++ * ax;
490 | else if (op == DIV) ax = *sp++ / ax;
491 | else if (op == MOD) ax = *sp++ % ax;
492 | ```
493 |
494 | ### Built-in Instructions
495 |
496 | Besides core logic, a program will need input/output mechanism to be
497 | able to interact with. `printf` in C is one of the commonly used output
498 | functions. `printf` is very complex to implement but unavoidable if our
499 | compiler wants to be bootstraping(interpret itself) yet it is meaningless for
500 | building a compiler.
501 |
502 | Our plan is to create new instructions to build a bridge between the
503 | interpreted program and the interpreter itself. So that we can utilize the
504 | libraries of the host system(your computer that runs the interpreter).
505 |
506 | We'll need `exit`, `open`, `close`, `read`, `printf`, `malloc`, `memset` and `memcmp`:
507 |
508 | ```c
509 | else if (op == EXIT) { printf("exit(%d)", *sp); return *sp;}
510 | else if (op == OPEN) { ax = open((char *)sp[1], sp[0]); }
511 | else if (op == CLOS) { ax = close(*sp);}
512 | else if (op == READ) { ax = read(sp[2], (char *)sp[1], *sp); }
513 | else if (op == PRTF) { tmp = sp + pc[1]; ax = printf((char *)tmp[-1], tmp[-2], tmp[-3], tmp[-4], tmp[-5], tmp[-6]); }
514 | else if (op == MALC) { ax = (int)malloc(*sp);}
515 | else if (op == MSET) { ax = (int)memset((char *)sp[2], sp[1], *sp);}
516 | else if (op == MCMP) { ax = memcmp((char *)sp[2], (char *)sp[1], *sp);}
517 | ```
518 |
519 | At last, add some error handling:
520 |
521 | ```c
522 | else {
523 | printf("unknown instruction:%d\n", op);
524 | return -1;
525 | }
526 | ```
527 |
528 | ## Test
529 |
530 | Now we'll do some "assembly programing" to calculate `10 + 20`:
531 |
532 | ```c
533 | int main(int argc, char *argv[])
534 | {
535 | ax = 0;
536 | ...
537 | i = 0;
538 | text[i++] = IMM;
539 | text[i++] = 10;
540 | text[i++] = PUSH;
541 | text[i++] = IMM;
542 | text[i++] = 20;
543 | text[i++] = ADD;
544 | text[i++] = PUSH;
545 | text[i++] = EXIT;
546 | pc = text;
547 | ...
548 | program();
549 | }
550 | ```
551 |
552 | Compile the interpreter with `gcc xc-tutor.c` and run it with `./a.out
553 | hello.c`, got the following result:
554 |
555 | ```
556 | exit(30)
557 | ```
558 |
559 | Note that we specified `hello.c` but it is actually not used, we need it
560 | because the interpreter we build in last chapter needs it.
561 |
562 | Well, it seems that our VM works well :)
563 |
564 | ## Summary
565 |
566 | We learned how computer works internally and build our own instruction set
567 | modeled after `x86` assembly instructions. We are actually trying to learn
568 | assembly language and how it actually work by building our own version.
569 |
570 |
571 | The code for this chapter can be downloaded from
572 | [Github](https://github.com/lotabout/write-a-C-interpreter/tree/step-1), or
573 | clone by:
574 |
575 | ```
576 | git clone -b step-1 https://github.com/lotabout/write-a-C-interpreter
577 | ```
578 |
579 | Note that adding a new instruction would require designing lots of circuits
580 | and cost a lot. But it is almost free to add new instructions in our virtual
581 | machine. We are taking advantage of this spliting the functions of an
582 | intruction into several to simplify the implementation.
583 |
584 | If you are interested, build your own instruction sets!
585 |
--------------------------------------------------------------------------------
/tutorial/en/3-Lexer.md:
--------------------------------------------------------------------------------
1 | > lexical analysis is the process of converting a sequence of characters (such
2 | > as in a computer program or web page) into a sequence of tokens (strings with
3 | > an identified "meaning").
4 |
5 | Normally we represent the token as a pair: `(token type, token value)`. For
6 | example, if a program's source file contains string: "998", the lexer will
7 | treat it as token `(Number, 998)` meaning it is a number with value of `998`.
8 |
9 |
10 | ## Lexer vs Compiler
11 |
12 | Let's first look at the structure of a compiler:
13 |
14 | ```
15 | +-------+ +--------+
16 | -- source code --> | lexer | --> token stream --> | parser | --> assembly
17 | +-------+ +--------+
18 | ```
19 |
20 | The Compiler can be treated as a transformer that transform C source code into
21 | assembly. In this sense, lexer and parser are transformers as well: Lexer
22 | takes C source code as input and output token stream; Parser will consume the
23 | token stream and generate assembly code.
24 |
25 | Then why do we need lexer and a parser? Well the Compiler's job is hard! So we
26 | recruit lexer to do part of the job and parser to do the rest so that each
27 | will need to deal with simple one only.
28 |
29 | That's the value of a lexer: to simplify the parser by converting the stream
30 | of source code into token stream.
31 |
32 | ## Implementation Choice
33 |
34 | Before we start I want to let you know that crafting a lexer is boring and
35 | error-prone. That's why geniuses out there had already created automation
36 | tools to do the job. `lex/flex` are example that allows us to describe the
37 | lexical rules using regular expressions and generate lexer for us.
38 |
39 | Also note that we won't follow the graph in the section above, i.e. not
40 | converting all source code into token stream at once. The reasons are:
41 |
42 | 1. Converting source code into token stream is stateful. How a string is
43 | interpreted is related with the place where it appears.
44 | 2. It is a waste to store all the tokens because only a few of them will be
45 | accessed at the same time.
46 |
47 | Thus we'll implement one function: `next()` that returns a token in one call.
48 |
49 | ## Tokens Supported
50 |
51 | Add the definition into the global area:
52 |
53 | ```c
54 | // tokens and classes (operators last and in precedence order)
55 | enum {
56 | Num = 128, Fun, Sys, Glo, Loc, Id,
57 | Char, Else, Enum, If, Int, Return, Sizeof, While,
58 | Assign, Cond, Lor, Lan, Or, Xor, And, Eq, Ne, Lt, Gt, Le, Ge, Shl, Shr, Add, Sub, Mul, Div, Mod, Inc, Dec, Brak
59 | };
60 | ```
61 |
62 | These are all the tokens our lexer can understand. Our lexer will interpret
63 | string `=` as token `Assign`; `==` as token `Eq`; `!=` as `Ne`; etc.
64 |
65 | So we have the impression that a token will contain one or more characters.
66 | That is the reason why lexer can reduce the complexity, now the parser doesn't
67 | have to look at several character to identify a the meaning of a substring.
68 | The job had been done.
69 |
70 | Of course, the tokens above is properly ordered reflecting their priority in
71 | the C programming language. `*(Mul)` operator for example has higher priority
72 | the `+(Add)` operator. We'll talk about it later.
73 |
74 | At last, there are some characters we don't included here are themselves a
75 | token such as `]` or `~`. The reason that we done encode them like others are:
76 |
77 | 1. These tokens contains only single character, thus are easier to identify.
78 | 2. They are not involved into the priority battle.
79 |
80 | ## Skeleton of Lexer
81 |
82 | ```c
83 | void next() {
84 | char *last_pos;
85 | int hash;
86 | while (token = *src) {
87 | ++src;
88 | // parse token here
89 | }
90 | return;
91 | }
92 | ```
93 |
94 | While do we need `while` here knowing that `next` will only parse one token?
95 | This raises a quesion in compiler construction(remember that lexer is kind of
96 | compiler?): How to handle error?
97 |
98 | Normally we had two solutions:
99 |
100 | 1. points out where the error happans and quit.
101 | 2. points out where the error happans, skip it, and continue.
102 |
103 | That will explain the existance of `while`: to skip unknown characters in the
104 | source code. Meanwhile it is used to skip whitespaces which is not the actual
105 | part of a program. They are treated as separators only.
106 |
107 | ## Newline
108 |
109 | It is quite like space in that we are skipping it. The only difference is that
110 | we need to increase the line number once a newline character is met:
111 |
112 | ```c
113 | // parse token here
114 | ...
115 | if (token == '\n') {
116 | ++line;
117 | }
118 | ...
119 | ```
120 |
121 | ## Macros
122 |
123 | Macros in C starts with character `#` such as `#include `. Our
124 | compiler don't support any macros, so we'll skip all of them:
125 |
126 | ```c
127 | else if (token == '#') {
128 | // skip macro, because we will not support it
129 | while (*src != 0 && *src != '\n') {
130 | src++;
131 | }
132 | }
133 | ```
134 |
135 | ## Identifers and Symbol Table
136 |
137 | Identifier is the name of a variable. But we don't actually care about the
138 | names in lexer, we cares about the identity. For example: `int a;`
139 | declares a variable, we have to know that the statement `a = 10` that comes
140 | after refers to the same variable that we declared before.
141 |
142 | Based on this reason, we'll make a table to store all the names we've already
143 | met and call it Symbol Table. We'll look up the table when a new
144 | name/identifier is accountered. If the name exists in the symbol table, the
145 | identity is returned.
146 |
147 | Then how to represent an identity?
148 |
149 | ```c
150 | struct identifier {
151 | int token;
152 | int hash;
153 | char * name;
154 | int class;
155 | int type;
156 | int value;
157 | int Bclass;
158 | int Btype;
159 | int Bvalue;
160 | }
161 | ```
162 |
163 | We'll need a little explanation here:
164 |
165 | 1. `token`: is the token type of an identifier. Theoretically it should be
166 | fixed to type `Id`. But it is not true because we will add keywords(e.g
167 | `if`, `while`) as special kinds of identifier.
168 | 2. `hash`: the hash value of the name of the identifier, to speed up the
169 | comparision of table lookup.
170 | 3. `name`: well, name of the identifier.
171 | 4. `class`: Whether the identifier is global, local or constants.
172 | 5. `type`: type of the identifier, `int`, `char` or pointer.
173 | 6. `value`: the value of the variable that the identifier points to.
174 | 7. `BXXX`: local variable can shadow global variable. It is used to store
175 | global ones if that happens.
176 |
177 | Traditional symbol table will contain only the unique identifer while our
178 | symbol table stores other information that will only be accessed by parser
179 | such as `type`.
180 |
181 | Yet sadly, our compiler do not support `struct` while we are trying to be
182 | bootstrapping. So we have to compromise in the actual structure of an
183 | identifier:
184 |
185 | ```
186 | Symbol table:
187 | ----+-----+----+----+----+-----+-----+-----+------+------+----
188 | .. |token|hash|name|type|class|value|btype|bclass|bvalue| ..
189 | ----+-----+----+----+----+-----+-----+-----+------+------+----
190 | |<--- one single identifier --->|
191 | ```
192 |
193 | That means we use a single `int` array to store all identifier information.
194 | Each ID will use 9 cells. The code is as following:
195 |
196 | ```c
197 | int token_val; // value of current token (mainly for number)
198 | int *current_id, // current parsed ID
199 | *symbols; // symbol table
200 | // fields of identifier
201 | enum {Token, Hash, Name, Type, Class, Value, BType, BClass, BValue, IdSize};
202 | void next() {
203 | ...
204 | else if ((token >= 'a' && token <= 'z') || (token >= 'A' && token <= 'Z') || (token == '_')) {
205 | // parse identifier
206 | last_pos = src - 1;
207 | hash = token;
208 | while ((*src >= 'a' && *src <= 'z') || (*src >= 'A' && *src <= 'Z') || (*src >= '0' && *src <= '9') || (*src == '_')) {
209 | hash = hash * 147 + *src;
210 | src++;
211 | }
212 | // look for existing identifier, linear search
213 | current_id = symbols;
214 | while (current_id[Token]) {
215 | if (current_id[Hash] == hash && !memcmp((char *)current_id[Name], last_pos, src - last_pos)) {
216 | //found one, return
217 | token = current_id[Token];
218 | return;
219 | }
220 | current_id = current_id + IdSize;
221 | }
222 | // store new ID
223 | current_id[Name] = (int)last_pos;
224 | current_id[Hash] = hash;
225 | token = current_id[Token] = Id;
226 | return;
227 | }
228 | ...
229 | }
230 | ```
231 |
232 | Note that the search in symbol table is linear search.
233 |
234 | ## Number
235 |
236 | We need to support decimal, hexadecimal and octal. The logic is quite
237 | straightforward except how to get the hexadecimal value. Maybe..
238 |
239 | ```c
240 | token_val = token_val * 16 + (token & 0x0F) + (token >= 'A' ? 9 : 0);
241 | ```
242 |
243 | In case you are not familiar with this "small trick", `a`'s hex value is `61`
244 | in ASCII while `A` is `41`. Thus `token & 0x0F` can get the the smallest digit
245 | out of the character.
246 |
247 | ```c
248 | void next() {
249 | ...
250 |
251 |
252 |
253 | else if (token >= '0' && token <= '9') {
254 | // parse number, three kinds: dec(123) hex(0x123) oct(017)
255 | token_val = token - '0';
256 | if (token_val > 0) {
257 | // dec, starts with [1-9]
258 | while (*src >= '0' && *src <= '9') {
259 | token_val = token_val*10 + *src++ - '0';
260 | }
261 | } else {
262 | // starts with number 0
263 | if (*src == 'x' || *src == 'X') {
264 | //hex
265 | token = *++src;
266 | while ((token >= '0' && token <= '9') || (token >= 'a' && token <= 'f') || (token >= 'A' && token <= 'F')) {
267 | token_val = token_val * 16 + (token & 15) + (token >= 'A' ? 9 : 0);
268 | token = *++src;
269 | }
270 | } else {
271 | // oct
272 | while (*src >= '0' && *src <= '7') {
273 | token_val = token_val*8 + *src++ - '0';
274 | }
275 | }
276 | }
277 | token = Num;
278 | return;
279 | }
280 |
281 |
282 | ...
283 | }
284 | ```
285 |
286 |
287 | ## String Literals
288 |
289 | If we find any string literal, we need to store it into the `data segment`
290 | that we introduced in a previous chapter and return the address. Another issue
291 | is we need to care about escaped characters such as `\n` to represent newline
292 | character. But we don't support escaped characters other than `\n` like `\t`
293 | or `\r`because we aim at bootstrapping only. Note that we still support
294 | syntax that `\x` to be character `x` itself.
295 |
296 | Our lexer will analyze single character (e.g. `'a'`) at the same time. Once
297 | character is found, we return it as a `Num`.
298 |
299 | ```c
300 | void next() {
301 | ...
302 |
303 | else if (token == '"' || token == '\'') {
304 | // parse string literal, currently, the only supported escape
305 | // character is '\n', store the string literal into data.
306 | last_pos = data;
307 | while (*src != 0 && *src != token) {
308 | token_val = *src++;
309 | if (token_val == '\\') {
310 | // escape character
311 | token_val = *src++;
312 | if (token_val == 'n') {
313 | token_val = '\n';
314 | }
315 | }
316 | if (token == '"') {
317 | *data++ = token_val;
318 | }
319 | }
320 |
321 | src++;
322 | // if it is a single character, return Num token
323 | if (token == '"') {
324 | token_val = (int)last_pos;
325 | } else {
326 | token = Num;
327 | }
328 |
329 | return;
330 | }
331 | }
332 | ```
333 |
334 | ## Comments
335 |
336 | Only C++ style comments(e.g. `// comment`) is supported. C style (`/* ... */`)
337 | is not supported.
338 |
339 | ```c
340 | void next() {
341 | ...
342 |
343 | else if (token == '/') {
344 | if (*src == '/') {
345 | // skip comments
346 | while (*src != 0 && *src != '\n') {
347 | ++src;
348 | }
349 | } else {
350 | // divide operator
351 | token = Div;
352 | return;
353 | }
354 | }
355 |
356 | ...
357 | }
358 | ```
359 |
360 | Now we'll introduce the concept: `lookahead`. In the above code we see that
361 | for source code starting with character `/`, either 'comment' or `/(Div)` may
362 | be encountered.
363 |
364 | Sometimes we cannot decide which token to generate by only looking at the current
365 | character (such as the above example about divide and comment), thus we need to
366 | check the next character (called `lookahead`) in order to determine. In our
367 | example, if it is another slash `/`, then we've encountered a comment line,
368 | otherwise it is a divide operator.
369 |
370 | Like we've said that a lexer and a parser are inherently a kind of compiler,
371 | `lookahead` also exists in parsers. However parsers will look ahead for "token"
372 | instead of "character". The `k` in `LL(k)` of compiler theory is the amount of
373 | tokens a parser needs to look ahead.
374 |
375 | Also if we don't split the compiler into a lexer and a parser, the compiler will have
376 | to look ahead a lot of character to decide what to do next. So we can say that
377 | a lexer reduces the amount of lookahead a compiler needs to check.
378 |
379 | ## Others
380 |
381 | Others are simpiler and straightforward, check the code:
382 |
383 | ```c
384 | void next() {
385 | ...
386 | else if (token == '=') {
387 | // parse '==' and '='
388 | if (*src == '=') {
389 | src ++;
390 | token = Eq;
391 | } else {
392 | token = Assign;
393 | }
394 | return;
395 | }
396 | else if (token == '+') {
397 | // parse '+' and '++'
398 | if (*src == '+') {
399 | src ++;
400 | token = Inc;
401 | } else {
402 | token = Add;
403 | }
404 | return;
405 | }
406 | else if (token == '-') {
407 | // parse '-' and '--'
408 | if (*src == '-') {
409 | src ++;
410 | token = Dec;
411 | } else {
412 | token = Sub;
413 | }
414 | return;
415 | }
416 | else if (token == '!') {
417 | // parse '!='
418 | if (*src == '=') {
419 | src++;
420 | token = Ne;
421 | }
422 | return;
423 | }
424 | else if (token == '<') {
425 | // parse '<=', '<<' or '<'
426 | if (*src == '=') {
427 | src ++;
428 | token = Le;
429 | } else if (*src == '<') {
430 | src ++;
431 | token = Shl;
432 | } else {
433 | token = Lt;
434 | }
435 | return;
436 | }
437 | else if (token == '>') {
438 | // parse '>=', '>>' or '>'
439 | if (*src == '=') {
440 | src ++;
441 | token = Ge;
442 | } else if (*src == '>') {
443 | src ++;
444 | token = Shr;
445 | } else {
446 | token = Gt;
447 | }
448 | return;
449 | }
450 | else if (token == '|') {
451 | // parse '|' or '||'
452 | if (*src == '|') {
453 | src ++;
454 | token = Lor;
455 | } else {
456 | token = Or;
457 | }
458 | return;
459 | }
460 | else if (token == '&') {
461 | // parse '&' and '&&'
462 | if (*src == '&') {
463 | src ++;
464 | token = Lan;
465 | } else {
466 | token = And;
467 | }
468 | return;
469 | }
470 | else if (token == '^') {
471 | token = Xor;
472 | return;
473 | }
474 | else if (token == '%') {
475 | token = Mod;
476 | return;
477 | }
478 | else if (token == '*') {
479 | token = Mul;
480 | return;
481 | }
482 | else if (token == '[') {
483 | token = Brak;
484 | return;
485 | }
486 | else if (token == '?') {
487 | token = Cond;
488 | return;
489 | }
490 | else if (token == '~' || token == ';' || token == '{' || token == '}' || token == '(' || token == ')' || token == ']' || token == ',' || token == ':') {
491 | // directly return the character as token;
492 | return;
493 | }
494 |
495 | ...
496 | }
497 | ```
498 |
499 | ## Keywords and Builtin Functions
500 |
501 | Keywords such as `if`, `while` or `return` are special because they are known
502 | by the compiler in advance. We cannot treat them like normal identifiers
503 | because the special meanings in it. There are two ways to deal with it:
504 |
505 | 1. Let lexer parse them and return a token to identify them.
506 | 2. Treat them as normal identifier but store them into the symbol table in
507 | advance.
508 |
509 | We choose the second way: add corresponding identifers into symbol table in
510 | advance and set the needed properties(e.g. the `Token` type we mentioned). So
511 | that when keywords are encountered in the source code, they will be interpreted
512 | as identifiers, but since they already exist in the symbol table we can know
513 | that they are different from normal identifiers.
514 |
515 | Builtin function are similar. They are only different in the internal
516 | information. In the main function, add the following:
517 |
518 | ```c
519 | // types of variable/function
520 | enum { CHAR, INT, PTR };
521 | int *idmain; // the `main` function
522 | int main(int argc, char **argv) {
523 | ...
524 |
525 | src = "char else enum if int return sizeof while "
526 | "open read close printf malloc memset memcmp exit void main";
527 |
528 |
529 | // add keywords to symbol table
530 | i = Char;
531 | while (i <= While) {
532 | next();
533 | current_id[Token] = i++;
534 | }
535 |
536 | // add library to symbol table
537 | i = OPEN;
538 | while (i <= EXIT) {
539 | next();
540 | current_id[Class] = Sys;
541 | current_id[Type] = INT;
542 | current_id[Value] = i++;
543 | }
544 |
545 | next(); current_id[Token] = Char; // handle void type
546 | next(); idmain = current_id; // keep track of main
547 |
548 | ...
549 | program();
550 | return eval();
551 | }
552 | ```
553 |
554 | ## Code
555 |
556 | You can check out the code on
557 | [Github](https://github.com/lotabout/write-a-C-interpreter/tree/step-2), or
558 | clone with:
559 |
560 | ```
561 | git clone -b step-2 https://github.com/lotabout/write-a-C-interpreter
562 | ```
563 |
564 | Executing the code will give 'Segmentation Falt' because it will try to
565 | execute the virtual machine that we build in previous chapter which will not
566 | work because it doesn't contain any runnable code.
567 |
568 | ## Summary
569 |
570 | 1. Lexer is used to pre-process the source code, so as to reduce the
571 | complexity of parser.
572 | 2. Lexer is also a kind of compiler which consumes source code and output
573 | token stream.
574 | 3. `lookahead(k)` is used to fully determine the meaning of current
575 | character/token.
576 | 4. How to represent identifier and symbol table.
577 |
578 | We will discuss about top-down recursive parser. See you then :)
579 |
--------------------------------------------------------------------------------
/tutorial/en/4-Top-down-Parsing.md:
--------------------------------------------------------------------------------
1 | In this chapter we will build a simple calculator using the top-down parsing
2 | technique. This is the preparation before we start to implement the parser.
3 |
4 | I will introduce a small set of theories but will not gurantee to be absolutely
5 | correct, please consult your textbook if you have any confusion.
6 |
7 | ## Top-down parsing
8 |
9 | Traditionally, we have top-down parsing and bottom-up parsing. The top-down
10 | method will start with a non-terminator and recursively check the source code to
11 | replace the non-terminators with its alternatives until no non-terminator is
12 | left.
13 |
14 | You see I used the top-down method for explaining "top-down" because you'll
15 | have to know what a "non-terminator" is to understand the above paragraph. But I
16 | havn't told you what that is. We will explain in the next section. For now,
17 | consider "top-down" is trying to tear down a big object into small pieces.
18 |
19 | On the other hand "bottom-up" parsing is trying to combine small objects into
20 | a big one. It is often used in automation tools that generate parsers.
21 |
22 | ## Terminator and Non-terminator
23 |
24 | They are terms used in
25 | [BNF](https://en.wikipedia.org/wiki/Backus%E2%80%93Naur_Form) (Backus–Naur
26 | Form) which is a language used to describe grammars. A simple elementary
27 | arithmetic calulater in BNF will be:
28 |
29 | ```
30 | ::= +
31 | | -
32 | |
33 |
34 | ::= *
35 | | /
36 | |
37 |
38 | ::= ( )
39 | | Num
40 | ```
41 |
42 | The item enclosed by `<>` is called a `Non-terminator`. They got the name
43 | because we can replace them with the items on the right hand of `::=`.
44 | `|` means alternative that means you can replace `` with any one of
45 | ` * `, ` / ` or ``. Those do not appear on
46 | the left side of `::=` is called `Terminator` such as `+`, `(`, `Num`, etc.
47 | They often corresponds to the tokens we got from the lexer.
48 |
49 | ## Top-down Example for Simple Calculator
50 |
51 | The parse tree is the inner structure we get after the parser consumes all the
52 | tokens and finishes all the parsing. Let's take `3 * (4 + 2)` as an example to
53 | show the connections between BNF grammer, parse tree and top-down parsing.
54 |
55 | Top-down parsing starts from a starting non-terminator which is `` in
56 | our example. You can specify it in practice, but also defaults to the first
57 | non-terminator we encountered.
58 |
59 | ```
60 | 1. =>
61 | 2. => *
62 | 3. => |
63 | 4. => Num (3) |
64 | 5. => ( )
65 | 6. => +
66 | 7. => |
67 | 8. => |
68 | 9. => Num (4) |
69 | 10. =>
70 | 11. => Num (2)
71 | ```
72 |
73 | You can see that each step we replace a non-terminator using one of its
74 | alternatives (top-down) Until all of the sub-items are replaced by
75 | terminators (bottom). Some non-terminators are used recursively such as
76 | ``.
77 |
78 | ## Advantages of Top-down Parsing
79 |
80 | As you can see in the above example, the parsing step is similar to the BNF
81 | grammar. Which means it is easy to convert the grammar into actual code by
82 | converting a production rule (`<...> ::= ...`) into a function with the same
83 | name.
84 |
85 | One question arises here: how do you know which alternative to apply? Why do
86 | you choose ` ::= * ` over ` ::= / `?
87 | That's right, we `lookahead`! We peek the next token and it is `*` so it is the
88 | first one to apply.
89 |
90 | However, top-down parsing requires the grammar should not have left-recursion.
91 |
92 | ## Left-recursion
93 |
94 | Suppose we have a grammer like this:
95 |
96 | ```
97 | ::= + Num
98 | ```
99 |
100 | we can translate it into function:
101 |
102 | ```
103 | int expr() {
104 | expr();
105 | num();
106 | }
107 | ```
108 |
109 | As you can see, function `expr` will never exit! In the grammar,
110 | non-terminator `` is used recursively and appears immediately after
111 | `::=` which causes left-recursion.
112 |
113 | Luckly, most left-recursive grammers (maybe all? I don't remember) can be
114 | properly transformed into non left-recursive equivalent ones. Our grammar for
115 | calculator can be converted into:
116 |
117 | ```
118 | ::=
119 |
120 | ::= +
121 | | -
122 | |
123 |
124 | ::=
125 |
126 | ::= *
127 | | /
128 | |
129 |
130 | ::= ( )
131 | | Num
132 | ```
133 |
134 | You should check out your textbook for more information.
135 |
136 | ## Implementation
137 |
138 | The following code is directly converted from the grammar. Notice how
139 | straightforward it is:
140 |
141 | ```c
142 | int expr();
143 |
144 | int factor() {
145 | int value = 0;
146 | if (token == '(') {
147 | match('(');
148 | value = expr();
149 | match(')');
150 | } else {
151 | value = token_val;
152 | match(Num);
153 | }
154 | return value;
155 | }
156 |
157 | int term_tail(int lvalue) {
158 | if (token == '*') {
159 | match('*');
160 | int value = lvalue * factor();
161 | return term_tail(value);
162 | } else if (token == '/') {
163 | match('/');
164 | int value = lvalue / factor();
165 | return term_tail(value);
166 | } else {
167 | return lvalue;
168 | }
169 | }
170 |
171 | int term() {
172 | int lvalue = factor();
173 | return term_tail(lvalue);
174 | }
175 |
176 | int expr_tail(int lvalue) {
177 | if (token == '+') {
178 | match('+');
179 | int value = lvalue + term();
180 | return expr_tail(value);
181 | } else if (token == '-') {
182 | match('-');
183 | int value = lvalue - term();
184 | return expr_tail(value);
185 | } else {
186 | return lvalue;
187 | }
188 | }
189 |
190 | int expr() {
191 | int lvalue = term();
192 | return expr_tail(lvalue);
193 | }
194 | ```
195 |
196 | Implmenting a top-down parser is straightforward with the help of BNF grammar.
197 | We now add the code for lexer:
198 |
199 | ```c
200 | #include
201 | #include
202 |
203 | enum {Num};
204 | int token;
205 | int token_val;
206 | char *line = NULL;
207 | char *src = NULL;
208 |
209 | void next() {
210 | // skip white space
211 | while (*src == ' ' || *src == '\t') {
212 | src ++;
213 | }
214 |
215 | token = *src++;
216 |
217 | if (token >= '0' && token <= '9' ) {
218 | token_val = token - '0';
219 | token = Num;
220 |
221 | while (*src >= '0' && *src <= '9') {
222 | token_val = token_val*10 + *src - '0';
223 | src ++;
224 | }
225 | return;
226 | }
227 | }
228 |
229 | void match(int tk) {
230 | if (token != tk) {
231 | printf("expected token: %d(%c), got: %d(%c)\n", tk, tk, token, token);
232 | exit(-1);
233 | }
234 |
235 | next();
236 | }
237 | ```
238 |
239 | Finally, the `main` method to bootstrap:
240 |
241 | ```c
242 | int main(int argc, char *argv[])
243 | {
244 | size_t linecap = 0;
245 | ssize_t linelen;
246 | while ((linelen = getline(&line, &linecap, stdin)) > 0) {
247 | src = line;
248 | next();
249 | printf("%d\n", expr());
250 | }
251 | return 0;
252 | }
253 | ```
254 |
255 | You can play with your own calculator now. Or try to add some more functions
256 | based on what we've learned in the previous chapter. Such as variable support
257 | so that a user can define variables to store values.
258 |
259 | ## Summary
260 |
261 | We don't like theory, but it exists for good reason as you can see that BNF can
262 | help us to build the parser. So I want to convice you to learn some theories,
263 | it will help you to become a better programmer.
264 |
265 | Top-down parsing technique is often used in manually crafting of parsers, so
266 | you are able to handle most jobs if you master it! As you'll see in laster
267 | chapters.
268 |
--------------------------------------------------------------------------------
/tutorial/en/5-Variables.md:
--------------------------------------------------------------------------------
1 | In this chapter we are going to use EBNF to describe the grammer of our C
2 | interpreter, and add the support of variables.
3 |
4 | The parser is more complicated than the lexer, thus we will split it into 3 parts:
5 | variables, functions and expressions.
6 |
7 | ## EBNF grammar
8 |
9 | We've talked about BNF in the previous chapter,
10 | [EBNF](https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_Form) is
11 | Extended-BNF. If you are familiar with regular expression, you should feel
12 | right at home. Personally I think it is more powerful and straightforward than
13 | BNF. Here is the EBNF grammar of our C interpreter, feel free to skip it if
14 | you feel it's too hard to understand.
15 |
16 | ```
17 | program ::= {global_declaration}+
18 |
19 | global_declaration ::= enum_decl | variable_decl | function_decl
20 |
21 | enum_decl ::= 'enum' [id] '{' id ['=' 'num'] {',' id ['=' 'num'] '}'
22 |
23 | variable_decl ::= type {'*'} id { ',' {'*'} id } ';'
24 |
25 | function_decl ::= type {'*'} id '(' parameter_decl ')' '{' body_decl '}'
26 |
27 | parameter_decl ::= type {'*'} id {',' type {'*'} id}
28 |
29 | body_decl ::= {variable_decl}, {statement}
30 |
31 | statement ::= non_empty_statement | empty_statement
32 |
33 | non_empty_statement ::= if_statement | while_statement | '{' statement '}'
34 | | 'return' expression | expression ';'
35 |
36 | if_statement ::= 'if' '(' expression ')' statement ['else' non_empty_statement]
37 |
38 | while_statement ::= 'while' '(' expression ')' non_empty_statement
39 | ```
40 |
41 | We'll leave `expression` to later chapters. Our grammar won't support
42 | function *declaration*, that means recursive calling between functions are not
43 | supported. And since we're bootstrapping, that means our code for
44 | implementation cannot use any cross-function recursions. (Sorry for the whole
45 | chapter of top-down recursive parsers.)
46 |
47 | In this chapter, we'll implement the `enum_decl` and `variable_decl`.
48 |
49 | ## program()
50 |
51 | We've already defined function `program`, turn it into:
52 |
53 | ```c
54 | void program() {
55 | // get next token
56 | next();
57 | while (token > 0) {
58 | global_declaration();
59 | }
60 | }
61 | ```
62 |
63 | I know that we havn't defined `global_declaration`, sometimes we need wishful
64 | thinking that maybe someone (say Bob) will implement that for you. So you can
65 | focus on the big picture at first instead of drill down into all the details.
66 | That's the essence of top-down thinking.
67 |
68 | ## global_declaration()
69 |
70 | Now it is our duty (not Bob's) to implement `global_declaration`. It will try
71 | to parse variable definitions, type definitions (only enum is supported) and
72 | function definitions:
73 |
74 | ```c
75 | int basetype; // the type of a declaration, make it global for convenience
76 | int expr_type; // the type of an expression
77 |
78 | void global_declaration() {
79 | // global_declaration ::= enum_decl | variable_decl | function_decl
80 | //
81 | // enum_decl ::= 'enum' [id] '{' id ['=' 'num'] {',' id ['=' 'num'} '}'
82 | //
83 | // variable_decl ::= type {'*'} id { ',' {'*'} id } ';'
84 | //
85 | // function_decl ::= type {'*'} id '(' parameter_decl ')' '{' body_decl '}'
86 |
87 |
88 | int type; // tmp, actual type for variable
89 | int i; // tmp
90 |
91 | basetype = INT;
92 |
93 | // parse enum, this should be treated alone.
94 | if (token == Enum) {
95 | // enum [id] { a = 10, b = 20, ... }
96 | match(Enum);
97 | if (token != '{') {
98 | match(Id); // skip the [id] part
99 | }
100 | if (token == '{') {
101 | // parse the assign part
102 | match('{');
103 | enum_declaration();
104 | match('}');
105 | }
106 |
107 | match(';');
108 | return;
109 | }
110 |
111 | // parse type information
112 | if (token == Int) {
113 | match(Int);
114 | }
115 | else if (token == Char) {
116 | match(Char);
117 | basetype = CHAR;
118 | }
119 |
120 | // parse the comma seperated variable declaration.
121 | while (token != ';' && token != '}') {
122 | type = basetype;
123 | // parse pointer type, note that there may exist `int ****x;`
124 | while (token == Mul) {
125 | match(Mul);
126 | type = type + PTR;
127 | }
128 |
129 | if (token != Id) {
130 | // invalid declaration
131 | printf("%d: bad global declaration\n", line);
132 | exit(-1);
133 | }
134 | if (current_id[Class]) {
135 | // identifier exists
136 | printf("%d: duplicate global declaration\n", line);
137 | exit(-1);
138 | }
139 | match(Id);
140 | current_id[Type] = type;
141 |
142 | if (token == '(') {
143 | current_id[Class] = Fun;
144 | current_id[Value] = (int)(text + 1); // the memory address of function
145 | function_declaration();
146 | } else {
147 | // variable declaration
148 | current_id[Class] = Glo; // global variable
149 | current_id[Value] = (int)data; // assign memory address
150 | data = data + sizeof(int);
151 | }
152 |
153 | if (token == ',') {
154 | match(',');
155 | }
156 | }
157 | next();
158 | }
159 | ```
160 |
161 | Well, that's more than two screen of code! I think that is a direct
162 | translation of the grammar. But to help you understand, I'll explain some of
163 | them:
164 |
165 | **Lookahead Token**: The `if (token == xxx)` statement is used to peek the
166 | next token to decide which production rule to use. For example, if token
167 | `enum` is met, we know it is enumeration that we are trying to parse. But if a
168 | type if parsed such as `int identifier`, we still cannot tell whether
169 | `identifier` is a variable or function. Thus the parser should continue to
170 | look ahead for the next token, if `(` is met, we are now sure `identifier` is
171 | a function, otherwise it is a variable.
172 |
173 | **Variable Type**: Our C interpreter supports pointers, that means pointers
174 | that points to other pointers are also supported such as `int **data;`. How do
175 | we represents them in code? We've already defined the types that we support:
176 |
177 | ```c
178 | // types of variable/function
179 | enum { CHAR, INT, PTR };
180 | ```
181 |
182 | So we will use an `int` to store the type. It starts with a base type: `CHAR`
183 | or `INT`. When the type is a pointer that points to a base type such as `int
184 | *data;` we add `PTR` to it: `type = type + PTR;`. The same goes to the pointer
185 | of pointer, we add another `PTR` to the type, etc.
186 |
187 | ## enum_declaration
188 |
189 | The main logic is trying to parse the `,` seperated variables. You need to pay
190 | attention to the representation of enumerations.
191 |
192 | We will store an enumeration as a global variable. However its `type` is set to
193 | `Num` instead of `Glo` to make it a constant instead of a normal global
194 | variable. The `type` information will later be used in parsing `expression`s.
195 |
196 | ```c
197 | void enum_declaration() {
198 | // parse enum [id] { a = 1, b = 3, ...}
199 | int i;
200 | i = 0;
201 | while (token != '}') {
202 | if (token != Id) {
203 | printf("%d: bad enum identifier %d\n", line, token);
204 | exit(-1);
205 | }
206 | next();
207 | if (token == Assign) {
208 | // like {a=10}
209 | next();
210 | if (token != Num) {
211 | printf("%d: bad enum initializer\n", line);
212 | exit(-1);
213 | }
214 | i = token_val;
215 | next();
216 | }
217 |
218 | current_id[Class] = Num;
219 | current_id[Type] = INT;
220 | current_id[Value] = i++;
221 |
222 | if (token == ',') {
223 | next();
224 | }
225 | }
226 | }
227 | ```
228 |
229 | ## Misc
230 |
231 | Of course `function_declaration` will be introduced in next chapter. `match` appears a lot. It is helper function that consume the current token and fetch the next:
232 |
233 | ```c
234 | void match(int tk) {
235 | if (token == tk) {
236 | next();
237 | } else {
238 | printf("%d: expected token: %d\n", line, tk);
239 | exit(-1);
240 | }
241 | }
242 | ```
243 |
244 | ## Code
245 |
246 | You can download the code of this chapter from [Github](https://github.com/lotabout/write-a-C-interpreter/tree/step-3), or clone with:
247 |
248 | ```
249 | git clone -b step-3 https://github.com/lotabout/write-a-C-interpreter
250 | ```
251 |
252 | The code won't run because there are still some un-implemented functions. You
253 | can challange yourself to fill them out first.
254 |
255 | ## Summary
256 |
257 | EBNF might be difficult to understand because of its syntax (maybe). But it
258 | should be easy to follow this chapter once you can read the syntax. What we do
259 | is to translate EBNF directly into C code. So the parsing is by no means
260 | exciting but you should pay attention to the representation of each concept.
261 |
262 | We'll talk about the function definition in the next chapter, see you then.
263 |
--------------------------------------------------------------------------------
/tutorial/en/6-Functions.md:
--------------------------------------------------------------------------------
1 | We've already seen how variable definitions are parsed in our interpreter. Now
2 | it's time for function definitions (note it is definition, not declaration,
3 | thus our interpreter doesn't support recursion across functions).
4 |
5 | ## EBNF Grammar
6 |
7 | Let's start by refreshing our memory of the EBNF grammar introduced in last
8 | chapter, we've already implement `program`, `global_declaration` and
9 | `enum_decl`. We'll deal with part of `variable_decl`, `function_decl`,
10 | `parameter_decl` and `body_decl`. The rest will be covered in the next chapter.
11 |
12 | ```
13 | variable_decl ::= type {'*'} id { ',' {'*'} id } ';'
14 |
15 | function_decl ::= type {'*'} id '(' parameter_decl ')' '{' body_decl '}'
16 |
17 | parameter_decl ::= type {'*'} id {',' type {'*'} id}
18 |
19 | body_decl ::= {variable_decl}, {statement}
20 |
21 | statement ::= non_empty_statement | empty_statement
22 |
23 | non_empty_statement ::= if_statement | while_statement | '{' statement '}'
24 | | 'return' expression | expression ';'
25 |
26 | if_statement ::= 'if' '(' expression ')' statement ['else' non_empty_statement]
27 |
28 | while_statement ::= 'while' '(' expression ')' non_empty_statement
29 | ```
30 |
31 | ## Function definition
32 |
33 | Recall that we've already encountered functions when handling
34 | `global_declaration`:
35 |
36 | ```c
37 | ...
38 | if (token == '(') {
39 | current_id[Class] = Fun;
40 | current_id[Value] = (int)(text + 1); // the memory address of function
41 | function_declaration();
42 | } else {
43 | ...
44 | ```
45 |
46 | The type for the current identifier (i.e. function name) had already been set
47 | correctly. The above chunk of code set the type (i.e. `Fun`) and the
48 | address in `text segment` for the function. Here comes `parameter_decl` and
49 | `body_decl`.
50 |
51 | ## Parameters and Assembly Output
52 |
53 | Before we get our hands dirty, we have to understand the assembly code that
54 | will be output for a function. Consider the following:
55 |
56 | ```c
57 | int demo(int param_a, int *param_b) {
58 | int local_1;
59 | char local_2;
60 |
61 | ...
62 | }
63 | ```
64 |
65 | When `demo` is called, its calling frame (states of stack) will look like the
66 | following (please refer to the VM of chapter 2):
67 |
68 | ```
69 | | .... | high address
70 | +---------------+
71 | | arg: param_a | new_bp + 3
72 | +---------------+
73 | | arg: param_b | new_bp + 2
74 | +---------------+
75 | |return address | new_bp + 1
76 | +---------------+
77 | | old BP | <- new BP
78 | +---------------+
79 | | local_1 | new_bp - 1
80 | +---------------+
81 | | local_2 | new_bp - 2
82 | +---------------+
83 | | .... | low address
84 | ```
85 |
86 | The key point here is no matter if it is a parameter (e.g. `param_a`) or local
87 | variable (e.g. `local_1`), they are all stored on the **stack**. Thus they are
88 | referred to by the pointer `new_bp` and relative offsets, while global variables
89 | which are stored in `text segment` are refered to by direct address. So we
90 | need to know the number of parameters and the offset of each.
91 |
92 | ## Skeleton for Parsing Function
93 |
94 | ```c
95 | void function_declaration() {
96 | // type func_name (...) {...}
97 | // | this part
98 |
99 | match('(');
100 | function_parameter();
101 | match(')');
102 | match('{');
103 | function_body();
104 | //match('}'); // ①
105 |
106 | // ②
107 | // unwind local variable declarations for all local variables.
108 | current_id = symbols;
109 | while (current_id[Token]) {
110 | if (current_id[Class] == Loc) {
111 | current_id[Class] = current_id[BClass];
112 | current_id[Type] = current_id[BType];
113 | current_id[Value] = current_id[BValue];
114 | }
115 | current_id = current_id + IdSize;
116 | }
117 | }
118 | ```
119 |
120 | Note that we are supposed to consume the last `}` character in ①. But we don't
121 | because `variable_decl` and `function_decl` are parsed together (because of the
122 | same prefix in EBNF grammar) inside `global_declaration`. `variable_decl` ends
123 | with `;` while `function_decl` ends with `}`. If `}` is consumed, the `while`
124 | loop in `global_declaration` won't be able to know that a `function_decl`
125 | parsing is end. Thus we leave it to `global_declaration` to consume it.
126 |
127 | What ② is trying to do is unwind local variable declarations for all local
128 | variables. As we know, local variables can have the same name as global ones,
129 | once it happans, global ones will be shadowed. So we should recover the status
130 | once we exit the function body. Informations about global variables are backed
131 | up to fields `BXXX`, so we iterate over all identifiers to recover.
132 |
133 | ## function_parameter()
134 |
135 | ```
136 | parameter_decl ::= type {'*'} id {',' type {'*'} id}
137 | ```
138 |
139 | It is quite straightforward except we need to remember the types and position
140 | of the parameter.
141 |
142 | ```c
143 | int index_of_bp; // index of bp pointer on stack
144 |
145 | void function_parameter() {
146 | int type;
147 | int params;
148 | params = 0;
149 | while (token != ')') {
150 | // ①
151 |
152 | // int name, ...
153 | type = INT;
154 | if (token == Int) {
155 | match(Int);
156 | } else if (token == Char) {
157 | type = CHAR;
158 | match(Char);
159 | }
160 |
161 | // pointer type
162 | while (token == Mul) {
163 | match(Mul);
164 | type = type + PTR;
165 | }
166 |
167 | // parameter name
168 | if (token != Id) {
169 | printf("%d: bad parameter declaration\n", line);
170 | exit(-1);
171 | }
172 | if (current_id[Class] == Loc) {
173 | printf("%d: duplicate parameter declaration\n", line);
174 | exit(-1);
175 | }
176 |
177 | match(Id);
178 |
179 | //②
180 | // store the local variable
181 | current_id[BClass] = current_id[Class]; current_id[Class] = Loc;
182 | current_id[BType] = current_id[Type]; current_id[Type] = type;
183 | current_id[BValue] = current_id[Value]; current_id[Value] = params++; // index of current parameter
184 |
185 | if (token == ',') {
186 | match(',');
187 | }
188 | }
189 |
190 | // ③
191 | index_of_bp = params+1;
192 | }
193 | ```
194 |
195 | Part ① is the same to what we've seen in `global_declaration` which is used to
196 | parse the type for the parameter.
197 |
198 | Part ② is to backup the information for global variables which will be
199 | shadowed by local variables. The position of current parameter is stored in
200 | field `Value`.
201 |
202 | Part ③ is used to calculate the position of pointer `bp` which corresponds to
203 | `new_bp` that we talked about in the above section.
204 |
205 | ## function_body()
206 |
207 | Different with modern C, our interpreter requires that all the definitinos of
208 | variables that are used in current function should be put at the beginning of
209 | current function. This rule is actually the same to ancient C compilers.
210 |
211 | ```c
212 | void function_body() {
213 | // type func_name (...) {...}
214 | // -->| |<--
215 |
216 | // ... {
217 | // 1. local declarations
218 | // 2. statements
219 | // }
220 |
221 | int pos_local; // position of local variables on the stack.
222 | int type;
223 | pos_local = index_of_bp;
224 |
225 | // ①
226 | while (token == Int || token == Char) {
227 | // local variable declaration, just like global ones.
228 | basetype = (token == Int) ? INT : CHAR;
229 | match(token);
230 |
231 | while (token != ';') {
232 | type = basetype;
233 | while (token == Mul) {
234 | match(Mul);
235 | type = type + PTR;
236 | }
237 |
238 | if (token != Id) {
239 | // invalid declaration
240 | printf("%d: bad local declaration\n", line);
241 | exit(-1);
242 | }
243 | if (current_id[Class] == Loc) {
244 | // identifier exists
245 | printf("%d: duplicate local declaration\n", line);
246 | exit(-1);
247 | }
248 | match(Id);
249 |
250 | // store the local variable
251 | current_id[BClass] = current_id[Class]; current_id[Class] = Loc;
252 | current_id[BType] = current_id[Type]; current_id[Type] = type;
253 | current_id[BValue] = current_id[Value]; current_id[Value] = ++pos_local; // index of current parameter
254 |
255 | if (token == ',') {
256 | match(',');
257 | }
258 | }
259 | match(';');
260 | }
261 |
262 | // ②
263 | // save the stack size for local variables
264 | *++text = ENT;
265 | *++text = pos_local - index_of_bp;
266 |
267 | // statements
268 | while (token != '}') {
269 | statement();
270 | }
271 |
272 | // emit code for leaving the sub function
273 | *++text = LEV;
274 | }
275 | ```
276 |
277 | You should be familiar with ①, it had been repeated several times.
278 |
279 | Part ② is writing assembly code into text segment. In the VM chapter, we said
280 | we have to preserve spaces for local variables on stack, well, this is it.
281 |
282 | ## Code
283 |
284 | You can download the code of this chapter from [Github](https://github.com/lotabout/write-a-C-interpreter/tree/step-4), or clone with:
285 |
286 | ```
287 | git clone -b step-4 https://github.com/lotabout/write-a-C-interpreter
288 | ```
289 |
290 | The code still won't run because there are still some un-implemented
291 | functions. You can challange yourself to fill them out first.
292 |
293 | ## Summary
294 |
295 | The code of this chapter isn't long, most of the part are used to parse
296 | variables and much of them are duplicated. The parsing for parameter and local
297 | variables are almost the same, but the stored information are different.
298 |
299 | Of course, you may want to review the VM chapter (chapter 2) to get better
300 | understanding of the expected output for function, so as to understand why
301 | would we want to gather such information. This is what we called
302 | "domain knowledge".
303 |
304 | We'll deal with `if`, `while` next chapter, see you then.
305 |
--------------------------------------------------------------------------------
/tutorial/en/7-Statements.md:
--------------------------------------------------------------------------------
1 | We have two concepts in C: statement and expression. Basically statements won't
2 | have a value as its result while expressions do. That means you cannot assign
3 | a statement to a variable.
4 |
5 | We have 6 statements in our interpreter:
6 |
7 | 1. `if (...) [else ]`
8 | 2. `while (...) `
9 | 3. `{ }`
10 | 4. `return xxx;`
11 | 5. ``;
12 | 6. `expression;` (expression end with semicolon)
13 |
14 | The parsing job is relatively easy compared to understanding the expected
15 | assembly output. Let's explain one by one.
16 |
17 | ## IF
18 |
19 | `if` statement is to jump to a different location according to the condition.
20 | Let's check its pseudo cdoe:
21 |
22 | ```
23 | if (...) [else ]
24 |
25 | if ()
26 | JZ a
27 | ===>
28 | else: JMP b
29 | a: a:
30 |
31 | b: b:
32 | ```
33 |
34 | The flow of assembly code is:
35 |
36 | 1. execute ``.
37 | 2. If the condition fails, jump to position `a`, i.e. `else` statement.
38 | 3. Because assembly is executed sequentially, if `` is executed,
39 | we need to skip ``, thus a jump to `b` is needed.
40 |
41 | Corresponding C code:
42 |
43 | ```c
44 | if (token == If) {
45 | match(If);
46 | match('(');
47 | expression(Assign); // parse condition
48 | match(')');
49 |
50 | *++text = JZ;
51 | b = ++text;
52 |
53 | statement(); // parse statement
54 | if (token == Else) { // parse else
55 | match(Else);
56 |
57 | // emit code for JMP B
58 | *b = (int)(text + 3);
59 | *++text = JMP;
60 | b = ++text;
61 |
62 | statement();
63 | }
64 |
65 | *b = (int)(text + 1);
66 | }
67 | ```
68 |
69 | ## While
70 |
71 | `while` is simplier than `if`:
72 |
73 | ```
74 | a: a:
75 | while ()
76 | JZ b
77 |
78 | JMP a
79 | b: b:
80 | ```
81 |
82 | Nothing worth mention. C code:
83 |
84 | ```c
85 | else if (token == While) {
86 | match(While);
87 |
88 | a = text + 1;
89 |
90 | match('(');
91 | expression(Assign);
92 | match(')');
93 |
94 | *++text = JZ;
95 | b = ++text;
96 |
97 | statement();
98 |
99 | *++text = JMP;
100 | *++text = (int)a;
101 | *b = (int)(text + 1);
102 | }
103 | ```
104 |
105 | ## Return
106 |
107 | Once we meet `return`, it means the function is about to end, thus `LEV` is
108 | needed to indicate the exit.
109 |
110 | ```c
111 | else if (token == Return) {
112 | // return [expression];
113 | match(Return);
114 |
115 | if (token != ';') {
116 | expression(Assign);
117 | }
118 |
119 | match(';');
120 |
121 | // emit code for return
122 | *++text = LEV;
123 | }
124 | ```
125 |
126 | ## Others
127 |
128 | Other statement acts as helpers for compiler to group the codes better. They
129 | won't generate assembly codes. As follows:
130 |
131 | ```c
132 | else if (token == '{') {
133 | // { ... }
134 | match('{');
135 |
136 | while (token != '}') {
137 | statement();
138 | }
139 |
140 | match('}');
141 | }
142 | else if (token == ';') {
143 | // empty statement
144 | match(';');
145 | }
146 | else {
147 | // a = b; or function_call();
148 | expression(Assign);
149 | match(';');
150 | }
151 | ```
152 |
153 | ## Code
154 |
155 | You can download the code of this chapter from [Github](https://github.com/lotabout/write-a-C-interpreter/tree/step-5), or clone with:
156 |
157 | ```
158 | git clone -b step-5 https://github.com/lotabout/write-a-C-interpreter
159 | ```
160 |
161 | The code still won't run because there are still some un-implemented
162 | functions. You can challange yourself to fill them out first.
163 |
164 | ## Summary
165 |
166 | As you can see, implementing parsing for an interpreter is not hard at all.
167 | But it did seems complicated because we need to gather enough knowledge during
168 | parsing in order to generate target code (assembly in our case). You see, that
169 | is one big obstacle for beginner to start implementation. So instead of
170 | "programming knowledge", "domain knowledge" is also required to actually
171 | achieve something.
172 |
173 | Thus I suggest you to learn assembly if you haven't, it is not difficult but
174 | helpful to understand how computers work.
175 |
--------------------------------------------------------------------------------
/tutorial/en/8-Expressions.md:
--------------------------------------------------------------------------------
1 | This chapter will be long, please make sure you have enough time for this.
2 | We'll dealing with the last puzzle of our interpreter: expressions.
3 |
4 | What is an expression? Well, it is combination of the elements of a
5 | programming language and generates a result, such as function invocation,
6 | variable assignment, calculation using various operators.
7 |
8 | We had to pay attention to two things: the precedence of operators and the
9 | target assembly code for operators.
10 |
11 | ## Precedence of operators
12 |
13 | The precedence of operators means we should compute some operators before
14 | others even though the latter may show up first. For example: operator `*`
15 | has higher precedence than operator `+`, so that in expression `2 + 3 * 4`,
16 | the correct calculation result is `2 + (3 * 4)` instead of `(2 + 3) * 4` even
17 | though `+` comes before `*`.
18 |
19 | C programming language had already defined the precedence for various
20 | operators, you can refer to [Operator
21 | Precedence](http://en.cppreference.com/w/c/language/operator_precedence).
22 |
23 | We'll use stack for handling precedence. One stack for arguments, the other
24 | one for operators. I'll give an example directly: consider `2 + 3 - 4 * 5`,
25 | we'll get the result through the following steps:
26 |
27 | 1. push `2` onto the stack.
28 | 2. operator `+` is met, push it onto the stack, now we are expecting the other
29 | argument for `+`.
30 | 3. `3` is met, push it onto the stack. We are supposed to calculate `2+3`
31 | immediately, but we are not sure whether `3` belongs to the operator with
32 | higher precedence, so leave it there.
33 | 4. operator `-` is met. `-` has the same precedence as `+`, so we are sure
34 | that the value `3` on the stack belongs to `+`. Thus the pending
35 | calculation `2+3` is evaluated. `3`, `+`, `2` are poped from the stack and
36 | the result `5` is pushed back. Don't forget to push `-` onto the stack.
37 | 5. `4` is met, but we are not sure if it 'belongs' to `-`, leave it there.
38 | 6. `*` is met and it has higher precedence than `-`, now we have two operators
39 | pending.
40 | 7. `5` is met, and still not sure whom `5` belongs to. Leave it there.
41 | 8. The expression end. Now we are sure that `5` belongs to the operator lower
42 | on the stack: `*`, pop them out and push the result `4 * 5 = 20` back.
43 | 9. Continue to pop out items push the result `5 - 20 = -15` back.
44 | 10. Now the operator stack is empty, pop out the result: `-15`
45 |
46 | ```
47 | // after step 1, 2
48 | | |
49 | +------+
50 | | 3 | | |
51 | +------+ +------+
52 | | 2 | | + |
53 | +------+ +------+
54 |
55 | // after step 4
56 | | | | |
57 | +------+ +------+
58 | | 5 | | - |
59 | +------+ +------+
60 |
61 | // after step 7
62 | | |
63 | +------+
64 | | 5 |
65 | +------+ +------+
66 | | 4 | | * |
67 | +------+ +------+
68 | | 5 | | - |
69 | +------+ +------+
70 | ```
71 |
72 | As described above, we had to make sure that the right side of argument
73 | belongs to current operator 'x', thus we have to look right of the expression,
74 | find out and calculate the ones that have higher precedence. Then do the
75 | calculation for current operator 'x'.
76 |
77 | Finally, we need to consider precedence for only binary/ternary operators.
78 | Because precedence means different operators try to "snatch" arguments from
79 | each other, while unary operators are the strongest.
80 |
81 | ## Unary operators
82 |
83 | Unary operators are strongest, so we serve them first. Of course, we'll also
84 | parse the arguments(i.e. variables, number, string, etc) for operators.
85 |
86 | We've already learned the parsing
87 |
88 | ### Constant
89 |
90 | First comes numbers, we use `IMM` to load it into `AX`:
91 |
92 | ```c
93 | if (token == Num) {
94 | match(Num);
95 | // emit code
96 |
97 | *++text = IMM;
98 | *++text = token_val;
99 | expr_type = INT;
100 | }
101 | ```
102 |
103 | Next comes string literals, however C support this kind of string
104 | concatination:
105 |
106 | ```c
107 | char *p;
108 | p = "first line"
109 | "second line";
110 |
111 | // which equals to:
112 |
113 | char *p;
114 | p = "first linesecond line";
115 | ```
116 |
117 | ```c
118 | else if (token == '"') {
119 | // emit code
120 | *++text = IMM;
121 | *++text = token_val;
122 | match('"');
123 |
124 | // store the rest strings
125 | while (token == '"') {
126 | match('"');
127 | }
128 |
129 | // append the end of string character '\0', all the data are default
130 | // to 0, so just move data one position forward.
131 | data = (char *)(((int)data + sizeof(int)) & (-sizeof(int)));
132 | expr_type = PTR;
133 | }
134 | ```
135 |
136 | ### sizeof
137 |
138 | It is an unary operator, we'll have to know to type of its argument which we
139 | are familiar with.
140 |
141 | ```c
142 | else if (token == Sizeof) {
143 | // sizeof is actually an unary operator
144 | // now only `sizeof(int)`, `sizeof(char)` and `sizeof(*...)` are
145 | // supported.
146 | match(Sizeof);
147 | match('(');
148 | expr_type = INT;
149 |
150 | if (token == Int) {
151 | match(Int);
152 | } else if (token == Char) {
153 | match(Char);
154 | expr_type = CHAR;
155 | }
156 |
157 | while (token == Mul) {
158 | match(Mul);
159 | expr_type = expr_type + PTR;
160 | }
161 |
162 | match(')');
163 |
164 | // emit code
165 | *++text = IMM;
166 | *++text = (expr_type == CHAR) ? sizeof(char) : sizeof(int);
167 | expr_type = INT;
168 | }
169 | ```
170 |
171 | Note that only `sizeof(int)`, `sizeof(char)` (which by the way, is always `1` - by definition) and `sizeof(pointer type ...)`
172 | are supported, and the type of the result is `int`.
173 |
174 | ### Variable and function invocation
175 |
176 | They all starts with an `Id` token, thus are handled together.
177 |
178 | ```c
179 | else if (token == Id) {
180 | // there are several type when occurs to Id
181 | // but this is unit, so it can only be
182 | // 1. function call
183 | // 2. Enum variable
184 | // 3. global/local variable
185 | match(Id);
186 |
187 | id = current_id;
188 |
189 | if (token == '(') {
190 | // function call
191 | match('(');
192 |
193 | // ①
194 | // pass in arguments
195 | tmp = 0; // number of arguments
196 | while (token != ')') {
197 | expression(Assign);
198 | *++text = PUSH;
199 | tmp ++;
200 |
201 | if (token == ',') {
202 | match(',');
203 | }
204 | }
205 | match(')');
206 |
207 | // ②
208 | // emit code
209 | if (id[Class] == Sys) {
210 | // system functions
211 | *++text = id[Value];
212 | }
213 | else if (id[Class] == Fun) {
214 | // function call
215 | *++text = CALL;
216 | *++text = id[Value];
217 | }
218 | else {
219 | printf("%d: bad function call\n", line);
220 | exit(-1);
221 | }
222 |
223 | // ③
224 | // clean the stack for arguments
225 | if (tmp > 0) {
226 | *++text = ADJ;
227 | *++text = tmp;
228 | }
229 | expr_type = id[Type];
230 | }
231 | else if (id[Class] == Num) {
232 | // ④
233 | // enum variable
234 | *++text = IMM;
235 | *++text = id[Value];
236 | expr_type = INT;
237 | }
238 | else {
239 | // ⑤
240 | // variable
241 | if (id[Class] == Loc) {
242 | *++text = LEA;
243 | *++text = index_of_bp - id[Value];
244 | }
245 | else if (id[Class] == Glo) {
246 | *++text = IMM;
247 | *++text = id[Value];
248 | }
249 | else {
250 | printf("%d: undefined variable\n", line);
251 | exit(-1);
252 | }
253 |
254 | //⑥
255 | // emit code, default behaviour is to load the value of the
256 | // address which is stored in `ax`
257 | expr_type = id[Type];
258 | *++text = (expr_type == Char) ? LC : LI;
259 | }
260 | }
261 | ```
262 |
263 | ①: Notice we are using the normal order to push the arguments which
264 | corresponds to the implementation of our virtual machine. However C standard
265 | push the argument in reverse order.
266 |
267 | ②: Note how we support `printf`, `read`, `malloc` and other built in
268 | functions in our virtual machine. These function calls have specific assembly
269 | instructions while normal functions are compiled into `CALL `.
270 |
271 | ③: Remove the arguments on the stack, we modifies the stack pointer directly
272 | because we don't care about the values.
273 |
274 | ④: Enum variables are treated as constant numbers.
275 |
276 | ⑤: Load the values of variable, use the `bp + offset` style for local
277 | variable(refer to chapter 7), use `IMM` to load the address of global
278 | variables.
279 |
280 | ⑥: Finally load the value of variables using `LI/LC` according to their type.
281 |
282 | You might ask how to deal with expressions like `a[10]` if we use `LI/LC` to
283 | load immediatly when an identifier is met? We might modify existing assembly
284 | instructions according to the operator after the identifier, as you'll see
285 | later.
286 |
287 | ### Casting
288 |
289 | Perhaps you've notice that we use `expr_type` to store the type of the return
290 | value of an expression. Type Casting is for changing the type of the return
291 | value of an expression.
292 |
293 | ```c
294 | else if (token == '(') {
295 | // cast or parenthesis
296 | match('(');
297 | if (token == Int || token == Char) {
298 | tmp = (token == Char) ? CHAR : INT; // cast type
299 | match(token);
300 | while (token == Mul) {
301 | match(Mul);
302 | tmp = tmp + PTR;
303 | }
304 |
305 | match(')');
306 |
307 | expression(Inc); // cast has precedence as Inc(++)
308 |
309 | expr_type = tmp;
310 | } else {
311 | // normal parenthesis
312 | expression(Assign);
313 | match(')');
314 | }
315 | }
316 | ```
317 |
318 | ### Dereference/Indirection
319 |
320 | `*a` in C is to get the object pointed by pointer `a`. It is essential to find
321 | out the type of pointer `a`. Luckily the type information will be stored in
322 | variable `expr_type` when an expression ends.
323 |
324 | ```c
325 | else if (token == Mul) {
326 | // dereference *
327 | match(Mul);
328 | expression(Inc); // dereference has the same precedence as Inc(++)
329 |
330 | if (expr_type >= PTR) {
331 | expr_type = expr_type - PTR;
332 | } else {
333 | printf("%d: bad dereference\n", line);
334 | exit(-1);
335 | }
336 |
337 | *++text = (expr_type == CHAR) ? LC : LI;
338 | }
339 | ```
340 |
341 | ### Address-Of
342 |
343 | In section "Variable and function invocation", we said we will modify `LI/LC`
344 | instructions dynamically, now it is the time. We said that we'll load the
345 | address of a variable first and call `LI/LC` instruction to load the actual
346 | content according to the type:
347 |
348 | ```
349 | IMM
350 | LI
351 | ```
352 |
353 | Then for getting `address-of` a variable, ignoring `LI/LC` instruction will do
354 | the trick.
355 |
356 | ```c
357 | else if (token == And) {
358 | // get the address of
359 | match(And);
360 | expression(Inc); // get the address of
361 | if (*text == LC || *text == LI) {
362 | text --;
363 | } else {
364 | printf("%d: bad address of\n", line);
365 | exit(-1);
366 | }
367 |
368 | expr_type = expr_type + PTR;
369 | }
370 | ```
371 |
372 | ### Logical NOT
373 |
374 | We don't have logical not instruction in our virtual machine, thus we'll
375 | compare the result to `0` which represents `False`:
376 |
377 | ```c
378 | else if (token == '!') {
379 | // not
380 | match('!');
381 | expression(Inc);
382 |
383 | // emit code, use == 0
384 | *++text = PUSH;
385 | *++text = IMM;
386 | *++text = 0;
387 | *++text = EQ;
388 |
389 | expr_type = INT;
390 | }
391 | ```
392 |
393 | ### Bitwise NOT
394 |
395 | We don't have corresponding instruction in our virtual machine either. Thus we
396 | use `XOR` to implement, e.g. `~a = a ^ 0xFFFF`.
397 |
398 | ```c
399 | else if (token == '~') {
400 | // bitwise not
401 | match('~');
402 | expression(Inc);
403 |
404 | // emit code, use XOR -1
405 | *++text = PUSH;
406 | *++text = IMM;
407 | *++text = -1;
408 | *++text = XOR;
409 |
410 | expr_type = INT;
411 | }
412 | ```
413 |
414 | ### Unary plus and Unary minus
415 |
416 | Use `0 - x` to implement `-x`:
417 |
418 | ```c
419 | else if (token == Add) {
420 | // +var, do nothing
421 | match(Add);
422 | expression(Inc);
423 | expr_type = INT;
424 |
425 | } else if (token == Sub) {
426 | // -var
427 | match(Sub);
428 |
429 | if (token == Num) {
430 | *++text = IMM;
431 | *++text = -token_val;
432 | match(Num);
433 | } else {
434 | *++text = IMM;
435 | *++text = -1;
436 | *++text = PUSH;
437 | expression(Inc);
438 | *++text = MUL;
439 | }
440 |
441 | expr_type = INT;
442 | }
443 | ```
444 |
445 | ### Increment and Decrement
446 |
447 | The precedence for increment or decrement is related to the position of the
448 | operator. Such that `++p` has higher precedence than `p++`. Now we are dealing
449 | with `++p`.
450 |
451 | ```c
452 | else if (token == Inc || token == Dec) {
453 | tmp = token;
454 | match(token);
455 | expression(Inc);
456 | // ①
457 | if (*text == LC) {
458 | *text = PUSH; // to duplicate the address
459 | *++text = LC;
460 | } else if (*text == LI) {
461 | *text = PUSH;
462 | *++text = LI;
463 | } else {
464 | printf("%d: bad lvalue of pre-increment\n", line);
465 | exit(-1);
466 | }
467 | *++text = PUSH;
468 | *++text = IMM;
469 |
470 | // ②
471 | *++text = (expr_type > PTR) ? sizeof(int) : sizeof(char);
472 | *++text = (tmp == Inc) ? ADD : SUB;
473 | *++text = (expr_type == CHAR) ? SC : SI;
474 | }
475 | ```
476 |
477 | For `++p` we need to access `p` twice: one for load the value, one for storing
478 | the incremented value, that's why we need to `PUSH` (①) it once.
479 |
480 | ② deal with cases when `p` is pointer.
481 |
482 | ## Binary Operators
483 |
484 | Now we need to deal with the precedence of operators. We will scan to the
485 | right of current operator, until one that has **the same or lower** precedence
486 | than the current operator is met.
487 |
488 | Let's recall the tokens that we've defined, they are order by their
489 | precedences from low to high. That mean `Assign` is the lowest and `Brak`(`[`)
490 | is the highest.
491 |
492 | ```c
493 | enum {
494 | Num = 128, Fun, Sys, Glo, Loc, Id,
495 | Char, Else, Enum, If, Int, Return, Sizeof, While,
496 | Assign, Cond, Lor, Lan, Or, Xor, And, Eq, Ne, Lt, Gt, Le, Ge, Shl, Shr, Add, Sub, Mul, Div, Mod, Inc, Dec, Brak
497 | };
498 | ```
499 |
500 | Thus the argument `level` in calling `expression(level)` is actually used to
501 | indicate the precedence of current operator, thus the skeleton for parsing
502 | binary operators is:
503 |
504 | ```c
505 | while (token >= level) {
506 | // parse token for binary operator and postfix operator
507 | }
508 | ```
509 |
510 | Now we know how to deal with precedence, let's check how operators are
511 | compiled into assembly instructions.
512 |
513 | ### Assignment
514 |
515 | `Assign` has the lowest precedence. Consider expression `a = (expression)`,
516 | we've already generated instructions for `a` as:
517 |
518 | ```
519 | IMM
520 | LC/LI
521 | ```
522 |
523 | After the expression on the right side of `=` is evaluated, the result will be
524 | stored in register `AX`. In order to assign the value in `AX` to the variable
525 | `a`, we need to change the original instructions into:
526 |
527 | ```
528 | IMM
529 | PUSH
530 | SC/SI
531 | ```
532 |
533 | Now we can understand how to parse `Assign`:
534 |
535 | ```c
536 | tmp = expr_type;
537 | if (token == Assign) {
538 | // var = expr;
539 | match(Assign);
540 | if (*text == LC || *text == LI) {
541 | *text = PUSH; // save the lvalue's pointer
542 | } else {
543 | printf("%d: bad lvalue in assignment\n", line);
544 | exit(-1);
545 | }
546 | expression(Assign);
547 |
548 | expr_type = tmp;
549 | *++text = (expr_type == CHAR) ? SC : SI;
550 | }
551 | ```
552 |
553 | ### Ternary Conditional
554 |
555 | That is `? :` in C. It is a operator version for `if` statement. The target
556 | instructions are almost identical to `if`:
557 |
558 | ```c
559 | else if (token == Cond) {
560 | // expr ? a : b;
561 | match(Cond);
562 | *++text = JZ;
563 | addr = ++text;
564 | expression(Assign);
565 | if (token == ':') {
566 | match(':');
567 | } else {
568 | printf("%d: missing colon in conditional\n", line);
569 | exit(-1);
570 | }
571 | *addr = (int)(text + 3);
572 | *++text = JMP;
573 | addr = ++text;
574 | expression(Cond);
575 | *addr = (int)(text + 1);
576 | }
577 | ```
578 |
579 | ### Logical Operators
580 |
581 | Two of them: `||` and `&&`. Their corresponding assembly instructions are:
582 |
583 | ```c
584 | || &&
585 |
586 | ...... ......
587 | JNZ b JZ b
588 | ...... ......
589 | b: b:
590 | ```
591 |
592 | Source code as following:
593 |
594 | ```c
595 | else if (token == Lor) {
596 | // logic or
597 | match(Lor);
598 | *++text = JNZ;
599 | addr = ++text;
600 | expression(Lan);
601 | *addr = (int)(text + 1);
602 | expr_type = INT;
603 | }
604 | else if (token == Lan) {
605 | // logic and
606 | match(Lan);
607 | *++text = JZ;
608 | addr = ++text;
609 | expression(Or);
610 | *addr = (int)(text + 1);
611 | expr_type = INT;
612 | }
613 | ```
614 |
615 | ### Mathematical Operators
616 |
617 | Including `|`, `^`, `&`, `==`, `!=`, `<=`, `>=`, `<`, `>`, `<<`, `>>`, `+`,
618 | `-`, `*`, `/`, `%`. They are similar to implement, we'll give `^` as an
619 | example:
620 |
621 | ```
622 | ^
623 |
624 | ...... <- now the result is on ax
625 | PUSH
626 | ...... <- now the value of is on ax
627 | XOR
628 | ```
629 |
630 | Thus the source code is:
631 |
632 | ```c
633 | else if (token == Xor) {
634 | // bitwise xor
635 | match(Xor);
636 | *++text = PUSH;
637 | expression(And);
638 | *++text = XOR;
639 | expr_type = INT;
640 | }
641 | ```
642 |
643 | Quite easy, hah? There are still something to mention about addition and
644 | substraction for pointers. A pointer plus/minus some number equals to the
645 | shiftment for a pointer according to its type. For example, `a + 1` will shift
646 | for 1 byte if `a` is `char *`, while 4 bytes(in 32 bit machine) if a is `int
647 | *`.
648 |
649 | Also, substraction for two pointers will give the number of element between
650 | them, thus need special treatment.
651 |
652 | Take addition as example:
653 |
654 | ```
655 | +
656 |
657 | normal pointer
658 |
659 |
660 | PUSH PUSH
661 | |
662 | ADD PUSH | *
663 | IMM |
664 | MUL |
665 | ADD
666 | ```
667 |
668 | It means if `` is a pointer, we need to multiply `` with the
669 | number of bytes that ``'s type occupies.
670 |
671 | ```c
672 | else if (token == Add) {
673 | // add
674 | match(Add);
675 | *++text = PUSH;
676 | expression(Mul);
677 | expr_type = tmp;
678 |
679 | if (expr_type > PTR) {
680 | // pointer type, and not `char *`
681 | *++text = PUSH;
682 | *++text = IMM;
683 | *++text = sizeof(int);
684 | *++text = MUL;
685 | }
686 | *++text = ADD;
687 | }
688 | ```
689 |
690 | You can try to implement substraction by your own or refer to the repository.
691 |
692 | ### Increment and Decrement Again
693 |
694 | Now we deal with the postfix version, e.g. `p++` or `p--`. Different from the
695 | prefix version, the postfix version need to store the value **before**
696 | increment/decrement on `AX` after the increment/decrement. Let's compare them:
697 |
698 | ```c
699 | // prefix version
700 | *++text = PUSH;
701 | *++text = IMM;
702 | *++text = (expr_type > PTR) ? sizeof(int) : sizeof(char);
703 | *++text = (tmp == Inc) ? ADD : SUB;
704 | *++text = (expr_type == CHAR) ? SC : SI;
705 |
706 | // postfix version
707 | *++text = PUSH;
708 | *++text = IMM;
709 | *++text = (expr_type > PTR) ? sizeof(int) : sizeof(char);
710 | *++text = (token == Inc) ? ADD : SUB;
711 | *++text = (expr_type == CHAR) ? SC : SI;
712 | *++text = PUSH; //
713 | *++text = IMM; // Inversion of increment/decrement
714 | *++text = (expr_type > PTR) ? sizeof(int) : sizeof(char); //
715 | *++text = (token == Inc) ? SUB : ADD; //
716 | ```
717 |
718 | ### Indexing
719 |
720 | You may already know that `a[10]` equals to `*(a + 10)` in C language. The
721 | interpreter is trying to do the same:
722 |
723 | ```c
724 | else if (token == Brak) {
725 | // array access var[xx]
726 | match(Brak);
727 | *++text = PUSH;
728 | expression(Assign);
729 | match(']');
730 |
731 | if (tmp > PTR) {
732 | // pointer, `not char *`
733 | *++text = PUSH;
734 | *++text = IMM;
735 | *++text = sizeof(int);
736 | *++text = MUL;
737 | }
738 | else if (tmp < PTR) {
739 | printf("%d: pointer type expected\n", line);
740 | exit(-1);
741 | }
742 | expr_type = tmp - PTR;
743 | *++text = ADD;
744 | *++text = (expr_type == CHAR) ? LC : LI;
745 | }
746 | ```
747 |
748 | ## Code
749 |
750 | We need to initialize the stack for our virtual machine besides all the
751 | expressions in the above sections so that `main` function is correctly called,
752 | and exit when `main` exit.
753 |
754 | ```c
755 | int *tmp;
756 | // setup stack
757 | sp = (int *)((int)stack + poolsize);
758 | *--sp = EXIT; // call exit if main returns
759 | *--sp = PUSH; tmp = sp;
760 | *--sp = argc;
761 | *--sp = (int)argv;
762 | *--sp = (int)tmp;
763 | ```
764 |
765 | Last, due to the limitation of our interpreter, all the definitions of variables
766 | should be put before all expressions, just like the old C compiler requires.
767 |
768 | You can download all the code on
769 | [Github](https://github.com/lotabout/write-a-C-interpreter/tree/step-6) or
770 | clone it by:
771 |
772 | ```
773 | git clone -b step-6 https://github.com/lotabout/write-a-C-interpreter
774 | ```
775 |
776 | Compile it by `gcc -o xc-tutor xc-tutor.c` and run it by `./xc-tutor hello.c`
777 | to check the result.
778 |
779 | Our code is bootstraping, that means our interpreter can parse itself, so that
780 | you can run our C interpreter inside itself by `./xc-tutor xc-tutor.c
781 | hello.c`.
782 |
783 | You might need to compile with `gcc -m32 -o xc-tutor xc-tutor.c` if you use a
784 | 64 bit machine.
785 |
786 | ## Summary
787 |
788 | This chapter is long mainly because there are quite a lot of operators there.
789 | You might pay attention to:
790 |
791 | 1. How to call `expression` recursively to handle the precedence of operators.
792 | 2. What is the target assembly instructions for each operator.
793 |
794 | Congratulations! You've build a runnable C interpeter all by yourself!
795 |
--------------------------------------------------------------------------------
/tutorial/es/0-Prefacio.md:
--------------------------------------------------------------------------------
1 | Esta serie de artículos es un tutorial para contruir un compilador de C
2 | desde cero.
3 |
4 | Menti un poquito en la frase superior: en realidad es un _interpretador_ en
5 | vez de un _compilador_. Mentí porque ¿Qué demonios es un "interpretador de
6 | C"? Sin embargo, entenderas mejor los compiladores construyendo un
7 | interpretador.
8 |
9 | Claro, deseo que puedas conseguir un entendimiento basíco de como un
10 | compilador es construido, y darte cuenta de que no es tan difícil
11 | constuir uno. ¡Buena suerte!
12 |
13 | Finalmente, esta serie se escribio originalmente en Chino, sientete libre
14 | eb corregirme si te sientes confundido por mi inglés. Y apreciaría mucho
15 | si pudieras enseñarme algo de inglés "nativo" :)
16 | (Nota del traductor: Yo ne he traducido esta serie del chino al inglés,
17 | sino del inglés al español.)
18 |
19 | No vamos a escribir ningún codigo en este capítulo, asique sientete libre
20 | de saltartelo si estas desesperado para ver algo de codigo...
21 |
22 | ## ¿Por qué deberiás preocuparte con teoría de compiladores?
23 | p
24 | ¡Porque es **GUAY**!
25 |
26 | Y es muy util. Los programas son contruidos para hacer algo por nosotros,
27 | cuanfo son usados para transformar un tipo de data en otro, los llamamos
28 | compiladores. De esta manera, aprendiendo teoria de compiladores estamos
29 | intentando dominar una muy útil y potente técnica para resolver problemas.
30 | ¿No es eso suficientemente guay para tí?
31 |
32 | La gente solía decir que entender como un compilador funciona te permitiría
33 | escribir mejor código. Algunos argumentaran que los compiladores modernos
34 | son tan buenos en optimizar que ya no deberíad preocuparte. Bueno, eso es
35 | cierto, la mayoría no necesita aprender teoría de compiladores solo para
36 | mejorar la eficiencía del codigo. Y por la mayoría, ¡Me refiero a tí!
37 |
38 | ## A nosotros tampoco nos gusta la teoría
39 |
40 | Siempre he admirado la teoría de compilar porque eso es lo que hace la
41 | programación fácil. De qualquier modo, ¿Puedes imaginarte contruir un
42 | navegador web solo en assembly? Asique cuando conseguí una oportunidad
43 | para aprender teoría de compilar en la facultad, ¡Estaba tan emocionado!
44 | I luego... Lo dejé, no entendiendo el que.
45 |
46 | Normalmente, por supuesto, un curso sobre compiladores cubrirá:
47 |
48 | 1. Como representar sintaxis (e.g.: BNF, etc.)
49 | 2. Lexografo, con algo de NFA (Nondeterministic Finite Automata / Automata
50 | Finito Nodeterminante), DFA (Deterministic Finite Automata / Automata
51 | Finito Deterministico).
52 | 3. Analidor, tal como descenso recursivo, LL(k), LALR, etc.
53 | 4. Lenguajes intermediarios.
54 | 5. Generación de código.
55 | 6. Optimización de código.
56 |
57 | Tal vez a más del 90% de estudiantes no les importe en nada más alla del
58 | analizador, y aún más, !Todavía no sabemos como construir un compilador!
59 | Despues de todo el esfuerzo en aprender las teorías. Bueno, la razón
60 | principal es que "Teoria de compiladores" intenta enseñar "Como construir
61 | un generador de analizadores", en pocas palabras una herramienta que consume
62 | sintaxis y gramática y genera un compilador por tí. lex/yacc o flex/bison
63 | o cosas como esas.
64 |
65 | Estas teorias intentan enseñarnos como resolver problemas generales de
66 | generación de compiladores automáticamente. Eso significa que una vez
67 | que los domines, seras capaz de trabajar con todo tipo de gramáticas. Son,
68 | en efecto útiles en la industria. Sin embargo, son demasiado poderosos y
69 | complejos para estudiantes y la mayoría de programadores. Entenderas eso
70 | si intentas leer codigo fuente lez/yacc.
71 |
72 | Las buenas noticias son que contruir un compilador puede ser mucho más fácil
73 | que nunca te ayas imaginado. No mentire, no es fácil, pero definitivámente
74 | no es difícil.
75 |
76 | ## Nacimiento de este proyecto
77 |
78 | Un día me cruzé con el proyecto [c4](https://github.com/rswier/c4) en GitHub.
79 | Es un pequeño interprete en el cual se proclama solo ser implementado en
80 | cuatro funciones. La parte más sorprendente es que "bootstrapping" (que se
81 | interpreta a si mismo). ¡También es implementado en alrededor de 500 lineas!
82 |
83 | Mientras tanto he leido muchos tutoriales sobre compiladores, son o muy
84 | simples (tal como implementar una simple calculadora) o utilizan
85 | herramientas de automación (tales como flex/bison). c4 es, sin embargo,
86 | implementado desde cero. Lo triste es que intenta ser mínimo, eso hace el
87 | codigo bastante rebuscado, y difícil de entender. Asique he empezado un
88 | nuevo proyecto tambien:
89 |
90 | 1. Implementar un compilador de C (en relidad, interprete)
91 | 2. Escribit un tutorial de como es construido.
92 |
93 | Me tomo una semana rescribirlo, resultando en 1400 de lineas incluyendo
94 | comentarios. El proyecto es hosteado en GitHub [Write a C interpreter](https://github.com/lotabout/write-a-C-interpreter)
95 |
96 | ¡Gracias a rswier por traernos un maravilloso proyecto!
97 |
98 | ## Antes de que te vallas
99 |
100 | Implementar un compilador puede ser aburrido y difícil de debuggear. Asique
101 | espero que puedas invertir sufuciente tiempo estudiando, tanto como
102 | escribiendo el codigo. Estoy seguro que sentiras una gran sensación de
103 | logro tal como you lo hago.
104 |
105 | ## Buenos recursos
106 |
107 | 1. [Let's Build a Compiler](http://compilers.ieec.com/crenshaw/): un muy
108 | buen tutorial para contruir un compilador para novatos.
109 | 2. [Lemon Parser Generator](http://www.hwaci.com/sw/lemon/): el generador
110 | de analizadores que es usado en SQLite. Buena lectura si quieres
111 | entender teoria de compiladores con codigo.
112 |
113 | Al final del dia, soy un humano con un nivel general, habran inevitablemente
114 | errores con los articulos y el código (también mi inglés). ¡Sientete libre
115 | de corregirme! (nota del traductor: aunque yo sea español y tenga un alto
116 | nivel de ingles, cometere errores. Si quieres notificarme sobre ellos manda
117 | un mensaje a mi email greenerclay@gmail.com, o haz una PR tu mismo)
118 |
119 | Espero que lo disfrutes.
120 |
--------------------------------------------------------------------------------
/tutorial/ko/0-머리말.md:
--------------------------------------------------------------------------------
1 | 이 시리즈는 C 컴파일러를 완전 처음에서부터 만드는 튜토리얼이에요.
2 |
3 | 윗문장에서 제가 한 가지 거짓말을 한 게 있어요: 사실 *컴파일러* 대신에
4 | *인터프리터*예요. "C 인터프리터"가 대체 뭐길래 거짓말을 한거죠? 하지만 아마
5 | 인터프리터를 만듦으로서 컴파일러를 더 잘 이해할 수 있게 될겁니다.
6 |
7 | 맞아요, 전 여러분이 어떻게 컴파일러가 구성되어있는지에 대해 기본적인 이해를 하고
8 | 인터프리터를 만드는게 그렇게까지 어렵지 않다는걸 깨닫길 바래요. 행운을 빕니다!
9 |
10 | 마지막으로, 이 시리즈는 제작자가 처음에 중국어로 적고, 직접 영어로 번역했어요.
11 | 그리고 그걸 제가 또 한국어로 번역했고요. 만약에 어색한 표현이나 이상한 부분이
12 | 있다면 정정해주세요.
13 |
14 | 이번 챕터에서 코드를 적지는 않을거라서 코드를 보고 싶으신 분들은 건너 뛰셔도
15 | 상관 없어요...
16 |
17 | ## 왜 컴파일러 이론에 관심을 가져야 하죠?
18 |
19 | 왜냐면 **멋지기** 때문이죠!
20 |
21 | 그리고 매우 유용해요. 프로그램들은 우리에게 무언가를 해주기 위해 만들어져요.
22 | 만약에 그것들이 어떤 형태의 데이터를 다른 형태로 변환하는데 쓰인다면 우리는 그걸
23 | 컴파일러라고 부르죠. 따라서 몇가지 컴파일러 이론을 배우는 것을 통해 우리는
24 | 매우 강력한 문제 해결 기술을 마스터 해볼 수 있어요. 충분히 멋지지 않나요?
25 |
26 | 사람들은 컴파일러가 어떻게 동작하는지 이해하는 것은 더 좋은 코드를 짜는데 도움을
27 | 줄 수 있다고 말하곤 하죠. 일부는 최신 컴파일러들은 최적화를 너무 잘 해줘서
28 | 더이상 신경쓰지 않아도 된다고 말할 거예요. 음... 맞는 말이에요. 대부분의
29 | 사람들은 단지 코드의 효율성만을 높이기 위해 컴파일러 이론을 배울 필요는 없죠.
30 | 그리고 대부분의 사람들은 당신을 의미해요!
31 |
32 | 전 컴파일러 이론이 프로그래밍을 쉽게 만들어주는 것이기 때문에 항상 경외해왔어요.
33 | 어쨌든 어셈블리어만으로 웹브라우저를 만드는 걸 상상할 수 있나요? 그래서 제가
34 | 대학에서 컴파일러 이론을 배울 기회가 생겼을 때, 전 너무 기뻤어요!
35 | 그리고나서... 그게 뭔지 이해를 못 해서 그만 뒀죠.
36 |
37 | 보통 컴파일러 코스는 다음을 다뤄요:
38 |
39 | 1. 구문 표현 방법(BNF 등)
40 | 2. NFA(Nondeterministic Finite Automata; 비결정적 유한 오토마타),
41 | DFA(Deterministic Finite Automata; 결정적 유한 오토마타)와 렉서
42 | 3. 파서(재귀하향, LL(k), LALR 등)
43 | 4. 중간언어
44 | 5. 코드 생성
45 | 6. 코드 최적화
46 |
47 | 아마도 90% 이상의 학생들은 파서 이상은 관심이 없을거예요. 게다가 우린 아직도
48 | 어떻게 컴러를 만드는지 몰라요! 심지어 그 이론들을 열심히 배운 뒤에도 말이죠.
49 | 음, 주된 원인은 "컴파일러 이론"이 가르치려고 하는게
50 | "파서 생성기 만드는 방법", 즉 구문 문법을 이용해서 컴파일러를 만들어주는
51 | 도구이기 때문이죠. lex/yacc나 flex/bison 같은 것들 말이에요.
52 |
53 | 이런 이론들은 컴파일러를 자동으로 생성하는 일반적인 문제의 해결법을
54 | 가르쳐줍니다. 한 번 마스터하고 나면 모든 종류의 문법을 다룰 수 있다는 걸
55 | 의미해요. 산업에서 정말 유용해요. 그렇지만 학생들과 프로그래머에게 너무 강력하고
56 | 너무 복잡해요. lex/yacc의 소스코드를 한 번 읽어보면 이해가 될거예요.
57 |
58 | 좋은 소식은 컴파일러를 만드는 건 상상하는 것보다 훨씬 쉽다는 거예요. 거짓말은
59 | 안 할게요. 쉽진 않지만 그렇다고 그렇게 어렵지는 않아요.
60 |
61 | ## 프로젝트의 탄생
62 |
63 | 어느날 깃헙에서 [c4](https://github.com/rswier/c4)를 발견했어요. 단지 네 가지의
64 | 함수만으로 구현되었다고 하는 작은 C 인터프리터예요. 가장 놀라운 부분은 그게
65 | 부트스트랩(스스로를 인터프리트 해요)이라는 거예요. 게다가 500줄 정도의 코드로
66 | 완성되었어요!
67 |
68 | 한편 전 컴파일러에 대한 많은 튜토리얼을 읽었어요, 너무 간단하거나(간단한 계산기
69 | 구현 등) 자동화 도구(flex/bison 등)을 이용했어요. 하지만 c4는 완전히 처음부터
70 | 구현되었어요. 하지만 슬픈 점은 c4가 최소한이 되려고 하는데, 이 점이 코드를 꽤
71 | 어지럽고 이해하기 어렵게 만들어요. 그래서 아래와 같은 목적을 위해서 새로운
72 | 프로젝트를 시작했어요:
73 |
74 | 1. 작동하는 C 컴파일러(사실은 인터프리터) 구현하기
75 | 2. 어떻게 만들어졌는지에 대한 튜토리얼 작성하기
76 |
77 | 다시 작성하는데 일주일이 걸렸고 결과적으로 주석을 포함해서 1400줄이 되었어요.
78 | 프로젝트는 깃헙에서 호스팅되고 있어요:
79 | [Write a C Interpreter](https://github.com/lotabout/write-a-C-interpreter).
80 |
81 | 멋진 프로젝트를 제공해주신 rswier에게 감사드립니다!
82 |
83 | ## 가기 전에
84 |
85 | 컴파일러를 구현하는 건 지루할 수 있고 디버깅하기가 어려워요. 그래서 저는
86 | 코드를 타이핑하는 것 뿐만 아니라 공부하는 데 충분한 시간을 할애해줬으면 해요.
87 | 제가 느꼈던 것처럼 큰 성취감을 느낄 수 있을 거라고 장담해요.
88 |
89 | ## 좋은 리소스
90 | 1. [Let’s Build a Compiler](http://compilers.iecc.com/crenshaw/): 새내기들에게
91 | 아주 좋은 컴파일러 제작에 관한 튜토리얼
92 | 2. [Lemon Parser Generator](http://www.hwaci.com/sw/lemon/): SQLite에서 사용되는
93 | 파서 생성기예요. 코드로 컴파일러 이론을 이해하고싶다면 읽어보는 것도 좋아요.
94 |
95 | 결론적으로 저는 일반적인 수준의 사람이라 필연적으로 내용과 코드에 문제가
96 | 있을거예요. (제 번역도요...). 얼마든지 정정해주세요!
97 |
98 | 그럼, 즐기길 바래요
99 |
--------------------------------------------------------------------------------
/tutorial/pt-br/0-prefacio.md:
--------------------------------------------------------------------------------
1 | Esta série de artigos é um tutorial para construir um compilador C do zero.
2 |
3 | Menti um pouco na frase acima: é na verdade um interpretador em vez de compilador. Menti porque, afinal, o que é um "interpretador C"? No entanto, você entenderá melhor os compiladores construindo um interpretador.
4 |
5 | Sim, espero que você possa ter uma compreensão básica de como um compilador é construído e perceba que não é tão difícil construir um. Boa sorte!
6 |
7 | Por fim, esta série foi escrita originalmente em chinês. Sinta-se à vontade para me corrigir se estiver confuso com o meu inglês. E eu ficaria muito grato se você pudesse me ensinar um pouco de inglês "nativo" :)
8 |
9 | Não escreveremos nenhum código neste capítulo, sinta-se à vontade para pular se estiver ansioso para ver algum código...
10 |
11 | ## Por que você deve se importar com a teoria dos compiladores?
12 |
13 | Porque é INCRÍVEL!
14 |
15 | E é muito útil. Programas são construídos para fazer algo por nós; quando são usados para traduzir uma forma de dados para outra, podemos chamá-los de compilador. Assim, ao aprender um pouco sobre a teoria dos compiladores, estamos tentando dominar uma técnica muito poderosa de resolução de problemas. Isso não é incrível para você?
16 |
17 | As pessoas costumavam dizer que entender como um compilador funciona ajudaria você a escrever um código melhor. Alguns argumentariam que os compiladores modernos são tão bons em otimização que você não deveria mais se preocupar. Bem, é verdade, a maioria das pessoas não precisa aprender a teoria dos compiladores apenas para melhorar a eficiência do código. E por maioria das pessoas, quero dizer você!
18 |
19 | ## Também Não Gostamos de Teoria
20 |
21 | Sempre fiquei impressionado com a teoria dos compiladores porque é isso que torna a programação fácil. De qualquer forma, você consegue imaginar construir um navegador web apenas em linguagem assembly? Então, quando tive a chance de aprender a teoria dos compiladores na faculdade, fiquei tão animado! E então... desisti, sem entender nada.
22 |
23 | Normalmente, um curso de compilador cobrirá:
24 |
25 | * Como representar sintaxe (como BNF, etc.)
26 |
27 | * Lexer, com algo como NFA (Autômato Finito Não Determinístico), DFA (Autômato Finito Determinístico).
28 |
29 | * Parser, como descida recursiva, LL(k), LALR, etc.
30 |
31 | * Linguagens Intermediárias.
32 |
33 | * Geração de código.
34 |
35 | * Otimização de código.
36 |
37 | Talvez mais de 90% dos alunos não se importem com nada além do parser e, além disso, ainda não sabemos como construir um compilador! Mesmo depois de todo o esforço para aprender as teorias. Bem, a principal razão é que o que a "Teoria do Compilador" tenta ensinar é "Como construir um gerador de parser", ou seja, uma ferramenta que consome gramática de sintaxe e gera um compilador para você. Como lex/yacc ou flex/bison.
38 |
39 | Essas teorias tentam nos ensinar como resolver os problemas gerais de geração de compiladores automaticamente. Isso significa que, uma vez que você os tenha dominado, será capaz de lidar com todos os tipos de gramáticas. Eles são realmente úteis na indústria. No entanto, são muito poderosos e complicados para estudantes e a maioria dos programadores. Você entenderá isso se tentar ler o código-fonte do lex/yacc.
40 |
41 | A boa notícia é que construir um compilador pode ser muito mais simples do que você imagina. Não vou mentir, não é fácil, mas definitivamente não é difícil.
42 |
43 | ## Nascimento deste projeto
44 |
45 | Um dia me deparei com o projeto c4 no Github. É um pequeno interpretador C que se diz ser implementado por apenas 4 funções. A parte mais incrível é que ele é auto-suficiente (interpreta a si mesmo). Além disso, é feito com cerca de 500 linhas!
46 |
47 | Ao mesmo tempo, li muitos tutoriais sobre compiladores, eles são ou muito simples (como implementar uma calculadora simples) ou usam ferramentas automáticas (como flex/bison). O c4, no entanto, é implementado do zero. A parte triste é que ele tenta ser minimalista, o que torna o código uma bagunça, difícil de entender. Então, comecei um novo projeto para:
48 |
49 | * Implementar um compilador C funcional (na verdade, um interpretador)
50 | * Escrever um tutorial de como ele é construído.
51 |
52 | Levei 1 semana para reescrevê-lo, resultando em 1400 linhas, incluindo comentários. O projeto está hospedado no Github: Escreva um Interpretador C.
53 |
54 | Obrigado rswier por nos trazer um projeto maravilhoso!
55 |
56 | ## Antes de você ir
57 |
58 | Implementar um compilador pode ser entediante e é difícil de depurar. Então, espero que você possa dedicar tempo suficiente para estudar, bem como digitar o código. Tenho certeza de que você sentirá um grande senso de realização, assim como eu.
59 |
60 | ## Bons Recursos
61 |
62 | * Vamos Construir um Compilador: um tutorial muito bom para construir um compilador para iniciantes.
63 |
64 | * Lemon Parser Generator: o gerador de parser usado no SQLite. Bom para ler se você quer entender a teoria dos compiladores com código.
65 |
66 | No final, sou humano com um nível geral, haverá inevitavelmente erros nos artigos e códigos (e também no meu inglês). Sinta-se à vontade para me corrigir!
67 |
68 | Espero que você goste.
69 |
--------------------------------------------------------------------------------
/tutorial/pt-br/1-Esqueleto.md:
--------------------------------------------------------------------------------
1 | Neste capítulo, teremos uma visão geral da estrutura do compilador.
2 |
3 | Antes de começarmos, gostaria de reforçar que é um interpretador que queremos construir. Isso significa que podemos executar um arquivo fonte C como um script. Ele é escolhido principalmente por duas razões:
4 |
5 | * O interpretador difere do compilador apenas na fase de geração de código, portanto, ainda aprenderemos todas as técnicas essenciais de construção de um compilador (como análise lexical e análise sintática).
6 |
7 | * Construiremos nossa própria máquina virtual e instruções de montagem, o que nos ajudará a entender como os computadores funcionam.
8 |
9 | ## Três Fases
10 |
11 | Dado um arquivo fonte, normalmente o compilador realizará três fases de processamento:
12 |
13 | * Análise Lexical: converte strings de origem em fluxo de tokens internos.
14 |
15 | * Análise Sintática: consome o fluxo de tokens e constrói a árvore sintática.
16 |
17 | * Geração de Código: percorre a árvore sintática e gera código para a plataforma alvo.
18 |
19 | A construção de compiladores tornou-se tão madura que as partes 1 e 2 podem ser feitas por ferramentas automáticas. Por exemplo, flex pode ser usado para análise lexical, e bison para análise sintática. Eles são poderosos, mas fazem milhares de coisas nos bastidores. Para entender completamente como construir um compilador, vamos construí-los todos do zero.
20 |
21 | Assim, construiremos nosso interpretador nas seguintes etapas:
22 |
23 | * Construir nossa própria máquina virtual e conjunto de instruções. Esta é a plataforma alvo que será usada em nossa fase de geração de código.
24 |
25 | * Construir nosso próprio analisador léxico para o compilador C.
26 |
27 | * Escrever um analisador sintático de descida recursiva por conta própria.
28 |
29 | ## Esqueleto do nosso compilador
30 |
31 | Modelando a partir do c4, nosso compilador inclui 4 funções principais:
32 |
33 | * next() para análise lexical; obter o próximo token; ignorará espaços, tabs, etc.
34 |
35 | * program() entrada principal para o analisador sintático.
36 |
37 | * expression(level): analisar expressão; o nível será explicado em capítulos posteriores.
38 |
39 | * eval(): a entrada para a máquina virtual; usado para interpretar instruções alvo.
40 |
41 | Por que expression existe quando temos program para análise sintática? Isso ocorre porque o analisador para expressões é relativamente independente e complexo, então o colocamos em um único módulo (função).
42 |
43 | O código é o seguinte:
44 |
45 | ```c
46 | #include
47 | #include
48 | #include
49 | #include
50 | #define int long long // trabalhar com alvo de 64 bits
51 |
52 | int token; // token atual
53 | char *src, *old_src; // ponteiro para a string de código fonte;
54 | int poolsize; // tamanho padrão de texto/dados/pilha
55 | int line; // número da linha
56 |
57 | void next() {
58 | token = *src++;
59 | return;
60 | }
61 |
62 | void expression(int level) {
63 | // não faz nada
64 | }
65 |
66 | void program() {
67 | next(); // obter o próximo token
68 | while (token > 0) {
69 | printf("token é: %c\n", token);
70 | next();
71 | }
72 | }
73 |
74 | int eval() { // ainda não faz nada
75 | return 0;
76 | }
77 |
78 | int main(int argc, char **argv)
79 | {
80 | int i, fd;
81 |
82 | argc--;
83 | argv++;
84 |
85 | poolsize = 256 * 1024; // tamanho arbitrário
86 | line = 1;
87 |
88 | if ((fd = open(*argv, 0)) < 0) {
89 | printf("não foi possível abrir(%s)\n", *argv);
90 | return -1;
91 | }
92 |
93 | if (!(src = old_src = malloc(poolsize))) {
94 | printf("não foi possível alocar(%d) para a área de origem\n", poolsize);
95 | return -1;
96 | }
97 |
98 | // ler o arquivo fonte
99 | if ((i = read(fd, src, poolsize-1)) <= 0) {
100 | printf("read() retornou %d\n", i);
101 | return -1;
102 | }
103 |
104 | src[i] = 0; // adicionar caractere EOF
105 | close(fd);
106 |
107 | program();
108 | return eval();
109 | }
110 | ```
111 |
112 | É bastante código para o primeiro capítulo do artigo. No entanto, é simples o suficiente. O código tenta ler um arquivo fonte, caractere por caractere e imprimi-los.
113 |
114 | Atualmente, o analisador léxico next() não faz nada além de retornar os caracteres como estão no arquivo fonte. O analisador sintático program() também não cuida de seu trabalho, nenhuma árvore sintática é gerada, nenhum código alvo é gerado.
115 |
116 | O importante aqui é entender o significado dessas funções e como elas estão conectadas, pois são o esqueleto do nosso interpretador. Preencheremos eles passo a passo nos próximos capítulos.
117 |
118 | ## Código
119 |
120 | O código deste capítulo pode ser baixado do Github, ou clonado por:
121 |
122 | ```shell
123 | git clone -b step-0 https://github.com/lotabout/write-a-C-interpreter
124 | ```
125 |
126 | Note que eu posso corrigir bugs mais tarde, e se houver alguma inconsistência entre o artigo e os ramos de código, siga o artigo. Só atualizarei o código no branch principal.
127 |
128 | ## Resumo
129 |
130 | Após digitar um pouco, temos o compilador mais simples: um compilador que não faz nada. No próximo capítulo, implementaremos a função eval, ou seja, nossa própria máquina virtual. Até lá.
131 |
132 |
133 | Espero que isso ajude! Se você tiver mais perguntas ou precisar de mais traduções, estou aqui para ajudar.
--------------------------------------------------------------------------------
/tutorial/pt-br/2-Maquina-Virtual.md:
--------------------------------------------------------------------------------
1 | Neste capítulo, vamos construir uma máquina virtual e projetar nosso próprio conjunto de instruções que será executado na VM. Esta VM será a plataforma alvo da fase de geração de código do interpretador.
2 |
3 | Se você já ouviu falar de JVM e bytecode, é isso que estamos tentando construir, mas de uma forma muito mais simples.
4 |
5 | ## Como o computador funciona internamente
6 |
7 | Existem três componentes que precisamos considerar: CPU, registradores e memória. O código (ou instrução de montagem) é armazenado na memória como dados binários; a CPU recuperará a instrução uma por uma e as executará; os estados de execução da máquina são armazenados nos registradores.
8 |
9 | ## Memória
10 |
11 | A memória pode ser usada para armazenar dados. Por dados, quero dizer código (ou chamado de instruções de montagem) ou outros dados, como a mensagem que você deseja imprimir. Todos eles são armazenados como binários.
12 |
13 | O sistema operacional moderno introduziu a Memória Virtual, que mapeia endereços de memória usados por um programa, chamados de endereço virtual, para endereços físicos na memória do computador. Ela pode ocultar os detalhes físicos da memória do programa.
14 |
15 | O benefício da memória virtual é que ela pode ocultar os detalhes de uma memória física dos programas. Por exemplo, em uma máquina de 32 bits, todos os endereços de memória disponíveis são 2^32 = 4G, enquanto a memória física real pode ser apenas 256M. O programa ainda pensará que pode ter 4G de memória para usar, e o SO mapeará esses endereços para os físicos.
16 |
17 | Claro, você não precisa entender os detalhes sobre isso. Mas o que você deve entender é que a memória utilizável de um programa é dividida em vários segmentos:
18 |
19 | 1.`text` Segmento : para armazenar código (instruções)
20 |
21 | 2. `data` Segmento : para armazenar dados inicializados. Por exemplo, int i = 10; precisará utilizar este segmento.
22 |
23 | 3. `bss` Segmento : para armazenar dados não inicializados. Por exemplo, int i[1000]; não precisa ocupar 1000*4 bytes, porque os valores reais no array não importam, então podemos armazená-los no bss para economizar espaço.
24 |
25 | 4. `stack` Segmento: usado para lidar com os estados das chamadas de função, como quadros de chamada e variáveis locais de uma função.
26 |
27 | 5. `heap` Segmento: usado para alocar memória dinamicamente para o programa.
28 |
29 | Um exemplo do layout desses segmentos é:
30 |
31 |
32 | ```
33 | +------------------+
34 | | stack | | endereço alto
35 | | ... v |
36 | | |
37 | | |
38 | | |
39 | | |
40 | | ... ^ |
41 | | heap | |
42 | +------------------+
43 | | segmento bss |
44 | +------------------+
45 | | segmento data |
46 | +------------------+
47 | | segmento text | endereço baixo
48 | +------------------+
49 | ```
50 |
51 | Nossa máquina virtual tende a ser o mais simples possível, então não nos preocupamos com os segmentos `bss` e `heap`. Nosso interpretador não suporta a inicialização de dados, então vamos mesclar os segmentos `data` e `bss`. Além disso, usamos o segmento `data` apenas para armazenar literais de string.
52 |
53 | Vamos descartar o `heap` também. Isso pode parecer insano porque, teoricamente, a VM deveria manter um `heap` para alocação de memórias. Mas ei, um interpretador em si também é um programa que teve seu heap alocado pelo nosso computador. Podemos dizer ao programa que queremos interpretar para utilizar o heap do interpretador, introduzindo uma instrução `MSET` Não diria que é uma trapaça porque reduz a complexidade da VM sem reduzir o conhecimento que queremos aprender sobre o compilador.
54 |
55 | Assim, adicionamos os seguintes códigos na área global:
56 |
57 | ```c
58 | int *text, // segmento de texto
59 | *old_text, // para despejar segmento de texto
60 | *stack; // pilha
61 | char *data; // segmento de dados
62 | ```
63 |
64 | Note o `int` aqui. O que deveríamos escrever é, na verdade, `unsigned` porque armazenaremos dados não assinados (como ponteiros/endereços de memória) no segmento `text`. Note que queremos que nosso interpretador seja inicializado (interprete-se), então não queremos introduzir `unsigned`. Finalmente, o `data` é `char *` porque o usaremos para armazenar apenas literais de string.
65 |
66 | Finalmente, adicione o código na função principal para realmente alocar os segmentos:
67 |
68 |
69 | ```c
70 | int main() {
71 | close(fd);
72 |
73 |
74 | // aloca memória para a máquina virtual
75 | if (!(text = old_text = malloc(poolsize))) {
76 | printf("não foi possível alocar(%d) para a área de texto\n", poolsize);
77 | return -1;
78 | }
79 | if (!(data = malloc(poolsize))) {
80 | printf("não foi possível alocar(%d) para a área de dados\n", poolsize);
81 | return -1;
82 | }
83 | if (!(stack = malloc(poolsize))) {
84 | printf("não foi possível alocar(%d) para a área de pilha\n", poolsize);
85 | return -1;
86 | }
87 |
88 | memset(text, 0, poolsize);
89 | memset(data, 0, poolsize);
90 | memset(stack, 0, poolsize);
91 |
92 | ...
93 | program();
94 | }
95 | ```
96 |
97 | ## Registradores
98 |
99 | Os registradores são usados para armazenar os estados de execução dos computadores. Existem vários deles em computadores reais, enquanto nossa VM usa apenas 4:
100 |
101 | 1. `PC` contador de programa, armazena um endereço de memória no qual está armazenada a instrução próxima a ser executada.
102 |
103 | 2. `SP` ponteiro de pilha, que sempre aponta para o topo da pilha. Observe que a pilha cresce de endereços altos para endereços baixos, então, quando empurramos um novo elemento para a pilha, `SP` diminui.
104 |
105 | 3. `BP` : ponteiro base, aponta para alguns elementos na pilha. É usado em chamadas de função.
106 |
107 | 4. `AX`: um registrador geral que usamos para armazenar o resultado de uma instrução.
108 |
109 | Para entender completamente por que precisamos desses registradores, você precisa entender quais estados um computador precisa armazenar durante a computação. Eles são apenas um lugar para armazenar valor. Você terá uma compreensão melhor após terminar este capítulo.
110 |
111 | Bem, adicione algum código na área global:
112 |
113 | ```c
114 | int *pc, *bp, *sp, ax, cycle; // registradores da máquina virtual
115 | ```
116 |
117 | E adicione o código de inicialização na função `main`.Note que `pc` deve apontar para `main`do programa a ser interpretado. Mas ainda não temos nenhuma geração de código, então pulamos por enquanto.
118 |
119 | ```c
120 | memset(stack, 0, poolsize);
121 | ...
122 |
123 | bp = sp = (int *)((int)stack + poolsize);
124 | ax = 0;
125 |
126 | ...
127 | program();
128 | ```
129 | O que resta é a parte da CPU, o que devemos fazer é implementar os conjuntos de instruções. Vamos salvar isso para uma nova seção.
130 |
131 | ## Conjunto de Instruções
132 |
133 | O conjunto de instruções é um conjunto de instruções que a CPU pode entender, é a linguagem que precisamos dominar para conversar com a CPU. Vamos projetar uma linguagem para nossa VM, ela é baseada no conjunto de instruções x86, mas muito mais simples.
134 |
135 | Começaremos adicionando um tipo `enum` listando todas as instruções que nossa VM entenderia:
136 |
137 | ```c
138 | // instruções
139 | enum { LEA ,IMM ,JMP ,CALL,JZ ,JNZ ,ENT ,ADJ ,LEV ,LI ,LC ,SI ,SC ,PUSH,
140 | OR ,XOR ,AND ,EQ ,NE ,LT ,GT ,LE ,GE ,SHL ,SHR ,ADD ,SUB ,MUL ,DIV ,MOD ,
141 | OPEN,READ,CLOS,PRTF,MALC,MSET,MCMP,EXIT };
142 |
143 | ```
144 | Essas instruções são ordenadas intencionalmente, pois você descobrirá mais tarde que as instruções com argumentos vêm primeiro, enquanto aquelas sem argumentos vêm depois. O único benefício aqui é para imprimir informações de depuração. No entanto, não contaremos com essa ordem para introduzi-los.
145 |
146 | ## MOV
147 |
148 | `MOV` é uma das instruções mais fundamentais que você encontrará. Sua função é mover dados para registradores ou para a memória, algo como a expressão de atribuição em C. Existem dois argumentos na instrução `MOV` do `x86`: MOV dest, `source` (estilo Intel), source pode ser um número, um registrador ou um endereço de memória.
149 |
150 | Mas não seguiremos o `x86`. Por um lado, nossa VM tem apenas um registrador geral (`AX`), por outro lado, é difícil determinar o tipo dos argumentos (se é um número, registrador ou endereço). Portanto, dividimos `MOV` em 5 partes:
151 |
152 | 1. `IMM ` para colocar o valor imediato `` no registrador `AX`.
153 |
154 | 2. `LC` para carregar um caractere em `AX` a partir de um endereço de memória que está armazenado em `AX` antes da execução.
155 |
156 | 3. ` LI` assim como `LC`, mas lida com um inteiro em vez de um caractere.
157 |
158 | 4. `SC` para armazenar o caractere em `AX` na memória cujo endereço está armazenado no topo do segmento de pilha.
159 |
160 | 5. `SI` assim como `SC`, mas lida com um inteiro em vez de um caractere.
161 |
162 | O quê? Eu quero um `MOV`, não 5 instruções apenas para substituí-lo! Não entre em pânico! Você deve saber que `MOV` é na verdade um conjunto de instruções que depende do tipo de seus argumentos, então você tem `MOVB` para bytes e `MOVW` para palavras, etc. Agora `LC/SC e LI/SI` não parecem tão ruins, certo?
163 |
164 | Bem, a razão mais importante é que, ao transformar `MOV` em 5 subinstruções, reduzimos muito a complexidade! Apenas `IMM` aceitará um argumento agora, mas não precisa se preocupar com seu tipo.
165 |
166 | Vamos implementá-lo na função `eval`:
167 |
168 | ```c
169 | void eval() {
170 | int op, *tmp;
171 | while (1) {
172 | op = *pc++; // obter o próximo código de operação
173 | if (op == IMM) {ax = *pc++;} // carregar valor imediato em ax
174 | else if (op == LC) {ax = *(char *)ax;} // carregar caractere em ax, endereço em ax
175 | else if (op == LI) {ax = *(int *)ax;} // carregar inteiro em ax, endereço em ax
176 | else if (op == SC) {ax = *(char *)*sp++ = ax;} // salvar caractere no endereço, valor em ax, endereço na pilha
177 | else if (op == SI) {*(int *)*sp++ = ax;} // salvar inteiro no endereço, valor em ax, endereço na pilha
178 | }
179 |
180 | ...
181 | return 0;
182 | }
183 | ```
184 |
185 | `*sp++` é usado para `POP` um elemento da pilha.
186 |
187 | Você pode se perguntar por que armazenamos o endereço no registrador `AX` para `LI/LC`, enquanto os armazenamos no topo do segmento de pilha para `SI/SC`. A razão é que o resultado de uma instrução é armazenado em `AX` por padrão. O endereço de memória também é calculado por uma instrução, portanto, é mais conveniente para `LI/LC` buscá-lo diretamente de `AX`. Além disso, `PUSH` só pode empurrar o valor de `AX` para a pilha. Então, se quisermos colocar um endereço na pilha, teríamos que armazená-lo em `AX` de qualquer maneira, por que não pular isso?
188 |
189 | ## PUSH
190 |
191 | `PUSH` no `x86` pode empurrar um valor imediato ou o valor de um registrador para a pilha. Aqui, em nossa `VM`, `PUSH`` empurrará o valor em `AX` para a pilha, apenas.
192 |
193 | ```c
194 | else if (op == PUSH) {*--sp = ax;} // empurrar o valor de ax para a pilha
195 | ```
196 |
197 | JMP
198 |
199 | `JMP ` definirá incondicionalmente o valor do registrador `PC` para ``.
200 |
201 | ```c
202 | else if (op == JMP) {pc = (int *)*pc;} // pular para o endereço
203 | ```
204 |
205 | Observe que `PC` aponta para a instrução PRÓXIMA a ser executada. Assim, `*pc` armazena o argumento da instrução `JMP`, ou seja, o .
206 |
207 | ## JZ/JNZ
208 |
209 | Precisaremos de um salto condicional para implementar a instrução `if`. Apenas dois são necessários aqui para pular quando `AX` é 0 ou não.
210 |
211 | ```c
212 | else if (op == JZ) {pc = ax ? pc + 1 : (int *)*pc;} // pular se ax for zero
213 | else if (op == JNZ) {pc = ax ? (int *)*pc : pc + 1;} // pular se ax não for zero
214 | ```
215 |
216 | ## Chamada de Função
217 |
218 | sso introduzirá o quadro de chamada, que é difícil de entender, então o colocamos junto para lhe dar uma visão geral. Vamos adicionar `CALL, ENT, ADJ e LEV` para suportar chamadas de função.
219 |
220 | Uma função é um bloco de código, ela pode estar fisicamente distante da instrução que estamos executando atualmente. Portanto, precisaremos de `JMP` para o ponto inicial de uma função. Então, por que introduzir uma nova instrução `CALL`? Porque precisaremos fazer algumas anotações: armazenar a posição de execução atual para que o programa possa retomar após o retorno da chamada de função.
221 |
222 | Portanto, precisaremos de `CALL` para chamar a função cujo ponto inicial é e `RET` para buscar as informações de anotação para retomar a execução anterior.
223 |
224 | ```c
225 | else if (op == CALL) {*--sp = (int)(pc+1); pc = (int *)*pc;} // chamar sub-rotina
226 | //else if (op == RET) {pc = (int *)*sp++;} // retornar da sub-rotina;
227 | ```
228 |
229 | Comentamos `RET` porque o substituiremos por `LEV` mais tarde.
230 |
231 | Na prática, o compilador deve lidar com mais: como passar os argumentos para uma função? Como retornar os dados da função?
232 |
233 | Nossa convenção aqui sobre o valor de retorno é armazená-lo em `AX`, não importa se você está retornando um valor ou um endereço de memória. E quanto ao argumento?
234 |
235 | Diferentes linguagens têm diferentes convenções, aqui está o padrão para C:
236 |
237 | 1. É dever do chamador empurrar os argumentos para a pilha.
238 |
239 | 2. Após o retorno da chamada da função, o chamador precisa retirar os argumentos.
240 |
241 | 3. Os argumentos são empurrados na ordem inversa.
242 |
243 | Observe que não seguiremos a regra 3. Agora, vamos verificar como o padrão C funciona
244 |
245 | ```c
246 | int callee(int, int, int);
247 |
248 | int caller(void)
249 | {
250 | int i, ret;
251 |
252 | ret = callee(1, 2, 3);
253 | ret += 5;
254 | return ret;
255 | }
256 | ```
257 | O compilador gerará as seguintes instruções de montagem:
258 |
259 | ```caller:
260 | ; make new call frame
261 | push ebp
262 | mov ebp, esp
263 | sub 1, esp ; save stack for variable: i
264 | ; push call arguments
265 | push 3
266 | push 2
267 | push 1
268 | ; call subroutine 'callee'
269 | call callee
270 | ; remove arguments from frame
271 | add esp, 12
272 | ; use subroutine result
273 | add eax, 5
274 | ; restore old call frame
275 | mov esp, ebp
276 | pop ebp
277 | ; return
278 | ```
279 |
280 | Aqui, `push` empurra os argumentos para a pilha, `call` chama a função e add esp, 12 remove os argumentos da pilha após o retorno da função. `eax` é o registrador usado para armazenar o valor de retorno em `x86`.
281 |
282 | Espero que isso tenha lhe dado uma boa introdução à construção de uma máquina virtual e ao design de um conjunto de instruções. No próximo capítulo, continuaremos a explorar mais instruções e como elas são implementadas em nossa VM.
283 |
284 |
285 | As instruções de montagem acima não podem ser realizadas em nossa VM devido a várias razões:
286 |
287 | 1. `push ebp`, enquanto nosso `PUSH` não aceita argumentos.
288 |
289 | 2. `move ebp, esp`, nossa instrução `MOV` não pode fazer isso.
290 |
291 | 3. `add esp, 12`, ainda não podemos fazer isso (como você descobrirá mais tarde).
292 |
293 | Nosso conjunto de instruções é tão simples que não podemos suportar chamadas de função! Mas não vamos desistir de mudar nosso design porque seria muito complexo para nós. Então, vamos adicionar mais instruções! Pode custar muito em computadores reais adicionar uma nova instrução, mas não para uma máquina virtual.
294 |
295 |
296 |
297 | ## ENT
298 |
299 | `ENT ` é chamado quando estamos prestes a entrar na chamada de função para "criar um novo quadro de chamada". Ele armazenará o valor atual de `PC` na pilha e reservará algum espaço ( bytes) para armazenar as variáveis locais da função.
300 |
301 | ```asm
302 | ; criar novo quadro de chamada
303 | push ebp
304 | mov ebp, esp
305 | sub 1, esp ; reservar pilha para variável: i
306 | ```
307 |
308 | Será traduzido para:
309 |
310 | ```c
311 | else if (op == ENT) {*--sp = (int)bp; bp = sp; sp = sp - *pc++;} // criar novo quadro de pilha
312 | ```
313 |
314 | ## ADJ
315 |
316 | `ADJ ` é para ajustar a pilha, para "remover argumentos do quadro". Precisamos desta instrução principalmente porque nosso `ADD` não tem poder suficiente. Portanto, trate-o como uma instrução de adição especial.
317 |
318 | ```c
319 | ; remover argumentos do quadro
320 | add esp, 12
321 | ```
322 |
323 | É implementado como:
324 |
325 | ```c
326 | else if (op == ADJ) {sp = sp + *pc++;} // add esp,
327 | ```
328 | ## LEV
329 |
330 | Caso você não tenha notado, nosso conjunto de instruções não tem `POP`. `POP` em nosso compilador seria usado apenas quando a chamada de função retorna. Que é assim:
331 |
332 | ```c
333 | ; restaurar quadro de chamada antigo
334 | mov esp, ebp
335 | pop ebp
336 | ; retornar
337 | ret
338 | ```
339 |
340 | Assim, faremos outra instrução LEV para realizar o trabalho de `MOV`, `POP` e `RET`:
341 |
342 | ```c
343 | else if (op == LEV) {sp = bp; bp = (int *)*sp++; pc = (int *)*sp++;} // restaurar quadro de chamada e PC
344 | ```
345 | ## LEA
346 |
347 | As instruções introduzidas acima tentam resolver o problema de criar/destruir quadros de chamada, resta saber como buscar os argumentos dentro da subfunção.
348 |
349 | Mas vamos verificar como um quadro de chamada se parece antes de aprender como buscar argumentos (Note que os argumentos são empurrados em sua ordem de chamada):
350 |
351 | ```c
352 | sub_function(arg1, arg2, arg3);
353 |
354 | | .... | endereço alto
355 | +---------------+
356 | | arg: 1 | new_bp + 4
357 | +---------------+
358 | | arg: 2 | new_bp + 3
359 | +---------------+
360 | | arg: 3 | new_bp + 2
361 | +---------------+
362 | | endereço de retorno | new_bp + 1
363 | +---------------+
364 | | old BP | <- new BP
365 | +---------------+
366 | | var local 1 | new_bp - 1
367 | +---------------+
368 | | var local 2 | new_bp - 2
369 | +---------------+
370 | | .... | endereço baixo
371 |
372 | ```
373 | Então, se precisarmos nos referir ao `arg1`, precisamos buscar new_bp + 4, o que, no entanto, não pode ser alcançado por nossa pobre instrução ADD. Assim, faremos outra adição especial para fazer isso: `LEA `.
374 |
375 | ```c
376 | else if (op == LEA) {ax = (int)(bp + *pc++);} // carregar endereço para argumentos.
377 | ```
378 |
379 | Junto com as instruções acima, somos capazes de fazer chamadas de função.
380 |
381 | ## Instruções Matemáticas
382 |
383 | Nossa VM fornecerá uma instrução para cada operador na linguagem C. Cada operador tem dois argumentos: o primeiro é armazenado no topo da pilha, enquanto o segundo é armazenado em `AX`. A ordem importa, especialmente em operadores como `-, /.` Após o cálculo, o argumento na pilha será retirado e o resultado será armazenado em `AX`. Portanto, você não poderá buscar o primeiro argumento da pilha após o cálculo, observe isso.
384 |
385 | ```c
386 | else if (op == OR) ax = *sp++ | ax;
387 | else if (op == XOR) ax = *sp++ ^ ax;
388 | else if (op == AND) ax = *sp++ & ax;
389 | else if (op == EQ) ax = *sp++ == ax;
390 | else if (op == NE) ax = *sp++ != ax;
391 | else if (op == LT) ax = *sp++ < ax;
392 | else if (op == LE) ax = *sp++ <= ax;
393 | else if (op == GT) ax = *sp++ > ax;
394 | else if (op == GE) ax = *sp++ >= ax;
395 | else if (op == SHL) ax = *sp++ << ax;
396 | else if (op == SHR) ax = *sp++ >> ax;
397 | else if (op == ADD) ax = *sp++ + ax;
398 | else if (op == SUB) ax = *sp++ - ax;
399 | else if (op == MUL) ax = *sp++ * ax;
400 | else if (op == DIV) ax = *sp++ / ax;
401 | else if (op == MOD) ax = *sp++ % ax;
402 | ```
403 | ## Instruções Integradas
404 |
405 | Além da lógica central, um programa precisará de mecanismos de entrada/saída para poder interagir. printf em C é uma das funções de saída comumente usadas. printf é muito complexo de implementar, mas inevitável se nosso compilador quiser ser inicializado (interpretar a si mesmo), mas é sem sentido para construir um compilador.
406 |
407 | Nosso plano é criar novas instruções para construir uma ponte entre o programa interpretado e o próprio interpretador. Assim, podemos utilizar as bibliotecas do sistema host (seu computador que executa o interpretador).
408 |
409 | Precisaremos de `exit`, `open`, `close`, `read`, `printf`, `malloc`, `memset` e `memcmp`:
410 |
411 | ```c
412 | else if (op == EXIT) { printf("exit(%d)", *sp); return *sp;}
413 | else if (op == OPEN) { ax = open((char *)sp[1], sp[0]); }
414 | else if (op == CLOS) { ax = close(*sp);}
415 | else if (op == READ) { ax = read(sp[2], (char *)sp[1], *sp); }
416 | else if (op == PRTF) { tmp = sp + pc[1]; ax = printf((char *)tmp[-1], tmp[-2], tmp[-3], tmp[-4], tmp[-5], tmp[-6]); }
417 | else if (op == MALC) { ax = (int)malloc(*sp);}
418 | else if (op == MSET) { ax = (int)memset((char *)sp[2], sp[1], *sp);}
419 | else if (op == MCMP) { ax = memcmp((char *)sp[2], (char *)sp[1], *sp);}
420 | ```
421 |
422 | Por fim, adicione algum tratamento de erro:
423 |
424 | ```c
425 | else {
426 | printf("instrução desconhecida:%d\n", op);
427 | return -1;
428 | }
429 |
430 | ```
431 |
432 | ## Teste
433 |
434 | Agora faremos alguma "programação de montagem" para calcular `10 + 20`:
435 |
436 | ```c
437 | int main(int argc, char *argv[])
438 | {
439 | ax = 0;
440 | ...
441 | i = 0;
442 | text[i++] = IMM;
443 | text[i++] = 10;
444 | text[i++] = PUSH;
445 | text[i++] = IMM;
446 | text[i++] = 20;
447 | text[i++] = ADD;
448 | text[i++] = PUSH;
449 | text[i++] = EXIT;
450 | pc = text;
451 | ...
452 | program();
453 | }
454 |
455 | ```
456 |
457 | Compile o interpretador com `gcc xc-tutor.c` e execute-o com `./a.out hello.c`, obtendo o seguinte resultado:
458 |
459 | ```c
460 | exit(30)
461 | ```
462 |
463 | Note que especificamos `hello.c`, mas ele na verdade não é usado, precisamos dele porque o interpretador que construímos no último capítulo precisa dele.
464 |
465 | Bem, parece que nossa VM funciona bem :)
466 |
467 |
468 | ## Resumo
469 |
470 | Aprendemos como o computador funciona internamente e construímos nosso próprio conjunto de instruções modelado após as instruções de montagem x86. Estamos realmente tentando aprender a linguagem de montagem e como ela realmente funciona construindo nossa própria versão.
471 |
472 | O código deste capítulo pode ser baixado do Github, ou clonado por:
473 |
474 | ```
475 | git clone -b step-1 https://github.com/lotabout/write-a-C-interpreter
476 | ```
477 |
478 | Note que adicionar uma nova instrução exigiria o design de muitos circuitos e custaria muito. Mas é quase gratuito adicionar novas instruções em nossa máquina virtual. Estamos aproveitando isso para dividir as funções de uma instrução em várias para simplificar a implementação.
479 |
480 | Se você estiver interessado, construa seus próprios conjuntos de instruções!
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
--------------------------------------------------------------------------------
/tutorial/pt-br/3-Lexer.md:
--------------------------------------------------------------------------------
1 | > A análise léxica é o processo de converter uma sequência de caracteres (como
2 | > em um programa de computador ou página da web) em uma sequência de tokens (strings com
3 | > um "significado" identificado).
4 |
5 | Normalmente representamos o token como um par: `(tipo de token, valor do token)`. Por
6 | exemplo, se um arquivo fonte de um programa contém a string: "998", o lexer irá
7 | tratá-lo como token `(Número, 998)`, significando que é um número com valor de `998`.
8 |
9 | ## Lexer vs Compilador
10 |
11 | Vamos primeiro olhar para a estrutura de um compilador:
12 | ```
13 | +-------+ +--------+
14 | --codigo fonte -->| lexer | --> fluxo de tokens --> | parser | --> assembly
15 | +-------+ +--------+
16 | ```
17 |
18 |
19 | O Compilador pode ser tratado como um transformador que converte código fonte C em
20 | assembly. Nesse sentido, lexer e parser são transformadores também: Lexer
21 | pega o código fonte C como entrada e produz um fluxo de tokens; O Parser irá consumir o
22 | fluxo de tokens e gerar código assembly.
23 |
24 | Então, por que precisamos de um lexer e um parser? Bem, o trabalho do Compilador é difícil! Então nós
25 | recrutamos o lexer para fazer parte do trabalho e o parser para fazer o resto, para que cada
26 | um só precise lidar com uma tarefa simples.
27 |
28 | Esse é o valor de um lexer: simplificar o parser convertendo o fluxo
29 | de código fonte em fluxo de tokens.
30 |
31 | ## Escolha de Implementação
32 |
33 | Antes de começarmos, quero que saiba que criar um lexer é tedioso e
34 | propenso a erros. É por isso que gênios já criaram ferramentas automáticas
35 | para fazer o trabalho. `lex/flex` são exemplos que nos permitem descrever as
36 | regras léxicas usando expressões regulares e gerar um lexer para nós.
37 |
38 | Também note que não seguiremos o gráfico na seção acima, ou seja, não
39 | converteremos todo o código fonte em fluxo de tokens de uma vez. As razões são:
40 |
41 | 1. Converter código fonte em fluxo de tokens é um processo com estado. Como uma string é
42 | interpretada está relacionado com o lugar onde ela aparece.
43 | 2. É um desperdício armazenar todos os tokens porque apenas alguns deles serão
44 | acessados ao mesmo tempo.
45 |
46 | Assim, implementaremos uma função: `next()` que retorna um token em uma chamada.
47 |
48 | ## Tokens Suportados
49 |
50 | Adicione a definição na área global:
51 |
52 | ```c
53 | // tokens e classes (operadores por último e em ordem de precedência)
54 | enum {
55 | Num = 128, Fun, Sys, Glo, Loc, Id,
56 | Char, Else, Enum, If, Int, Return, Sizeof, While,
57 | Assign, Cond, Lor, Lan, Or, Xor, And, Eq, Ne, Lt, Gt, Le, Ge, Shl, Shr, Add, Sub, Mul, Div, Mod, Inc, Dec, Brak
58 | };
59 | ```
60 |
61 | Estes são todos os tokens que nosso lexer pode entender. Nosso lexer interpretará
62 | a string = como token `Assign`; `==` como token `Eq`; != como `Ne`; etc.
63 |
64 | Então temos a impressão de que um token conterá um ou mais caracteres.
65 | Essa é a razão pela qual o lexer pode reduzir a complexidade, agora o parser não
66 | precisa olhar vários caracteres para identificar o significado de uma substring.
67 | O trabalho já foi feito.
68 |
69 | Claro, os tokens acima estão devidamente ordenados refletindo sua prioridade em
70 | a linguagem de programação C. O operador `*(Mul)`, por exemplo, tem prioridade mais alta
71 | que o operador `+(Add)`. Falaremos sobre isso mais tarde.
72 |
73 | Por fim, existem alguns caracteres que não incluímos aqui que são eles mesmos um
74 | token como `]` ou `~`. A razão de não codificá-los como outros são:
75 |
76 | 1. Esses tokens contêm apenas um único caractere, portanto, são mais fáceis de identificar.
77 | 2. Eles não estão envolvidos na batalha de prioridade.
78 |
79 | ## Esqueleto do Lexer
80 |
81 | ```c
82 | void next() {
83 | char *last_pos;
84 | int hash;
85 | while (token = *src) {
86 | ++src;
87 | // parse token here
88 | }
89 | return;
90 | }
91 | ```
92 |
93 | Por que precisamos de `while` aqui sabendo que `next` só analisará um token?
94 | Isso levanta uma questão na construção do compilador (lembre-se de que o lexer é um tipo
95 | de compilador?): Como lidar com erros?
96 |
97 | Normalmente temos duas soluções:
98 |
99 | 1. aponta onde o erro ocorre e sai.
100 | 2. aponta onde o erro ocorre, ignora e continua.
101 |
102 | Isso explicará a existência de `while`: para pular caracteres desconhecidos no
103 | código fonte. Enquanto isso, é usado para pular espaços em branco que não são a parte real
104 | de um programa. Eles são tratados apenas como separadores.
105 |
106 | ## Nova Linha
107 |
108 | É bastante semelhante ao espaço, pois estamos pulando. A única diferença é que
109 | precisamos aumentar o número da linha quando um caractere de nova linha é encontrado:
110 |
111 | ```c
112 | // analisa token aqui
113 |
114 | ...
115 | if (token == '\n') {
116 | ++line;
117 | }
118 | ```
119 |
120 | ## Macros
121 |
122 | Macros em C começam com o caractere `#` como `#include `. Nosso
123 | compilador não suporta nenhum macro, então vamos pular todos eles:
124 |
125 | ```c
126 | else if (token == '#') {
127 | // pula macro, porque não vamos suportá-lo
128 | while (*src != 0 && *src != '\n') {
129 | src++;
130 | }
131 | }
132 | ```
133 |
134 | ## Identificadores e Tabela de Símbolos
135 |
136 | Identificador é o nome de uma variável. Mas não nos importamos realmente com os
137 | nomes no lexer, nos importamos com a identidade. Por exemplo: `int a;`
138 | declara uma variável, temos que saber que a declaração `a = 10` que vem
139 | depois refere-se à mesma variável que declaramos anteriormente.
140 |
141 | Com base nisso, faremos uma tabela para armazenar todos os nomes que já
142 | encontramos e chamaremos de Tabela de Símbolos. Vamos consultar a tabela quando um novo
143 | nome/identificador é encontrado. Se o nome existir na tabela de símbolos, a
144 | identidade é retornada.
145 |
146 | Então, como representar uma identidade?
147 |
148 | ```c
149 | struct identifier {
150 | int token;
151 | int hash;
152 | char * name;
153 | int class;
154 | int type;
155 | int value;
156 | int Bclass;
157 | int Btype;
158 | int Bvalue;
159 | }
160 | ```
161 |
162 | Vamos precisar de uma pequena explicação aqui:
163 |
164 | 1. `token`: é o tipo de token de um identificador. Teoricamente, deveria ser
165 | fixado para o tipo `Id`. Mas isso não é verdade porque adicionaremos palavras-chave (por exemplo,
166 | `if`, `while`) como tipos especiais de identificador.
167 | 2. `hash`: o valor hash do nome do identificador, para acelerar a
168 | comparação na busca na tabela.
169 | 3. `name`: bem, o nome do identificador.
170 | 4. `class`: Se o identificador é global, local ou constantes.
171 | 5. `type`: tipo do identificador, `int`, `char` ou ponteiro.
172 | 6. `value`: o valor da variável à qual o identificador aponta.
173 | 7. `BXXX`: uma variável local pode ocultar uma variável global. É usado para armazenar
174 | as globais se isso acontecer.
175 |
176 | Uma tabela de símbolos tradicional conterá apenas o identificador único, enquanto nossa
177 | tabela de símbolos armazena outras informações que só serão acessadas pelo parser,
178 | como `type`.
179 |
180 | Infelizmente, nosso compilador não suporta `struct` enquanto estamos tentando ser
181 | auto-suficientes. Portanto, temos que fazer um compromisso na estrutura real de um
182 | identificador:
183 |
184 | ```
185 | Tabela de símbolos:
186 | ----+-----+----+----+----+-----+-----+-----+------+------+----
187 | .. |token|hash|name|type|class|value|btype|bclass|bvalue| ..
188 | ----+-----+----+----+----+-----+-----+-----+------+------+----
189 | |<--- um único identificador --->|
190 | ```
191 |
192 |
193 | Isso significa que usamos um único array `int` para armazenar todas as informações do identificador.
194 | Cada ID usará 9 células. O código é o seguinte:
195 |
196 | ```c
197 | int token_val; // valor do token atual (principalmente para números)
198 | int *current_id, // ID analisado atualmente
199 | *symbols; // tabela de símbolos
200 | // campos do identificador
201 | enum {Token, Hash, Name, Type, Class, Value, BType, BClass, BValue, IdSize};
202 | void next() {
203 | ...
204 | else if ((token >= 'a' && token <= 'z') || (token >= 'A' && token <= 'Z') || (token == '_')) {
205 | // analisa identificador
206 | last_pos = src - 1;
207 | hash = token;
208 | while ((*src >= 'a' && *src <= 'z') || (*src >= 'A' && *src <= 'Z') || (*src >= '0' && *src <= '9') || (*src == '_')) {
209 | hash = hash * 147 + *src;
210 | src++;
211 | }
212 | // procura identificador existente, busca linear
213 | current_id = symbols;
214 | while (current_id[Token]) {
215 | if (current_id[Hash] == hash && !memcmp((char *)current_id[Name], last_pos, src - last_pos)) {
216 | // encontrou um, retorna
217 | token = current_id[Token];
218 | return;
219 | }
220 | current_id = current_id + IdSize;
221 | }
222 | // armazena novo ID
223 | current_id[Name] = (int)last_pos;
224 | current_id[Hash] = hash;
225 | token = current_id[Token] = Id;
226 | return;
227 | }
228 | ...
229 | }
230 |
231 | ```
232 |
233 | Note que a busca na tabela de símbolos é uma busca linear.
234 |
235 | ## Número
236 |
237 | Precisamos suportar decimal, hexadecimal e octal. A lógica é bastante
238 | direta, exceto como obter o valor hexadecimal. Talvez..
239 |
240 | ```c
241 | token_val = token_val * 16 + (token & 0x0F) + (token >= 'A' ? 9 : 0);
242 | ```
243 |
244 | Caso você não esteja familiarizado com esse "pequeno truque", o valor hexadecimal de`a`
245 | é `61` em ASCII, enquanto `A` é `41`. Assim, `token & 0x0F` pode obter o dígito mais pequeno
246 | do caractere.
247 |
248 | ```c
249 | void next() {
250 | ...
251 |
252 |
253 |
254 | else if (token >= '0' && token <= '9') {
255 | // analisa número, três tipos: dec(123) hex(0x123) oct(017)
256 | token_val = token - '0';
257 | if (token_val > 0) {
258 | // dec, começa com [1-9]
259 | while (*src >= '0' && *src <= '9') {
260 | token_val = token_val*10 + *src++ - '0';
261 | }
262 | } else {
263 | // começa com o número 0
264 | if (*src == 'x' || *src == 'X') {
265 | // hex
266 | token = *++src;
267 | while ((token >= '0' && token <= '9') || (token >= 'a' && token <= 'f') || (token >= 'A' && token <= 'F')) {
268 | token_val = token_val * 16 + (token & 15) + (token >= 'A' ? 9 : 0);
269 | token = *++src;
270 | }
271 | } else {
272 | // oct
273 | while (*src >= '0' && *src <= '7') {
274 | token_val = token_val*8 + *src++ - '0';
275 | }
276 | }
277 | }
278 | token = Num;
279 | return;
280 | }
281 |
282 |
283 | ...
284 | }
285 | ```
286 | ## Literais de String
287 |
288 | Se encontrarmos qualquer literal de string, precisamos armazená-lo no `segmento de dados`
289 | que introduzimos em um capítulo anterior e retornar o endereço. Outra questão
290 | é que precisamos nos preocupar com caracteres de escape, como `\n` para representar o caractere de nova linha. Mas não suportamos caracteres de escape além de `\n`, como `\t`
291 | ou `\r` porque nosso objetivo é apenas a inicialização. Note que ainda suportamos
292 | a sintaxe que `\x` seja o caractere `x` em si.
293 |
294 | Nosso analisador léxico analisará um único caractere (por exemplo, `'a'`) ao mesmo tempo. Uma vez
295 | que o caractere é encontrado, o retornamos como um `Num`.
296 |
297 | ```c
298 | void next() {
299 | ...
300 |
301 | else if (token == '"' || token == '\'') {
302 | // analisa literal de string, atualmente, o único caractere de escape suportado
303 | // é '\n', armazene o literal de string nos dados.
304 | last_pos = data;
305 | while (*src != 0 && *src != token) {
306 | token_val = *src++;
307 | if (token_val == '\\') {
308 | // caractere de escape
309 | token_val = *src++;
310 | if (token_val == 'n') {
311 | token_val = '\n';
312 | }
313 | }
314 | if (token == '"') {
315 | *data++ = token_val;
316 | }
317 | }
318 |
319 | src++;
320 | // se for um único caractere, retorne o token Num
321 | if (token == '"') {
322 | token_val = (int)last_pos;
323 | } else {
324 | token = Num;
325 | }
326 |
327 | return;
328 | }
329 | }
330 | ```
331 |
332 |
333 | ## Comentários
334 |
335 | Apenas comentários no estilo C++ (por exemplo, `// comentário`) são suportados. O estilo C (`/* ... */`)
336 | não é suportado.
337 |
338 | ```c
339 |
340 | void next() {
341 | ...
342 |
343 | else if (token == '/') {
344 | if (*src == '/') {
345 | // pula comentários
346 | while (*src != 0 && *src != '\n') {
347 | ++src;
348 | }
349 | } else {
350 | // operador de divisão
351 | token = Div;
352 | return;
353 | }
354 | }
355 |
356 | ...
357 | }
358 |
359 | ```
360 | Agora, introduziremos o conceito: `lookahead` (antecipação). No código acima, vemos que
361 | para o código-fonte começando com o caractere `/`, pode-se encontrar 'comentário' ou `/(Div)`.
362 |
363 | Às vezes, não podemos decidir qual token gerar apenas olhando para o caractere atual
364 | (como o exemplo acima sobre divisão e comentário), assim precisamos
365 | verificar o próximo caractere (chamado `lookahead`) para determinar. Em nosso
366 | exemplo, se for outra barra `/`, então encontramos uma linha de comentário,
367 | caso contrário, é um operador de divisão.
368 |
369 | Como dissemos que um analisador léxico e um analisador sintático são inerentemente um tipo de compilador,
370 | `lookahead` também existe em analisadores sintáticos. No entanto, os analisadores vão olhar para "token"
371 | em vez de "caractere". O `k` em `LL(k)` da teoria do compilador é a quantidade de
372 | tokens que um analisador precisa antecipar.
373 |
374 | Além disso, se não dividirmos o compilador em um analisador léxico e um analisador sintático, o compilador terá
375 | que antecipar muitos caracteres para decidir o que fazer a seguir. Portanto, podemos dizer que
376 | um analisador léxico reduz a quantidade de antecipação que um compilador precisa verificar.
377 |
378 | ## Outros
379 |
380 | Outros são mais simples e diretos, verifique o código:
381 |
382 |
383 | ```c
384 | void next() {
385 | ...
386 | else if (token == '=') {
387 | // analisa '==' e '='
388 | if (*src == '=') {
389 | src ++;
390 | token = Eq;
391 | } else {
392 | token = Assign;
393 | }
394 | return;
395 | }
396 | else if (token == '+') {
397 | // analisa '+' e '++'
398 | if (*src == '+') {
399 | src ++;
400 | token = Inc;
401 | } else {
402 | token = Add;
403 | }
404 | return;
405 | }
406 | else if (token == '-') {
407 | // analisa '-' e '--'
408 | if (*src == '-') {
409 | src ++;
410 | token = Dec;
411 | } else {
412 | token = Sub;
413 | }
414 | return;
415 | }
416 | else if (token == '!') {
417 | // analisa '!='
418 | if (*src == '=') {
419 | src++;
420 | token = Ne;
421 | }
422 | return;
423 | }
424 | else if (token == '<') {
425 | // analisa '<=', '<<' ou '<'
426 | if (*src == '=') {
427 | src ++;
428 | token = Le;
429 | } else if (*src == '<') {
430 | src ++;
431 | token = Shl;
432 | } else {
433 | token = Lt;
434 | }
435 | return;
436 | }
437 | else if (token == '>') {
438 | // analisa '>=', '>>' ou '>'
439 | if (*src == '=') {
440 | src ++;
441 | token = Ge;
442 | } else if (*src == '>') {
443 | src ++;
444 | token = Shr;
445 | } else {
446 | token = Gt;
447 | }
448 | return;
449 | }
450 | else if (token == '|') {
451 | // analisa '|' ou '||'
452 | if (*src == '|') {
453 | src ++;
454 | token = Lor;
455 | } else {
456 | token = Or;
457 | }
458 | return;
459 | }
460 | else if (token == '&') {
461 | // analisa '&' e '&&'
462 | if (*src == '&') {
463 | src ++;
464 | token = Lan;
465 | } else {
466 | token = And;
467 | }
468 | return;
469 | }
470 | else if (token == '^') {
471 | token = Xor;
472 | return;
473 | }
474 | else if (token == '%') {
475 | token = Mod;
476 | return;
477 | }
478 | else if (token == '*') {
479 | token = Mul;
480 | return;
481 | }
482 | else if (token == '[') {
483 | token = Brak;
484 | return;
485 | }
486 | else if (token == '?') {
487 | token = Cond;
488 | return;
489 | }
490 | else if (token == '~' || token == ';' || token == '{' || token == '}' || token == '(' || token == ')' || token == ']' || token == ',' || token == ':') {
491 | // retorna diretamente o caractere como token;
492 | return;
493 | }
494 |
495 | ...
496 | }
497 |
498 | ```
499 |
500 | ## Palavras-chave e Funções Embutidas
501 |
502 | Palavras-chave como `if`, `while` ou `return` são especiais porque são conhecidas
503 | pelo compilador antecipadamente. Não podemos tratá-las como identificadores normais
504 | por causa dos significados especiais nelas. Existem duas maneiras de lidar com isso:
505 |
506 | 1. Permitir que o analisador léxico as interprete e retorne um token para identificá-las.
507 | 2. Tratá-las como identificador normal, mas armazená-las na tabela de símbolos
508 | antecipadamente.
509 |
510 | Escolhemos a segunda opção: adicionar identificadores correspondentes à tabela de símbolos
511 | antecipadamente e definir as propriedades necessárias (por exemplo, o tipo `Token` que mencionamos). Assim,
512 | quando as palavras-chave são encontradas no código-fonte, elas serão interpretadas
513 | como identificadores, mas como já existem na tabela de símbolos, podemos saber
514 | que são diferentes dos identificadores normais.
515 |
516 | Funções embutidas são semelhantes. Elas são apenas diferentes nas informações internas.
517 | Na função principal, adicione o seguinte:
518 |
519 | ```c
520 | // tipos de variável/função
521 | enum { CHAR, INT, PTR };
522 | int *idmain; // a função `main`
523 | int main(int argc, char **argv) {
524 | ...
525 |
526 | src = "char else enum if int return sizeof while "
527 | "open read close printf malloc memset memcmp exit void main";
528 |
529 | // adiciona palavras-chave à tabela de símbolos
530 | i = Char;
531 | while (i <= While) {
532 | next();
533 | current_id[Token] = i++;
534 | }
535 |
536 | // adiciona biblioteca à tabela de símbolos
537 | i = OPEN;
538 | while (i <= EXIT) {
539 | next();
540 | current_id[Class] = Sys;
541 | current_id[Type] = INT;
542 | current_id[Value] = i++;
543 | }
544 |
545 | next(); current_id[Token] = Char; // lida com o tipo void
546 | next(); idmain = current_id; // mantém o rastreamento de main
547 |
548 | ...
549 | program();
550 | return eval();
551 | }
552 | ```
553 | ## Código
554 |
555 | Você pode conferir o código no
556 |
557 | [Github](https://github.com/lotabout/write-a-C-interpreter/tree/step-2), ou
558 | clone com:
559 |
560 | ```
561 | git clone -b step-2 https://github.com/lotabout/write-a-C-interpreter
562 | ```
563 |
564 | Executar o código resultará em 'Segmentation Fault' porque tentará
565 | executar a máquina virtual que construímos no capítulo anterior, o que não
566 | funcionará porque não contém nenhum código executável.
567 |
568 | ## Resumo
569 |
570 | 1. O analisador léxico é usado para pré-processar o código-fonte, de modo a reduzir a
571 | complexidade do analisador sintático.
572 |
573 | 2. O analisador léxico também é um tipo de compilador que consome código-fonte e produz
574 | fluxo de tokens.
575 |
576 | 3. `lookahead(k)` é usado para determinar completamente o significado do atual
577 | caractere/token.
578 |
579 | 4. Como representar identificador e tabela de símbolos.
580 |
581 | Discutiremos sobre o analisador sintático recursivo de cima para baixo. Até mais :)
582 |
--------------------------------------------------------------------------------
/tutorial/pt-br/4-Top-down-Parsing.md:
--------------------------------------------------------------------------------
1 | Neste capítulo, construiremos uma calculadora simples usando a técnica de análise top-down (análise descendente). Esta é a preparação antes de começarmos a implementar o analisador.
2 |
3 | Vou introduzir um pequeno conjunto de teorias, mas não garanto que estejam absolutamente corretas. Por favor, consulte seu livro didático se tiver alguma dúvida.
4 |
5 | ## Análise top-down
6 |
7 | Tradicionalmente, temos análise top-down e análise bottom-up. O método top-down começa com um não-terminal e verifica recursivamente o código-fonte para substituir os não-terminais por suas alternativas até que nenhum não-terminal reste.
8 |
9 | Você viu que usei o método top-down para explicar "top-down" porque você precisa saber o que é um "não-terminal" para entender o parágrafo acima. Mas ainda não te disse o que é isso. Explicaremos na próxima seção. Por agora, considere que "top-down" está tentando decompor um grande objeto em pequenas partes.
10 |
11 | Por outro lado, a análise "bottom-up" tenta combinar pequenos objetos em um grande. É frequentemente usado em ferramentas de automação que geram analisadores.
12 |
13 | ## Terminal e Não-terminal
14 |
15 | São termos usados em [BNF](https://pt.wikipedia.org/wiki/Forma_de_Backus-Naur) (Forma de Backus–Naur) que é uma linguagem usada para descrever gramáticas. Uma simples calculadora aritmética elementar em BNF seria:
16 |
17 | ```
18 | ::= +
19 | | -
20 | |
21 |
22 | ::= *
23 | | /
24 | |
25 |
26 | ::= ( )
27 | | Num
28 | ```
29 |
30 | O item delimitado por `<>` é chamado de `Não-terminal`. Eles receberam este nome porque podemos substituí-los pelos itens à direita de `::=`. `|` significa alternativa, isso significa que você pode substituir `` por qualquer um de ` * `, ` / ` ou ``. Aqueles que não aparecem à esquerda de `::=` são chamados de `Terminal`, como `+`, `(`, `Num`, etc. Eles geralmente correspondem aos tokens que obtemos do analisador léxico.
31 |
32 | ## Exemplo de análise top-down para uma calculadora simples
33 |
34 | A árvore de análise é a estrutura interna que obtemos após o analisador consumir todos os tokens e concluir toda a análise. Vamos pegar `3 * (4 + 2)` como exemplo para mostrar as conexões entre gramática BNF, árvore de análise e análise top-down.
35 |
36 | A análise top-down começa a partir de um não-terminal inicial, que é `` em nosso exemplo. Você pode especificá-lo na prática, mas por padrão é o primeiro não-terminal que encontramos.
37 |
38 |
39 | ```
40 | 1. =>
41 | 2. => *
42 | 3. => |
43 | 4. => Num (3) |
44 | 5. => ( )
45 | 6. => +
46 | 7. => |
47 | 8. => |
48 | 9. => Num (4) |
49 | 10. =>
50 | 11. => Num (2)
51 | ```
52 |
53 |
54 | Você pode ver que, a cada etapa, substituímos um não-terminal por uma de suas alternativas (top-down) até que todos os subitens sejam substituídos por terminais (bottom). Alguns não-terminais são usados recursivamente, como ``.
55 |
56 | ## Vantagens da análise top-down
57 |
58 | Como você pode ver no exemplo acima, a etapa de análise é semelhante à gramática BNF. O que significa que é fácil converter a gramática em código real, convertendo uma regra de produção (`<...> ::= ...`) em uma função com o mesmo nome.
59 |
60 | Uma questão surge aqui: como você sabe qual alternativa aplicar? Por que escolher ` ::= * ` em vez de ` ::= / `? Isso mesmo, nós usamos `lookahead`! Nós espiamos o próximo token e é `*`, então é o primeiro a ser aplicado.
61 |
62 | No entanto, a análise top-down requer que a gramática não tenha recursão à esquerda.
63 |
64 | ## Recursão à esquerda
65 |
66 | Suponha que tenhamos uma gramática assim:
67 |
68 |
69 | ```
70 | ::= + Num
71 | ```
72 | Podemos traduzi-la para a função:
73 |
74 |
75 | ```
76 | int expr() {
77 | expr();
78 | num();
79 | }
80 | ```
81 |
82 |
83 | Como você pode ver, a função `expr` nunca sairá! Na gramática, o não-terminal `` é usado recursivamente e aparece imediatamente após `::=`, o que causa recursão à esquerda.
84 |
85 | Felizmente, a maioria das gramáticas com recursão à esquerda (talvez todas? Não me lembro) pode ser devidamente transformada em equivalentes não-recursivas à esquerda. Nossa gramática para a calculadora pode ser convertida para:
86 |
87 | ```
88 | ::=
89 |
90 | ::= +
91 | | -
92 | |
93 |
94 | ::=
95 |
96 | ::= *
97 | | /
98 | |
99 |
100 | ::= ( )
101 | | Num
102 | ```
103 |
104 |
105 | Você deve consultar seu livro didático para obter mais informações.
106 |
107 | ## Implementação
108 |
109 | O código a seguir é diretamente convertido da gramática. Observe como é direto:
110 |
111 |
112 | ```c
113 | int expr();
114 |
115 | int factor() {
116 | int value = 0;
117 | if (token == '(') {
118 | match('(');
119 | value = expr();
120 | match(')');
121 | } else {
122 | value = token_val;
123 | match(Num);
124 | }
125 | return value;
126 | }
127 |
128 | int term_tail(int lvalue) {
129 | if (token == '*') {
130 | match('*');
131 | int value = lvalue * factor();
132 | return term_tail(value);
133 | } else if (token == '/') {
134 | match('/');
135 | int value = lvalue / factor();
136 | return term_tail(value);
137 | } else {
138 | return lvalue;
139 | }
140 | }
141 |
142 | int term() {
143 | int lvalue = factor();
144 | return term_tail(lvalue);
145 | }
146 |
147 | int expr_tail(int lvalue) {
148 | if (token == '+') {
149 | match('+');
150 | int value = lvalue + term();
151 | return expr_tail(value);
152 | } else if (token == '-') {
153 | match('-');
154 | int value = lvalue - term();
155 | return expr_tail(value);
156 | } else {
157 | return lvalue;
158 | }
159 | }
160 |
161 | int expr() {
162 | int lvalue = term();
163 | return expr_tail(lvalue);
164 | }
165 | ```
166 |
167 | Implementar um analisador sintático "top-down" é simples com a ajuda da gramática BNF.
168 | Agora, adicionamos o código para o lexer:
169 |
170 |
171 | ```c
172 | #include
173 | #include
174 |
175 | enum {Num};
176 | int token;
177 | int token_val;
178 | char *line = NULL;
179 | char *src = NULL;
180 |
181 | void next() {
182 | // pula espaços em branco
183 | while (*src == ' ' || *src == '\t') {
184 | src ++;
185 | }
186 |
187 | token = *src++;
188 |
189 | if (token >= '0' && token <= '9' ) {
190 | token_val = token - '0';
191 | token = Num;
192 |
193 | while (*src >= '0' && *src <= '9') {
194 | token_val = token_val*10 + *src - '0';
195 | src ++;
196 | }
197 | return;
198 | }
199 | }
200 |
201 | void match(int tk) {
202 | if (token != tk) {
203 | printf("token esperado: %d(%c), recebido: %d(%c)\n", tk, tk, token, token);
204 | exit(-1);
205 | }
206 |
207 | next();
208 | }
209 | ```
210 | Finalmente, o método main para inicialização:
211 |
212 | ```c
213 | int main(int argc, char *argv[])
214 | {
215 | size_t linecap = 0;
216 | ssize_t linelen;
217 | while ((linelen = getline(&line, &linecap, stdin)) > 0) {
218 | src = line;
219 | next();
220 | printf("%d\n", expr());
221 | }
222 | return 0;
223 | }
224 | ```
225 |
226 | Você pode brincar com sua própria calculadora agora. Ou tente adicionar mais funções baseado no que aprendemos no capítulo anterior. Como suporte a variáveis, para que um usuário possa definir variáveis para armazenar valores.
227 |
228 | ## Resumo
229 |
230 | Não gostamos de teoria, mas ela existe por um bom motivo, como você pode ver que a BNF pode nos ajudar a construir o analisador sintático. Então, quero convencê-lo a aprender algumas teorias, isso ajudará você a se tornar um programador melhor.
231 |
232 | A técnica de análise sintática "top-down" é frequentemente usada na criação manual de analisadores, então você é capaz de lidar com a maioria dos trabalhos se dominá-la! Como você verá nos próximos capítulos.
--------------------------------------------------------------------------------
/tutorial/pt-br/5-variaveis.md:
--------------------------------------------------------------------------------
1 | Neste capítulo vamos usar EBNF para descrever a gramática do nosso C
2 | intérprete e adicione o suporte de variáveis.
3 |
4 | O parser é mais complicado que o lexer, por isso vamos dividi-lo em 3 partes:
5 | variáveis, funções e expressões.
6 |
7 | ## Gramática EBNF do Interpretador C
8 |
9 | Neste capítulo, discutimos a Gramática EBNF (Extended Backus–Naur Form) do nosso interpretador de C. Se você está familiarizado com expressões regulares, deverá se sentir à vontade. Pessoalmente, acho que é mais poderosa e direta do que a BNF. Aqui está a gramática EBNF do nosso interpretador C, sinta-se à vontade para pular se achar que é muito difícil de entender.
10 |
11 | ```
12 | programa ::= {declaracao_global}+
13 | declaracao_global ::= decl_enum | decl_variavel | decl_funcao
14 |
15 | decl_enum ::= 'enum' [id] '{' id ['=' 'num'] {',' id ['=' 'num'] '}'
16 |
17 | decl_variavel ::= tipo {'*'} id { ',' {'*'} id } ';'
18 |
19 | decl_funcao ::= tipo {'*'} id '(' decl_parametro ')' '{' decl_corpo '}'
20 |
21 | decl_parametro ::= tipo {'*'} id {',' tipo {'*'} id}
22 |
23 | decl_corpo ::= {decl_variavel}, {instrucao}
24 |
25 | instrucao ::= instrucao_nao_vazia | instrucao_vazia
26 |
27 | instrucao_nao_vazia ::= instrucao_se | instrucao_enquanto | '{' instrucao '}'
28 | | 'return' expressao | expressao ';'
29 |
30 | instrucao_se ::= 'if' '(' expressao ')' instrucao ['else' instrucao_nao_vazia]
31 |
32 | instrucao_enquanto ::= 'while' '(' expressao ')' instrucao_nao_vazia
33 | ```
34 |
35 | Deixaremos `expressao` para capítulos posteriores. Nossa gramática não suportará a declaração de funções, o que significa que chamadas recursivas entre funções não são suportadas. E como estamos começando, isso significa que nosso código de implementação não pode usar recursões cruzadas entre funções. (Desculpe pelo capítulo inteiro sobre parsers recursivos top-down.)
36 |
37 | Neste capítulo, implementaremos as `decl_enum` e `decl_variavel`.
38 |
39 | ## programa()
40 |
41 | Já definimos a função `programa`, vamos transformá-la em:
42 |
43 | ```c
44 |
45 | void programa() {
46 | // obter próximo token
47 | proximo();
48 | while (token > 0) {
49 | declaracao_global();
50 | }
51 | }
52 | ```
53 |
54 | Eu sei que ainda não definimos `declaracao_global`, às vezes precisamos de um pouco de pensamento otimista de que talvez alguém (digamos, o Bob) a implementará para você. Assim, você pode se concentrar na visão geral a princípio, em vez de mergulhar em todos os detalhes. Essa é a essência do pensamento top-down.
55 |
56 | ## função declaracao_global()
57 |
58 | Agora é nosso dever (não do Bob) implementar a `declaracao_global``. Ela tentará analisar definições de variáveis, definições de tipos (apenas enum é suportado) e definições de funções:
59 |
60 | ```c
61 | int basetype; // the type of a declaration, make it global for convenience
62 | int expr_type; // the type of an expression
63 |
64 | void global_declaration() {
65 | // global_declaration ::= enum_decl | variable_decl | function_decl
66 | //
67 | // enum_decl ::= 'enum' [id] '{' id ['=' 'num'] {',' id ['=' 'num'} '}'
68 | //
69 | // variable_decl ::= type {'*'} id { ',' {'*'} id } ';'
70 | //
71 | // function_decl ::= type {'*'} id '(' parameter_decl ')' '{' body_decl '}'
72 |
73 |
74 | int type; // tmp, actual type for variable
75 | int i; // tmp
76 |
77 | basetype = INT;
78 |
79 | // parse enum, this should be treated alone.
80 | if (token == Enum) {
81 | // enum [id] { a = 10, b = 20, ... }
82 | match(Enum);
83 | if (token != '{') {
84 | match(Id); // skip the [id] part
85 | }
86 | if (token == '{') {
87 | // parse the assign part
88 | match('{');
89 | enum_declaration();
90 | match('}');
91 | }
92 |
93 | match(';');
94 | return;
95 | }
96 |
97 | // parse type information
98 | if (token == Int) {
99 | match(Int);
100 | }
101 | else if (token == Char) {
102 | match(Char);
103 | basetype = CHAR;
104 | }
105 |
106 | // parse the comma seperated variable declaration.
107 | while (token != ';' && token != '}') {
108 | type = basetype;
109 | // parse pointer type, note that there may exist `int ****x;`
110 | while (token == Mul) {
111 | match(Mul);
112 | type = type + PTR;
113 | }
114 |
115 | if (token != Id) {
116 | // invalid declaration
117 | printf("%d: bad global declaration\n", line);
118 | exit(-1);
119 | }
120 | if (current_id[Class]) {
121 | // identifier exists
122 | printf("%d: duplicate global declaration\n", line);
123 | exit(-1);
124 | }
125 | match(Id);
126 | current_id[Type] = type;
127 |
128 | if (token == '(') {
129 | current_id[Class] = Fun;
130 | current_id[Value] = (int)(text + 1); // the memory address of function
131 | function_declaration();
132 | } else {
133 | // variable declaration
134 | current_id[Class] = Glo; // global variable
135 | current_id[Value] = (int)data; // assign memory address
136 | data = data + sizeof(int);
137 | }
138 |
139 | if (token == ',') {
140 | match(',');
141 | }
142 | }
143 | next();
144 | }
145 | ```
146 |
147 | Bem, isso é mais do que duas telas de código! Acredito que isso seja uma tradução direta da gramática. No entanto, para ajudar na compreensão, vou explicar alguns pontos:
148 |
149 | `Token Antecipado (Lookahead Token)`: A instrução `if (token == xxx)` é usada para observar o próximo token e decidir qual regra de produção usar. Por exemplo, se encontrarmos o token `enum`, sabemos que estamos tentando analisar uma enumeração. Mas se um tipo for analisado, como `int identificador`, ainda não podemos determinar se `identificador` é uma variável ou uma função. Portanto, o analisador deve continuar a olhar adiante para o próximo token. Se encontrarmos (, agora temos certeza de que identificador é uma função; caso contrário, é uma variável.
150 |
151 | `Tipo de Variável`: Nosso interpretador de C suporta ponteiros, o que significa que também suporta ponteiros que apontam para outros ponteiros, como `int **data;`. Como representá-los no código? Já definimos os tipos que oferecemos suporte:
152 |
153 | ```c
154 | // tipos de variável/função
155 | enum { CHAR, INT, PTR };
156 | ```
157 |
158 | Então usaremos um `int` para armazenar o tipo. Ele começa com um tipo base: `CHAR` ou `INT`. Quando o tipo é um ponteiro que aponta para um tipo base como `int *data;`, adicionamos `PTR` a ele: `tipo = tipo + PTR;`. O mesmo se aplica ao ponteiro de ponteiro; adicionamos outro `PTR` ao tipo, etc.
159 |
160 | ## enum_declaration
161 |
162 | A lógica principal tenta analisar as variáveis separadas por ','. Você precisa prestar atenção à representação das enumerações.
163 |
164 | Armazenaremos uma enumeração como uma variável global. No entanto, seu tipo é definido como `Num` em vez de `Glo` para torná-la uma constante em vez de uma variável global normal. As informações de tipo serão usadas posteriormente na análise de `expressions`.
165 |
166 | ```c
167 | void enum_declaration() {
168 | // analisar enum [id] { a = 1, b = 3, ...}
169 | int i;
170 | i = 0;
171 | while (token != '}') {
172 | if (token != Id) {
173 | printf("%d: identificador de enumeração inválido %d\n", line, token);
174 | exit(-1);
175 | }
176 | next();
177 | if (token == Assign) {
178 | // como {a=10}
179 | next();
180 | if (token != Num) {
181 | printf("%d: inicializador de enumeração inválido\n", line);
182 | exit(-1);
183 | }
184 | i = token_val;
185 | next();
186 | }
187 |
188 | current_id[Class] = Num;
189 | current_id[Type] = INT;
190 | current_id[Value] = i++;
191 |
192 | if (token == ',') {
193 | next();
194 | }
195 | }
196 | }
197 | ```
198 |
199 | ## Miscelânea
200 |
201 | Claro, `function_declaration` será introduzido no próximo capítulo. `match` aparece com frequência. É uma função auxiliar que consome o token atual e busca o próximo:
202 |
203 | ```c
204 | void match(int tk) {
205 | if (token == tk) {
206 | next();
207 | } else {
208 | printf("%d: token esperado: %d\n", line, tk);
209 | exit(-1);
210 | }
211 | }
212 | ```
213 |
214 | ## Código
215 |
216 | Você pode baixar o código deste capítulo no [Github](https://github.com/lotabout/write-a-C-interpreter/tree/step-3), ou clonar com:
217 |
218 | ```c
219 | git clone -b step-3 https://github.com/lotabout/write-a-C-interpreter
220 | ```
221 |
222 | O código não vai rodar porque ainda existem algumas funções não implementadas. Você pode se desafiar a completá-las primeiro.
223 |
224 | ## Resumo
225 |
226 | EBNF pode ser difícil de entender devido à sua sintaxe (talvez). Mas deve ser fácil seguir este capítulo uma vez que você consiga ler a sintaxe. O que fazemos é traduzir EBNF diretamente para o código C. Portanto, a análise não é, de forma alguma, emocionante, mas você deve prestar atenção à representação de cada conceito.
227 |
228 | Falaremos sobre a definição de função no próximo capítulo. Até lá!
229 |
230 |
--------------------------------------------------------------------------------
/tutorial/pt-br/6-Funcoes.md:
--------------------------------------------------------------------------------
1 | Já vimos como as definições de variáveis são analisadas em nosso interpretador. Agora é a vez das definições de funções (note que é definição, não declaração, portanto nosso interpretador não suporta recursão entre funções).
2 |
3 | ## Gramática EBNF
4 |
5 | Vamos começar relembrando a gramática EBNF apresentada no último capítulo. Já implementamos `program`, `global_declaration` e `enum_decl`. Lidaremos com parte de variable_decl, function_decl, `parameter_decl` e `body_decl`. O restante será abordado no próximo capítulo.
6 |
7 |
8 | ```
9 | variable_decl ::= type {'*'} id { ',' {'*'} id } ';'
10 |
11 | function_decl ::= type {'*'} id '(' parameter_decl ')' '{' body_decl '}'
12 |
13 | parameter_decl ::= type {'*'} id {',' type {'*'} id}
14 |
15 | body_decl ::= {variable_decl}, {statement}
16 |
17 | statement ::= non_empty_statement | empty_statement
18 |
19 | non_empty_statement ::= if_statement | while_statement | '{' statement '}'
20 | | 'return' expression | expression ';'
21 |
22 | if_statement ::= 'if' '(' expression ')' statement ['else' non_empty_statement]
23 |
24 | while_statement ::= 'while' '(' expression ')' non_empty_statement
25 | ```
26 |
27 |
28 | ## Definição de Função
29 |
30 | Lembre-se de que já encontramos funções ao lidar com `global_declaration`:
31 |
32 | ```c
33 | ...
34 | if (token == '(') {
35 | current_id[Class] = Fun;
36 | current_id[Value] = (int)(text + 1); // o endereço de memória da função
37 | function_declaration();
38 | } else {
39 | ...
40 | ```
41 |
42 | O tipo para o identificador atual (ou seja, o nome da função) já havia sido definido corretamente. O trecho de código acima define o tipo (ou seja, `Fun)` e o endereço no `segmento de texto` para a função. Agora entram `parameter_decl` e `body_decl`.
43 |
44 | ## Parâmetros e Saída em Assembly
45 |
46 | Antes de colocar a mão na massa, precisamos entender o código em assembly que será gerado para uma função. Considere o seguinte:
47 |
48 |
49 | ```c
50 | int demo(int param_a, int *param_b) {
51 | int local_1;
52 | char local_2;
53 |
54 | ...
55 | }
56 | ```
57 |
58 | Quando demo é chamada, seu quadro de chamadas (estados da pilha) se parecerá com o seguinte (consulte a VM do capítulo 2):
59 |
60 |
61 | Neste caso, precisamos entender como os parâmetros e as variáveis locais serão manipulados no código em assembly. Isso é crucial para implementar corretamente a `parameter_decl` e `body_decl` no nosso interpretador.
62 |
63 |
64 | ```
65 | | .... | endereço alto
66 | +---------------+
67 | | arg: param_a | new_bp + 3
68 | +---------------+
69 | | arg: param_b | new_bp + 2
70 | +---------------+
71 | | endereço de retorno | new_bp + 1
72 | +---------------+
73 | | BP antigo | <- novo BP
74 | +---------------+
75 | | local_1 | new_bp - 1
76 | +---------------+
77 | | local_2 | new_bp - 2
78 | +---------------+
79 | | .... | endereço baixo
80 |
81 | ```
82 |
83 | O ponto chave aqui é que, independentemente de se tratar de um parâmetro (por exemplo, `param_a`) ou de uma variável local (por exemplo, `local_1`), todos são armazenados na `pilha`. Portanto, eles são referenciados pelo ponteiro `new_bp` e deslocamentos relativos, enquanto variáveis globais armazenadas no segmento de texto são referidas por endereço direto. Assim, precisamos saber o número de parâmetros e o deslocamento de cada um.
84 |
85 |
86 | ## Esqueleto para Análise de Função
87 |
88 | Aqui, você precisa desenvolver a estrutura básica do código que vai analisar a definição da função, incluindo `parameter_decl e body_decl`. Isso vai envolver lidar com a pilha e os endereços de forma apropriada, para garantir que a função seja executada corretamente.
89 | ```c
90 | void function_declaration() {
91 | // tipo nome_func (...) {...}
92 | // | essa parte
93 |
94 | match('(');
95 | function_parameter();
96 | match(')');
97 | match('{');
98 | function_body();
99 | //match('}'); // ①
100 |
101 | // ②
102 | // desfaz as declarações de variáveis locais para todas as variáveis locais.
103 | current_id = symbols;
104 | while (current_id[Token]) {
105 | if (current_id[Class] == Loc) {
106 | current_id[Class] = current_id[BClass];
107 | current_id[Type] = current_id[BType];
108 | current_id[Value] = current_id[BValue];
109 | }
110 | current_id = current_id + IdSize;
111 | }
112 | }
113 | ```
114 | Note que supostamente deveríamos consumir o último caractere } em ①. No entanto, não fazemos isso porque `variable_decl` e `function_decl` são analisados juntos (devido ao mesmo prefixo na gramática EBNF) dentro de `global_declaration`. variable_decl termina com ; enquanto `function_decl` termina com }. Se } for consumido, o loop `while` em `global_declaration` não poderá saber que o parsing de function_decl terminou. Portanto, deixamos para `global_declaration` consumi-lo.
115 |
116 |
117 | O que ② está tentando fazer é desfazer as declarações de variáveis locais para todas as variáveis locais. Como sabemos, as variáveis locais podem ter o mesmo nome que as globais; quando isso acontece, as globais ficam ofuscadas. Portanto, devemos recuperar o status assim que sairmos do corpo da função. As informações sobre variáveis globais são armazenadas nos campos `BXXX`, então iteramos sobre todos os identificadores para recuperá-los.
118 |
119 | ## function_parameter()
120 |
121 | ```
122 | parameter_decl ::= type {'*'} id {',' type {'*'} id}
123 | ```
124 | É bastante direto, exceto que precisamos lembrar os tipos e posições dos parâmetros.
125 |
126 | ```C
127 | int index_of_bp; // índice do ponteiro bp na pilha
128 |
129 | void function_parameter() {
130 | int type;
131 | int params;
132 | params = 0;
133 | while (token != ')') {
134 | // ①
135 |
136 | // int nome, ...
137 | type = INT;
138 | if (token == Int) {
139 | match(Int);
140 | } else if (token == Char) {
141 | type = CHAR;
142 | match(Char);
143 | }
144 |
145 | // tipo ponteiro
146 | while (token == Mul) {
147 | match(Mul);
148 | type = type + PTR;
149 | }
150 |
151 | // nome do parâmetro
152 | if (token != Id) {
153 | printf("%d: declaração de parâmetro ruim\n", line);
154 | exit(-1);
155 | }
156 | if (current_id[Class] == Loc) {
157 | printf("%d: declaração duplicada de parâmetro\n", line);
158 | exit(-1);
159 | }
160 |
161 | match(Id);
162 |
163 | // ②
164 | // armazena a variável local
165 | current_id[BClass] = current_id[Class]; current_id[Class] = Loc;
166 | current_id[BType] = current_id[Type]; current_id[Type] = type;
167 | current_id[BValue] = current_id[Value]; current_id[Value] = params++; // índice do parâmetro atual
168 |
169 | if (token == ',') {
170 | match(',');
171 | }
172 | }
173 |
174 | // ③
175 | index_of_bp = params+1;
176 | }
177 | ```
178 |
179 | Neste exemplo de código C, a função `function_declaration` é responsável por analisar a declaração de uma função, enquanto `function_parameter` trata dos parâmetros da função. O esqueleto oferece uma estrutura básica para manipular declarações de funções em um interpretador C simplificado.
180 |
181 | É bastante direto, exceto que precisamos lembrar os tipos e posições dos parâmetros.
182 |
183 | ```c
184 | int index_of_bp; // índice do ponteiro bp na pilha
185 |
186 | void function_parameter() {
187 | int type;
188 | int params;
189 | params = 0;
190 | while (token != ')') {
191 | // ①
192 |
193 | // int nome, ...
194 | type = INT;
195 | if (token == Int) {
196 | match(Int);
197 | } else if (token == Char) {
198 | type = CHAR;
199 | match(Char);
200 | }
201 |
202 | // tipo ponteiro
203 | while (token == Mul) {
204 | match(Mul);
205 | type = type + PTR;
206 | }
207 |
208 | // nome do parâmetro
209 | if (token != Id) {
210 | printf("%d: declaração de parâmetro ruim\n", line);
211 | exit(-1);
212 | }
213 | if (current_id[Class] == Loc) {
214 | printf("%d: declaração duplicada de parâmetro\n", line);
215 | exit(-1);
216 | }
217 |
218 | match(Id);
219 |
220 | // ②
221 | // armazena a variável local
222 | current_id[BClass] = current_id[Class]; current_id[Class] = Loc;
223 | current_id[BType] = current_id[Type]; current_id[Type] = type;
224 | current_id[BValue] = current_id[Value]; current_id[Value] = params++; // índice do parâmetro atual
225 |
226 | if (token == ',') {
227 | match(',');
228 | }
229 | }
230 |
231 | // ③
232 | index_of_bp = params+1;
233 | }
234 |
235 | ```
236 | A parte ① é a mesma que vimos em `global_declaration`, que é usada para analisar o tipo do parâmetro.
237 |
238 | A parte ② é para fazer backup das informações das variáveis globais que serão ofuscadas por variáveis locais. A posição do parâmetro atual é armazenada no campo `Value`.
239 |
240 |
241 | A parte ③ é usada para calcular a posição do ponteiro `bp`, que corresponde ao `new_bp` sobre o qual falamos na seção acima.
242 |
243 | ## function_body()
244 |
245 |
246 | Diferentemente do C moderno, nosso interpretador requer que todas as definições de variáveis que são usadas na função atual sejam colocadas no início da função atual. Essa regra é, na verdade, a mesma dos antigos compiladores C.
247 |
248 |
249 | ```c
250 | void function_body() {
251 | // type func_name (...) {...}
252 | // -->| |<--
253 |
254 | // ... {
255 | // 1. declarações locais
256 | // 2. instruções
257 | // }
258 |
259 | int pos_local; // posição das variáveis locais na pilha.
260 | int type;
261 | pos_local = index_of_bp;
262 |
263 | // ①
264 | while (token == Int || token == Char) {
265 | // declaração de variável local, assim como as globais.
266 | basetype = (token == Int) ? INT : CHAR;
267 | match(token);
268 |
269 | while (token != ';') {
270 | type = basetype;
271 | while (token == Mul) {
272 | match(Mul);
273 | type = type + PTR;
274 | }
275 |
276 | if (token != Id) {
277 | // declaração inválida
278 | printf("%d: má declaração local\n", line);
279 | exit(-1);
280 | }
281 | if (current_id[Class] == Loc) {
282 | // identificador já existe
283 | printf("%d: declaração local duplicada\n", line);
284 | exit(-1);
285 | }
286 | match(Id);
287 |
288 | // armazena a variável local
289 | current_id[BClass] = current_id[Class]; current_id[Class] = Loc;
290 | current_id[BType] = current_id[Type]; current_id[Type] = type;
291 | current_id[BValue] = current_id[Value]; current_id[Value] = ++pos_local; // índice do parâmetro atual
292 |
293 | if (token == ',') {
294 | match(',');
295 | }
296 | }
297 | match(';');
298 | }
299 |
300 | // ②
301 | // guarda o tamanho da pilha para variáveis locais
302 | *++text = ENT;
303 | *++text = pos_local - index_of_bp;
304 |
305 | // instruções
306 | while (token != '}') {
307 | statement();
308 | }
309 |
310 | // emite código para sair da subfunção
311 | *++text = LEV;
312 | }
313 |
314 | ```
315 |
316 | Você deve estar familiarizado com ①, já foi repetido várias vezes.
317 |
318 | A parte ② está escrevendo código assembly no segmento de texto. No capítulo sobre VM, dissemos que precisamos preservar espaços para variáveis locais na pilha; bem, é isso aí.
319 |
320 | ## Código
321 |
322 | Você pode baixar o código deste capítulo do [Github](https://github.com/lotabout/write-a-C-interpreter/tree/step-4), ou clonar com:
323 |
324 | ```
325 | git clone -b step-4 https://github.com/lotabout/write-a-C-interpreter
326 | ```
327 |
328 | O Código Ainda Não Funciona O código ainda não está em execução porque ainda existem algumas funções não implementadas. Você pode se desafiar a completá-las primeiro.
329 |
330 |
331 | ## Resumo
332 |
333 | O código deste capítulo não é extenso, sendo a maior parte dele usada para analisar variáveis, e muitas dessas partes são duplicadas. A análise para parâmetros e variáveis locais é quase a mesma, mas as informações armazenadas são diferentes.
334 |
335 | É claro que você pode querer revisar o capítulo sobre a Máquina Virtual (capítulo 2) para ter uma melhor compreensão da saída esperada para a função, de modo a entender por que gostaríamos de reunir tais informações. Isso é o que chamamos de "conhecimento de domínio".
336 |
337 |
338 | Lidaremos com `if` e `while` no próximo capítulo, até lá!
339 |
340 |
341 |
--------------------------------------------------------------------------------
/tutorial/pt-br/7-Statements.md:
--------------------------------------------------------------------------------
1 | Temos dois conceitos em C: declaração e expressão. Basicamente, declarações não têm um valor como resultado, enquanto expressões têm. Isso significa que você não pode atribuir uma declaração a uma variável.
2 |
3 | Temos 6 declarações em nosso interpretador:
4 |
5 | 1. `if (...) [else ]`
6 | 2. `while (...) `
7 | 3. `{ }`
8 | 4. `return xxx;`
9 | 5. `;`
10 | 6. `expressão;` (expressão termina com ponto e vírgula)
11 |
12 | O trabalho de análise sintática é relativamente fácil em comparação com a compreensão da saída em assembly esperada. Vamos explicar uma por uma.
13 |
14 | ## IF
15 |
16 | A declaração `if` serve para pular para um local diferente de acordo com a condição. Vamos verificar seu pseudo-código:
17 |
18 | ```
19 | if (...) [else ]
20 |
21 | if ()
22 | JZ a
23 | ===>
24 | else: JMP b
25 | a: a:
26 |
27 | b: b:
28 | ```
29 |
30 | O fluxo do código de assembly é:
31 |
32 | 1. execute ``.
33 | 2. Se a condição falhar, pule para `a` posição a, ou seja, declaração `else`.
34 | 3. Como o assembly é executado sequencialmente, se `` for executada,
35 | precisamos pular ``, portanto, um salto para `b` é necessário.
36 |
37 | Código C correspondente:
38 |
39 |
40 | ```c
41 | if (token == If) {
42 | match(If);
43 | match('(');
44 | expression(Assign); // parse condition
45 | match(')');
46 |
47 | *++text = JZ;
48 | b = ++text;
49 |
50 | statement(); // parse statement
51 | if (token == Else) { // parse else
52 | match(Else);
53 |
54 | // emit code for JMP B
55 | *b = (int)(text + 3);
56 | *++text = JMP;
57 | b = ++text;
58 |
59 | statement();
60 | }
61 |
62 | *b = (int)(text + 1);
63 | }
64 | ```
65 |
66 | ## While
67 |
68 | `while` é mais simples que `if`:
69 |
70 | ```
71 | a: a:
72 | while ()
73 | JZ b
74 |
75 | JMP a
76 | b: b:
77 | ```
78 | Nada digno de menção. Código em C:
79 |
80 | ```c
81 | else if (token == While) {
82 | match(While);
83 | a = text + 1;
84 |
85 | match('(');
86 | expression(Assign);
87 | match(')');
88 |
89 | *++text = JZ;
90 | b = ++text;
91 |
92 | statement();
93 |
94 | *++text = JMP;
95 | *++text = (int)a;
96 | *b = (int)(text + 1);
97 | }
98 | ```
99 | ## Return
100 |
101 | Quando encontramos `return`, isso significa que a função está prestes a terminar, portanto, `LEV` é necessário para indicar a saída.
102 |
103 | ```c
104 | else if (token == Return) {
105 | // return [expression];
106 | match(Return);
107 |
108 | if (token != ';') {
109 | expression(Assign);
110 | }
111 |
112 | match(';');
113 |
114 | // emit code for return
115 | *++text = LEV;
116 | }
117 | ```
118 | ## Outros
119 |
120 | Outras declarações atuam como auxiliares para o compilador agrupar melhor os códigos. Elas não gerarão códigos de assembly. A seguir:
121 |
122 | ```c
123 | else if (token == '{') {
124 | // { ... }
125 | match('{');
126 | while (token != '}') {
127 | statement();
128 | }
129 |
130 | match('}');
131 | }
132 | else if (token == ';') {
133 | // declaração vazia
134 | match(';');
135 | }
136 | else {
137 | // a = b; ou function_call();
138 | expression(Assign);
139 | match(';');
140 | }
141 | ```
142 | ## Código
143 |
144 | Você pode baixar o código deste capítulo no [Github](https://github.com/lotabout/write-a-C-interpreter/tree/step-5),ou clonar com:
145 |
146 | ```
147 | git clone -b step-5 https://github.com/lotabout/write-a-C-interpreter
148 | ```
149 | O código ainda não funcionará porque ainda há algumas funções não implementadas. Você pode se desafiar a preenchê-las primeiro.
150 |
151 | ## Resumo
152 |
153 | Como você pode ver, implementar a análise sintática para um interpretador não é difícil. Mas parece complicado porque precisamos reunir conhecimento suficiente durante a análise para gerar o código alvo (assembly, no nosso caso). Você vê, esse é um grande obstáculo para os iniciantes começarem a implementação. Portanto, em vez de "conhecimento de programação", o "conhecimento de domínio" também é necessário para realmente alcançar algo.
154 |
155 | Assim, sugiro que você aprenda assembly se ainda não o fez. Não é difícil, mas é útil para entender como os computadores funcionam.
156 |
--------------------------------------------------------------------------------