├── .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 | --------------------------------------------------------------------------------