├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── bootstrap.bat ├── common └── opcodes.h ├── doc └── grammar.md ├── passembler ├── fixuptbl.c ├── fixuptbl.h ├── keywords.c ├── keywords.h ├── lex.c ├── lex.h ├── main.c ├── parse.c ├── parse.h ├── symtbl.c ├── symtbl.h └── test.p ├── pdisasm └── main.c ├── src ├── lexer.c ├── lexer.h ├── main.c ├── parser.c ├── parser.h ├── ptypes.h ├── symtbl.c ├── symtbl.h ├── typestack.c └── typestack.h ├── tests ├── arraytest.pl0 ├── fortest.pl0 ├── int2bin.pl0 ├── lextest.pl0 ├── passembler │ └── test1.p ├── primes1000.pl0 ├── primes4096.pl0 ├── recursion.pl0 ├── simple0.pl0 ├── simple1.pl0 ├── simple2.pl0 ├── simple3.pl0 ├── simple4.pl0 └── typerror1.pl0 ├── virtualmachine ├── main.c ├── vm.c └── vm.h └── vmdbgui ├── CMakeLists.txt └── src ├── common.h ├── instrmodel.cpp ├── instrmodel.h ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── regmodel.cpp ├── regmodel.h ├── stackmodel.cpp ├── stackmodel.h ├── vmwrapper.cpp └── vmwrapper.h /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | build 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.7) 2 | project(nanopascal) 3 | 4 | include_directories(${PROJECT_SOURCE_DIR}/common) 5 | 6 | set(PASCALSRC 7 | ${PROJECT_SOURCE_DIR}/src/symtbl.c 8 | ${PROJECT_SOURCE_DIR}/src/parser.c 9 | ${PROJECT_SOURCE_DIR}/src/lexer.c 10 | ${PROJECT_SOURCE_DIR}/src/typestack.c 11 | ${PROJECT_SOURCE_DIR}/src/main.c 12 | ) 13 | 14 | set(VMSRC 15 | ${PROJECT_SOURCE_DIR}/virtualmachine/main.c 16 | ${PROJECT_SOURCE_DIR}/virtualmachine/vm.c 17 | ) 18 | 19 | set(PASMSRC 20 | ${PROJECT_SOURCE_DIR}/passembler/main.c 21 | ${PROJECT_SOURCE_DIR}/passembler/keywords.c 22 | ${PROJECT_SOURCE_DIR}/passembler/lex.c 23 | ${PROJECT_SOURCE_DIR}/passembler/symtbl.c 24 | ${PROJECT_SOURCE_DIR}/passembler/fixuptbl.c 25 | ${PROJECT_SOURCE_DIR}/passembler/parse.c 26 | ) 27 | 28 | set(PDISASMSRC 29 | ${PROJECT_SOURCE_DIR}/pdisasm/main.c 30 | ${PROJECT_SOURCE_DIR}/passembler/keywords.c 31 | ) 32 | 33 | add_subdirectory(vmdbgui) 34 | 35 | add_executable(vm ${VMSRC}) 36 | add_executable(passembler ${PASMSRC}) 37 | add_executable(pdisasm ${PDISASMSRC}) 38 | add_executable(nanopascal ${PASCALSRC}) 39 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nano Pascal 2 | A Tiny Pascal compiler with a virtual machine target to bootstrap systems. 3 | 4 | ## Current state of the project 5 | About as useful as a bucket of sand in the desert. 6 | 7 | ## Introduction 8 | Nano Pascal cross-compiler was developed to be able to write programs for an HD6309-based 8-bit computer. At the time of writing, the only way to take advantage of the HD6309 capabilities was to write directly in assembly langauge. 9 | 10 | The compiler is not aimed to produce the fastest code or smallest code, but to allow to bring up a system in a short amount of time. This is accomplished by generating byte code for a virtual machine instead of native HD6309 code. A small hand-crafted virtual machine executes the byte code on the 8-bit CPU. 11 | 12 | Porting the compiler to another CPU is straightforward. Only the virtual machine needs to be implemented on the target CPU; the byte code remains the same. 13 | 14 | Nano Pascal implements a subset of the Pascal langauge. For instance, it does not support floating-point numbers or pointers. 15 | 16 | ## Building the compiler 17 | The compiler should compile on Windows, Linux and OSX. 18 | 19 | Dependencies: 20 | * Ninja build 21 | * CMake 22 | * GCC or Clang 23 | 24 | ## Ready-made binaries 25 | At this time, there are no ready-made binaries available. 26 | -------------------------------------------------------------------------------- /bootstrap.bat: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mkdir build 4 | cd build 5 | cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug .. 6 | cd .. 7 | -------------------------------------------------------------------------------- /common/opcodes.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | p-code machine based on Niklaus Wirth's VM 4 | 5 | byte 1 : operand code (lower nibble), level high nibble 6 | byte 2,3: n - numeric constant or 7 | a - address 8 | 9 | the machine is little-endian 10 | 11 | */ 12 | 13 | #pragma once 14 | #include 15 | 16 | typedef struct 17 | { 18 | uint8_t *mem; /* program memory */ 19 | int16_t *dstack; /* data stack memory */ 20 | uint16_t pc; /* program counter (mem) */ 21 | uint16_t t; /* stack pointer/index (dstack) */ 22 | uint16_t b; /* base pointer/index (dstack) */ 23 | size_t inscount; /* number of instructions executed */ 24 | uint16_t memsize; /* number of bytes in mem buffer */ 25 | } vm_context_t; 26 | 27 | 28 | typedef enum 29 | { 30 | VM_LIT = 0, // load literal constant 0,n 31 | VM_OPR = 1, // arithmetic or logical operation 0,n 32 | VM_LOD = 2, // load variable v,d 33 | VM_STO = 3, // store variable v,d 34 | VM_CAL = 4, // call procedure or function v,a 35 | VM_INT = 5, // increment stack pointer 0,n 36 | VM_JMP = 6, // unconditional jump 0,a 37 | VM_JPC = 7, // jump if false (tos = 0) 0,a 38 | VM_LODX = 8, // load indexed v,d with offset loaded onto stack 39 | VM_STOX = 9, // load indexed v,d with offset loaded onto stack 40 | VM_HALT 41 | } opcode_t; // lower nibble code 42 | 43 | // functions of the VM_OPR code (stored in the 16 bit immediate) 44 | typedef enum 45 | { 46 | OPR_RET = 0, 47 | OPR_NEG = 1, 48 | OPR_ADD = 2, 49 | OPR_SUB = 3, 50 | OPR_MUL = 4, 51 | OPR_DIV = 5, 52 | OPR_ODD = 6, 53 | OPR_NULL = 7, // ?? 54 | OPR_EQ = 8, 55 | OPR_NEQ = 9, 56 | OPR_LESS = 10, 57 | OPR_LEQ = 11, 58 | OPR_GREATER = 12, 59 | OPR_GEQ = 13, 60 | OPR_SHR = 14, 61 | OPR_SHL = 15, 62 | OPR_SAR = 16, 63 | OPR_OUTCHAR = 17, 64 | OPR_OUTINT = 18, 65 | OPR_INCHAR = 19, 66 | OPR_ININT = 20 67 | } opr_t; 68 | 69 | #pragma pack(push,1) 70 | typedef struct 71 | { 72 | uint8_t opcode; // lower nibble is opcode_t, upper is level for procedures/functions 73 | uint16_t opt16; // optional 16-bit payload (address or literal) 74 | } instruction_t; 75 | #pragma pack(pop) 76 | -------------------------------------------------------------------------------- /doc/grammar.md: -------------------------------------------------------------------------------- 1 | 2 | # EXTENDED BNF GRAMMAR FOR NANO PASCAL 3 | 4 | ``` 5 | program = block "." . 6 | 7 | simple_type = "integer" | "char". 8 | 9 | type = simple_type 10 | | "array" "[" (number | const_id) "]" "of" simple_type. 11 | 12 | block = { "const" ident "=" number {"," ident "=" number} ";"} 13 | { "var" ident {"," ident} ":" type ";"} 14 | { "procedure" ident ";" block ";" } statement . 15 | 16 | const_id = ident. 17 | variable_id = ident [ '[' expression ']' ]. 18 | 19 | statement = [ variable_id ":=" expression | "call" ident 20 | | "?" variable_id | "!" expression {"," expression} 21 | | "begin" statement {";" statement } "end" 22 | | "if" condition "then" statement [ "else" statement ] 23 | | "for" variable_id ":=" expression ("to"|"downto") expression "do" statement. 24 | | "while" condition "do" statement ]. 25 | 26 | condition = "odd" expression | 27 | expression ("="|"#"|"<"|"<="|">"|">=") expression . 28 | 29 | expression = [ "+"|"-"] term { ("+"|"-") term} 30 | | "shr" expression 31 | | "shl" expression 32 | | "sar" expression. 33 | 34 | term = factor {("*"|"/") factor}. 35 | 36 | factor = variable_id | const_id | number | "(" expression ")". 37 | ``` -------------------------------------------------------------------------------- /passembler/fixuptbl.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include "fixuptbl.h" 4 | 5 | static void cpy(char *dst, const char *src, size_t len) 6 | { 7 | for(uint8_t idx=0; idxNentries = 0; 26 | } 27 | 28 | bool fix_add(fixtbl_t *tbl, const char *name, uint16_t namelen, uint16_t address) 29 | { 30 | tbl->fixups[tbl->Nentries].namelen = namelen; 31 | tbl->fixups[tbl->Nentries].name = malloc(namelen); 32 | tbl->fixups[tbl->Nentries].address = address; 33 | cpy(tbl->fixups[tbl->Nentries].name, name, namelen); 34 | tbl->Nentries++; 35 | return true; 36 | } 37 | 38 | fixup_t* fix_lookup(fixtbl_t *tbl, const char *name, uint16_t namelen) 39 | { 40 | for(uint16_t i=0; iNentries; i++) 41 | { 42 | if (namelen == tbl->fixups[i].namelen) 43 | { 44 | if (cmp(name, tbl->fixups[i].name, namelen)) 45 | { 46 | return &tbl->fixups[i]; 47 | } 48 | } 49 | } 50 | return NULL; 51 | } 52 | 53 | void fix_dump(fixtbl_t *tbl) 54 | { 55 | for(uint16_t i=0; iNentries; i++) 56 | { 57 | printf("; "); 58 | for(uint16_t j=0; jfixups[i].namelen; j++) 59 | { 60 | putchar(tbl->fixups[i].name[j]); 61 | } 62 | printf(" 0x%04X\n", tbl->fixups[i].address); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /passembler/fixuptbl.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | call/jump fix-up table for p-code assembler 4 | Niels A. Moseley (c) 2021 5 | 6 | */ 7 | 8 | #pragma once 9 | #include 10 | #include 11 | #include 12 | 13 | /** fixup table entry */ 14 | typedef struct 15 | { 16 | uint16_t address; // address of instruction to fix up 17 | char *name; // name of label 18 | uint16_t namelen; 19 | } fixup_t; 20 | 21 | #define MAX_FIXES 1000 22 | 23 | /** symbol table for labels */ 24 | typedef struct 25 | { 26 | uint16_t Nentries; ///< number of fixups in the table 27 | fixup_t fixups[MAX_FIXES]; ///< storage for fixups 28 | } fixtbl_t; 29 | 30 | bool fix_init(fixtbl_t *tbl); 31 | bool fix_add(fixtbl_t *tbl, const char *name, uint16_t namelen, uint16_t address); 32 | void fix_dump(fixtbl_t *tbl); 33 | -------------------------------------------------------------------------------- /passembler/keywords.c: -------------------------------------------------------------------------------- 1 | #include "keywords.h" 2 | 3 | // define PL/0 keywords 4 | // the order must be the same 5 | // as the TOK_ definitions starting at value 100 6 | 7 | const char* keywords[NKEYWORDS] = 8 | { 9 | "LIT", // 0 10 | "OPR", 11 | "LOD", 12 | "STO", 13 | "CAL", 14 | "INT", 15 | "JMP", 16 | "JPC", 17 | "LODX", 18 | "STOX", 19 | "HALT", 20 | "RET", 21 | "NEG", 22 | "ADD", 23 | "SUB", 24 | "MUL", 25 | "DIV", 26 | "ODD", 27 | "LES", 28 | "LEQ", 29 | "GRE", 30 | "GEQ", 31 | "NEQ", 32 | "EQU", 33 | "SHL", 34 | "SHR", 35 | "SAR", 36 | "OUTCHAR", 37 | "OUTINT", 38 | "INCHAR", 39 | "ININT" 40 | }; 41 | 42 | -------------------------------------------------------------------------------- /passembler/keywords.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define NKEYWORDS 31 4 | extern const char* keywords[NKEYWORDS]; 5 | 6 | typedef enum 7 | { 8 | TOK_UNDEFINED = 0, 9 | TOK_INTEGER = 1, 10 | TOK_IDENT = 2, 11 | TOK_EOL = 3, 12 | TOK_LABEL = 4, 13 | 14 | /* keywords */ 15 | TOK_LIT = 100, 16 | TOK_OPR, 17 | TOK_LOD, 18 | TOK_STO, 19 | TOK_CAL, 20 | TOK_INT, 21 | TOK_JMP, 22 | TOK_JPC, 23 | TOK_LODX, 24 | TOK_STOX, 25 | TOK_HALT, 26 | // OPR functions: 27 | TOK_RET, 28 | TOK_NEG, 29 | TOK_ADD, 30 | TOK_SUB, 31 | TOK_MUL, 32 | TOK_DIV, 33 | TOK_ODD, 34 | TOK_LES, 35 | TOK_LEQ, 36 | TOK_GRE, 37 | TOK_GEQ, 38 | TOK_NEQ, 39 | TOK_EQU, 40 | TOK_SHL, 41 | TOK_SHR, 42 | TOK_SAR, 43 | TOK_OUTCHAR, 44 | TOK_OUTINT, 45 | TOK_INCHAR, 46 | TOK_ININT, 47 | TOK_EOF = 255 48 | } token_t; 49 | 50 | 51 | -------------------------------------------------------------------------------- /passembler/lex.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Lexer for p-code assembler 4 | N.A. Moseley (c) 2021 5 | 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include "lex.h" 12 | 13 | void lex_init(lex_context_t *context, const char *sourcePtr) 14 | { 15 | context->src = sourcePtr; 16 | context->srcidx = 0; 17 | context->toklen = 0; 18 | context->tokstr = sourcePtr; 19 | context->lstate = LS_IDLE; 20 | context->linenum = 1; 21 | context->curtok = TOK_UNDEFINED; 22 | } 23 | 24 | void lex_free(lex_context_t *context) 25 | { 26 | 27 | } 28 | 29 | static bool isNumeric(char s) 30 | { 31 | if ((s>='0') && (s<='9')) 32 | return true; 33 | return false; 34 | } 35 | 36 | static bool isAlpha(char s) 37 | { 38 | if ((s>='A') && (s<='Z')) 39 | return true; 40 | if ((s>='a') && (s<='z')) 41 | return true; 42 | return false; 43 | } 44 | 45 | static uint8_t hex2dec(const char s) 46 | { 47 | if ((s>='0') && (s<='9')) 48 | return s-'0'; 49 | if ((s >= 'A') && (s <='F')) 50 | return s - 'A' + 10; 51 | if ((s >= 'a') && (s <='f')) 52 | return s - 'a' + 10; 53 | return 255; 54 | } 55 | 56 | void lex_acceptchar(lex_context_t *context) 57 | { 58 | context->toklen++; 59 | context->srcidx++; 60 | } 61 | 62 | void lex_ignorechar(lex_context_t *context) 63 | { 64 | context->srcidx++; 65 | } 66 | 67 | char lex_toupper(const char c) 68 | { 69 | if ((c >= 'a') && (c <='z')) 70 | { 71 | return c - 'a' + 'A'; 72 | } 73 | } 74 | 75 | // check if the current token string is a keyword 76 | // and set the token accordingly 77 | void lex_checkKeyword(lex_context_t *context) 78 | { 79 | for(int kwindex=0; kwindextoklen] != 0) 84 | { 85 | // keyword and token are not the same length 86 | // so skip this keyword 87 | continue; 88 | } 89 | 90 | bool fail = false; 91 | for(int cindex=0; cindex < context->toklen; cindex++) 92 | { 93 | if (kw[cindex] != lex_toupper(context->tokstr[cindex])) 94 | { 95 | fail = true; 96 | break; 97 | } 98 | } 99 | 100 | if (fail) 101 | continue; 102 | 103 | context->curtok = 100 + kwindex; 104 | return; 105 | } 106 | } 107 | 108 | void lex_emit(lex_context_t *context, token_t tok) 109 | { 110 | context->curtok = tok; 111 | if (tok == TOK_IDENT) 112 | { 113 | lex_checkKeyword(context); 114 | } 115 | } 116 | 117 | void lex_next(lex_context_t *context) 118 | { 119 | uint8_t tmp; 120 | while(1) 121 | { 122 | char c = context->src[context->srcidx]; 123 | if (c == 0) 124 | { 125 | lex_emit(context, TOK_EOF); 126 | return; 127 | } 128 | 129 | switch(context->lstate) 130 | { 131 | case LS_IDLE: 132 | context->toklen = 0; 133 | context->tokstr = context->src + context->srcidx; 134 | if (isAlpha(c)) 135 | { 136 | lex_acceptchar(context); 137 | context->lstate = LS_IDENT; 138 | } 139 | else if (isNumeric(c)) 140 | { 141 | lex_acceptchar(context); 142 | context->lit = c - '0'; 143 | context->lstate = LS_INTEGER; 144 | } 145 | else if (c == 10) 146 | { 147 | lex_acceptchar(context); 148 | lex_emit(context, TOK_EOL); 149 | context->linenum++; 150 | return; 151 | } 152 | else if (c == '@') 153 | { 154 | context->lit = 0; 155 | context->lstate = LS_LABEL; 156 | context->tokstr++; 157 | lex_ignorechar(context); 158 | } 159 | else if (c == '$') 160 | { 161 | context->lit = 0; 162 | context->lstate = LS_HEX; 163 | lex_ignorechar(context); 164 | } 165 | else if (c == ';') 166 | { 167 | // line comment 168 | context->lstate = LS_COMMENT; 169 | lex_ignorechar(context); 170 | } 171 | else 172 | { 173 | // whitespace or something.. 174 | // skip. 175 | lex_ignorechar(context); 176 | } 177 | break; 178 | case LS_HEX: 179 | tmp = hex2dec(c); 180 | if (tmp == 255) // not hex 181 | { 182 | lex_emit(context, TOK_INTEGER); 183 | context->lstate = LS_IDLE; 184 | return; 185 | } 186 | else 187 | { 188 | context->lit <<= 4; 189 | context->lit |= tmp; 190 | lex_acceptchar(context); 191 | } 192 | break; 193 | case LS_INTEGER: 194 | if (!isNumeric(c)) 195 | { 196 | lex_emit(context, TOK_INTEGER); 197 | context->lstate = LS_IDLE; 198 | return; 199 | } 200 | else 201 | { 202 | uint16_t orig = context->lit; 203 | context->lit <<= 2; // times 4 204 | context->lit += orig; // times 5 205 | context->lit <<= 1; // times 10 206 | context->lit += c - '0'; 207 | lex_acceptchar(context); 208 | } 209 | break; 210 | case LS_IDENT: 211 | if (isNumeric(c) || isAlpha(c)) 212 | { 213 | lex_acceptchar(context); 214 | } 215 | else 216 | { 217 | lex_emit(context, TOK_IDENT); 218 | context->lstate = LS_IDLE; 219 | return; 220 | } 221 | break; 222 | case LS_LABEL: 223 | if (isNumeric(c) || isAlpha(c)) 224 | { 225 | lex_acceptchar(context); 226 | } 227 | else 228 | { 229 | lex_emit(context, TOK_LABEL); 230 | context->lstate = LS_IDLE; 231 | return; 232 | } 233 | break; 234 | case LS_COMMENT: 235 | if (c != 10) 236 | { 237 | lex_ignorechar(context); 238 | } 239 | else 240 | { 241 | context->lstate = LS_IDLE; 242 | } 243 | break; 244 | default: 245 | context->lstate = LS_IDLE; 246 | } 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /passembler/lex.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Lexer for p-code assembler 4 | N.A. Moseley (c) 2021 5 | 6 | */ 7 | 8 | #pragma once 9 | #include 10 | #include "keywords.h" 11 | 12 | typedef enum 13 | { 14 | LS_IDLE = 0, 15 | LS_INTEGER, 16 | LS_IDENT, 17 | LS_LABEL, 18 | LS_COMMENT, 19 | LS_HEX 20 | } lexstate_t; 21 | 22 | typedef struct 23 | { 24 | const char *src; 25 | int srcidx; 26 | 27 | lexstate_t lstate; 28 | 29 | const char *tokstr; 30 | int toklen; 31 | uint16_t lit; ///< literal integer or hex number 32 | 33 | token_t curtok; 34 | int linenum; 35 | } lex_context_t; 36 | 37 | void lex_init(lex_context_t *context, const char *sourcePtr); 38 | void lex_free(lex_context_t *context); 39 | void lex_next(lex_context_t *context); 40 | -------------------------------------------------------------------------------- /passembler/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | p-code assembler 4 | Niels A. Moseley (c) 2021 5 | 6 | */ 7 | 8 | #include 9 | #include 10 | #include "lex.h" 11 | #include "parse.h" 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | printf("; p-code assembler v0.1\n\n"); 16 | 17 | if (argc < 2) 18 | { 19 | printf("Usage: %s \n", argv[0]); 20 | return -1; 21 | } 22 | 23 | FILE *fin = fopen(argv[1],"rb"); 24 | if (fin == 0) 25 | { 26 | printf("Could not read file %s\n", argv[1]); 27 | return -1; 28 | } 29 | 30 | fseek(fin,0,SEEK_END); 31 | size_t bytes = ftell(fin); 32 | rewind(fin); 33 | 34 | printf("; Loading %lu bytes\n", bytes); 35 | 36 | char *src = malloc(bytes); 37 | if (fread(src, 1, bytes, fin) != bytes) 38 | { 39 | printf("Could not read file %s\n", argv[1]); 40 | return -1; 41 | } 42 | 43 | parse(src); 44 | } 45 | -------------------------------------------------------------------------------- /passembler/parse.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Parser for p-code assembler 4 | N.A. Moseley (c) 2021 5 | 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "symtbl.h" 13 | #include "parse.h" 14 | #include "fixuptbl.h" 15 | #include "opcodes.h" 16 | 17 | typedef struct 18 | { 19 | lex_context_t lex; 20 | symtbl_t symtbl; 21 | fixtbl_t fixtbl; 22 | uint16_t emitaddress; ///< address of next emitted instruction 23 | 24 | uint8_t *code; 25 | uint16_t codelen; 26 | } parse_context_t; 27 | 28 | void emit_ins(parse_context_t *context, 29 | const uint8_t opcode, 30 | const uint16_t imm16) 31 | { 32 | context->code[context->emitaddress*3 ] = opcode; 33 | context->code[context->emitaddress*3+1] = imm16 & 0xFF; 34 | context->code[context->emitaddress*3+2] = (imm16 >> 8) & 0xFF; 35 | } 36 | 37 | void next(parse_context_t *context) 38 | { 39 | lex_next(&context->lex); 40 | } 41 | 42 | token_t token(parse_context_t *context) 43 | { 44 | return context->lex.curtok; 45 | } 46 | 47 | void parse_error(parse_context_t *context, const char *str) 48 | { 49 | printf("\nLine %d: %s", context->lex.linenum, str); 50 | printf(" tokstr: '"); 51 | for(uint16_t i=0; ilex.toklen; i++) 52 | putchar(context->lex.tokstr[i]); 53 | printf("'\n"); 54 | } 55 | 56 | // instruction (level|aluop) (offset | addr) 57 | bool parse_instruction(parse_context_t *context) 58 | { 59 | token_t optok = token(context); 60 | uint8_t opcode = optok - 100; 61 | next(context); 62 | 63 | uint16_t addr = 0; 64 | if ((optok == TOK_JMP) || (optok == TOK_JPC)) 65 | { 66 | // expect label or absolute address 67 | if (token(context) == TOK_INTEGER) 68 | { 69 | emit_ins(context, opcode, context->lex.lit); 70 | context->emitaddress++; 71 | } 72 | else if (token(context) == TOK_LABEL) 73 | { 74 | sym_t *label = sym_lookup(&context->symtbl, 75 | context->lex.tokstr, 76 | context->lex.toklen); 77 | 78 | if (label == NULL) 79 | { 80 | // label not found, add to fixup list! 81 | fix_add(&context->fixtbl, context->lex.tokstr, context->lex.toklen, 82 | context->emitaddress); 83 | 84 | emit_ins(context, opcode, 0); 85 | } 86 | else 87 | { 88 | emit_ins(context, opcode, label->address); 89 | } 90 | context->emitaddress++; 91 | } 92 | else 93 | { 94 | // error 95 | parse_error(context, "expected integer or label\n"); 96 | return false; 97 | } 98 | } 99 | else if (optok == TOK_CAL) 100 | { 101 | // expect level 102 | if (token(context) != TOK_INTEGER) 103 | { 104 | parse_error(context, "CAL level expected\n"); 105 | } 106 | 107 | uint8_t level = (context->lex.lit) & 0xF; 108 | opcode |= (level << 4); 109 | 110 | next(context); 111 | 112 | // expect label or absolute address 113 | if (token(context) == TOK_INTEGER) 114 | { 115 | emit_ins(context, opcode, context->lex.lit); 116 | context->emitaddress++; 117 | } 118 | else if (token(context) == TOK_LABEL) 119 | { 120 | sym_t *label = sym_lookup(&context->symtbl, 121 | context->lex.tokstr, 122 | context->lex.toklen); 123 | 124 | if (label == NULL) 125 | { 126 | // label not found, add to fixup list! 127 | fix_add(&context->fixtbl, context->lex.tokstr, context->lex.toklen, 128 | context->emitaddress); 129 | 130 | emit_ins(context, opcode, 0); 131 | } 132 | else 133 | { 134 | emit_ins(context, opcode, label->address); 135 | } 136 | context->emitaddress++; 137 | } 138 | else 139 | { 140 | // error 141 | parse_error(context, "expected integer or label\n"); 142 | return false; 143 | } 144 | } 145 | else if (optok == TOK_HALT) 146 | { 147 | emit_ins(context, opcode, 0); 148 | context->emitaddress++; 149 | } 150 | else if ((optok == TOK_LIT) || (optok == TOK_INT)) 151 | { 152 | if (token(context) != TOK_INTEGER) 153 | { 154 | // error 155 | parse_error(context, "expected integer in literal\n"); 156 | return false; 157 | } 158 | 159 | emit_ins(context, opcode, context->lex.lit); 160 | context->emitaddress++; 161 | } 162 | else if ((optok == TOK_LOD) || (optok == TOK_STO) || (optok == TOK_LODX) || (optok == TOK_STOX)) 163 | { 164 | if (token(context) != TOK_INTEGER) 165 | { 166 | parse_error(context, "Expected integer level after opcode\n"); 167 | return false; 168 | } 169 | 170 | opcode |= (context->lex.lit << 4); 171 | 172 | next(context); 173 | if (token(context) != TOK_INTEGER) 174 | { 175 | // error 176 | parse_error(context, "expected integer\n"); 177 | return false; 178 | } 179 | emit_ins(context, opcode, context->lex.lit); 180 | context->emitaddress++; 181 | } 182 | else 183 | { 184 | // alu operations 185 | if (optok == TOK_RET) 186 | { 187 | emit_ins(context, 0x01, OPR_RET); // opr 188 | context->emitaddress++; 189 | } 190 | else if (optok == TOK_NEG) 191 | { 192 | emit_ins(context, 0x01, OPR_NEG); // opr 193 | context->emitaddress++; 194 | } 195 | else if (optok == TOK_ADD) 196 | { 197 | emit_ins(context, 0x01, OPR_ADD); // opr 198 | context->emitaddress++; 199 | } 200 | else if (optok == TOK_SUB) 201 | { 202 | emit_ins(context, 0x01, OPR_SUB); // opr 203 | context->emitaddress++; 204 | } 205 | else if (optok == TOK_MUL) 206 | { 207 | emit_ins(context, 0x01, OPR_MUL); // opr 208 | context->emitaddress++; 209 | } 210 | else if (optok == TOK_DIV) 211 | { 212 | emit_ins(context, 0x01, OPR_DIV); // opr 213 | context->emitaddress++; 214 | } 215 | else if (optok == TOK_ODD) 216 | { 217 | emit_ins(context, 0x01, OPR_ODD); // opr 218 | context->emitaddress++; 219 | } 220 | else if (optok == TOK_EQU) 221 | { 222 | emit_ins(context, 0x01, OPR_EQ); // opr 223 | context->emitaddress++; 224 | } 225 | else if (optok == TOK_NEQ) 226 | { 227 | emit_ins(context, 0x01, OPR_NEQ); // opr 228 | context->emitaddress++; 229 | } 230 | else if (optok == TOK_LES) 231 | { 232 | emit_ins(context, 0x01, OPR_LESS); // opr 233 | context->emitaddress++; 234 | } 235 | else if (optok == TOK_LEQ) 236 | { 237 | emit_ins(context, 0x01, OPR_LEQ); // opr 238 | context->emitaddress++; 239 | } 240 | else if (optok == TOK_GRE) 241 | { 242 | emit_ins(context, 0x01, OPR_GREATER); // opr 243 | context->emitaddress++; 244 | } 245 | else if (optok == TOK_GEQ) 246 | { 247 | emit_ins(context, 0x01, OPR_GEQ); // opr 248 | context->emitaddress++; 249 | } 250 | else if (optok == TOK_OUTCHAR) 251 | { 252 | emit_ins(context, 0x01, OPR_OUTCHAR); // opr 253 | context->emitaddress++; 254 | } 255 | else if (optok == TOK_OUTINT) 256 | { 257 | emit_ins(context, 0x01, OPR_OUTINT); // opr 258 | context->emitaddress++; 259 | } 260 | else if (optok == TOK_INCHAR) 261 | { 262 | emit_ins(context, 0x01, OPR_INCHAR); // opr 263 | context->emitaddress++; 264 | } 265 | else if (optok == TOK_ININT) 266 | { 267 | emit_ins(context, 0x01, OPR_ININT); // opr 268 | context->emitaddress++; 269 | } 270 | else if (optok == TOK_SHL) 271 | { 272 | emit_ins(context, 0x01, OPR_SHL); // opr 273 | context->emitaddress++; 274 | } 275 | else if (optok == TOK_SHR) 276 | { 277 | emit_ins(context, 0x01, OPR_SHR); // opr 278 | context->emitaddress++; 279 | } 280 | else if (optok == TOK_SAR) 281 | { 282 | emit_ins(context, 0x01, OPR_SAR); // opr 283 | context->emitaddress++; 284 | } 285 | else 286 | { 287 | parse_error(context, "undefined opcode\n"); 288 | return false; 289 | } 290 | } 291 | next(context); 292 | return true; 293 | } 294 | 295 | bool parseLine(parse_context_t *context) 296 | { 297 | // skip empty lines 298 | if (context->lex.curtok == TOK_EOL) 299 | { 300 | next(context); 301 | return true; 302 | } 303 | 304 | while((context->lex.curtok != TOK_EOL) && (context->lex.curtok != TOK_EOF)) 305 | { 306 | if (context->lex.curtok >= 100) 307 | { 308 | if (!parse_instruction(context)) 309 | return false; 310 | } 311 | else if (context->lex.curtok == TOK_LABEL) 312 | { 313 | // enter label into the symbol table 314 | sym_add(&context->symtbl, context->lex.tokstr, 315 | context->lex.toklen, context->emitaddress); 316 | 317 | next(context); 318 | } 319 | else 320 | { 321 | // error 322 | parse_error(context, "unexpected token\n"); 323 | return false; 324 | } 325 | } 326 | 327 | return true; 328 | } 329 | 330 | bool parse(const char *src) 331 | { 332 | parse_context_t context; 333 | lex_init(&context.lex, src); 334 | lex_next(&context.lex); 335 | sym_init(&context.symtbl); 336 | fix_init(&context.fixtbl); 337 | 338 | context.codelen = 4096; 339 | context.code = malloc(context.codelen); 340 | 341 | context.emitaddress = 0; 342 | 343 | while(context.lex.curtok != TOK_EOF) 344 | { 345 | if (!parseLine(&context)) 346 | return false; 347 | } 348 | 349 | printf("; Label table:\n"); 350 | sym_dump(&context.symtbl); 351 | 352 | printf("; Fixup table:\n"); 353 | fix_dump(&context.fixtbl); 354 | 355 | printf("; Produced %d bytes\n", context.emitaddress); 356 | 357 | // do fixups 358 | for(uint16_t i=0; iname, fix->namelen); 362 | if (s == NULL) 363 | { 364 | printf("Error: cannot find symbol for fixup!\n"); 365 | return false; 366 | } 367 | else 368 | { 369 | context.code[3*fix->address+1] = s->address & 0xFF; 370 | context.code[3*fix->address+2] = (s->address >> 8) & 0xFF; 371 | } 372 | } 373 | 374 | printf("; Program words:\n"); 375 | for(uint16_t i=0; i 10 | #include "lex.h" 11 | 12 | bool parse(const char *src); 13 | -------------------------------------------------------------------------------- /passembler/symtbl.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include "symtbl.h" 4 | 5 | static void cpy(char *dst, const char *src, size_t len) 6 | { 7 | for(uint8_t idx=0; idxNsymbols = 0; 26 | } 27 | 28 | bool sym_add(symtbl_t *tbl, const char *name, uint16_t namelen, uint16_t address) 29 | { 30 | tbl->syms[tbl->Nsymbols].namelen = namelen; 31 | tbl->syms[tbl->Nsymbols].name = malloc(namelen); 32 | tbl->syms[tbl->Nsymbols].address = address; 33 | cpy(tbl->syms[tbl->Nsymbols].name, name, namelen); 34 | tbl->Nsymbols++; 35 | return true; 36 | } 37 | 38 | sym_t* sym_lookup(symtbl_t *tbl, const char *name, uint16_t namelen) 39 | { 40 | for(uint16_t i=0; iNsymbols; i++) 41 | { 42 | if (namelen == tbl->syms[i].namelen) 43 | { 44 | if (cmp(name, tbl->syms[i].name, namelen)) 45 | { 46 | return &tbl->syms[i]; 47 | } 48 | } 49 | } 50 | return NULL; 51 | } 52 | 53 | void sym_dump(symtbl_t *tbl) 54 | { 55 | for(uint16_t i=0; iNsymbols; i++) 56 | { 57 | printf("; "); 58 | for(uint16_t j=0; jsyms[i].namelen; j++) 59 | { 60 | putchar(tbl->syms[i].name[j]); 61 | } 62 | printf(" 0x%04X\n", tbl->syms[i].address); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /passembler/symtbl.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Symbol table for p-code assembler 4 | Niels A. Moseley (c) 2021 5 | 6 | */ 7 | 8 | #pragma once 9 | #include 10 | #include 11 | #include 12 | 13 | /** symbol table entry */ 14 | typedef struct 15 | { 16 | uint16_t address; // address of label 17 | char *name; // name of symbol 18 | uint16_t namelen; 19 | } sym_t; 20 | 21 | #define MAX_SYMS 1000 22 | 23 | /** symbol table for labels */ 24 | typedef struct 25 | { 26 | uint16_t Nsymbols; ///< number of symbols in the table 27 | sym_t syms[MAX_SYMS]; ///< storage for symbols 28 | } symtbl_t; 29 | 30 | bool sym_init(symtbl_t *tbl); 31 | bool sym_add(symtbl_t *tbl, const char *name, uint16_t namelen, uint16_t address); 32 | sym_t* sym_lookup(symtbl_t *tbl, const char *name, uint16_t namelen); 33 | void sym_dump(symtbl_t *tbl); 34 | -------------------------------------------------------------------------------- /passembler/test.p: -------------------------------------------------------------------------------- 1 | CAL 0 2 2 | JMP 0 2 ignored 3 | INT 0 5 4 | LOD 1 3 5 | STO 0 3 6 | LOD 1 4 7 | STO 0 4 8 | LIT 0 0 9 | STO 1 5 10 | LOD 0 4 11 | LIT 0 0 12 | OPR 0 12 13 | JPC 0 29 14 | LOD 0 4 15 | OPR 0 6 16 | JPC 0 20 17 | LOD 1 5 18 | LOD 0 3 19 | OPR 0 2 20 | STO 1 5 21 | LIT 0 2 22 | LOD 0 3 23 | OPR 0 4 24 | STO 0 3 25 | LOD 0 4 26 | LIT 0 2 27 | OPR 0 5 28 | STO 0 4 29 | JMP 0 9 30 | OPR 0 0 31 | -------------------------------------------------------------------------------- /pdisasm/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "../passembler/keywords.h" 5 | #include "../virtualmachine/vm.h" 6 | 7 | void printalu(uint16_t imm16) 8 | { 9 | switch(imm16) 10 | { 11 | case OPR_RET: 12 | printf("RET\n"); 13 | break; 14 | case OPR_NEG: 15 | printf("NEG\n"); 16 | break; 17 | case OPR_ADD: 18 | printf("ADD\n"); 19 | break; 20 | case OPR_SUB: 21 | printf("SUB\n"); 22 | break; 23 | case OPR_MUL: 24 | printf("SUB\n"); 25 | break; 26 | case OPR_DIV: 27 | printf("SUB\n"); 28 | break; 29 | case OPR_ODD: 30 | printf("ODD\n"); 31 | break; 32 | case OPR_EQ: 33 | printf("EQU\n"); 34 | break; 35 | case OPR_NEQ: 36 | printf("NEQ\n"); 37 | break; 38 | case OPR_LESS: 39 | printf("LES\n"); 40 | break; 41 | case OPR_LEQ: 42 | printf("LEQ\n"); 43 | break; 44 | case OPR_GREATER: 45 | printf("GRE\n"); 46 | break; 47 | case OPR_GEQ: 48 | printf("GEQ\n"); 49 | break; 50 | case OPR_SHL: 51 | printf("SHL\n"); 52 | break; 53 | case OPR_SHR: 54 | printf("SHR\n"); 55 | break; 56 | case OPR_SAR: 57 | printf("SAR\n"); 58 | break; 59 | case OPR_ININT: 60 | printf("ININT\n"); 61 | break; 62 | case OPR_OUTINT: 63 | printf("OUTINT\n"); 64 | break; 65 | case OPR_INCHAR: 66 | printf("INCHAR\n"); 67 | break; 68 | case OPR_OUTCHAR: 69 | printf("OUTCHAR\n"); 70 | break; 71 | default: 72 | printf("??? ALU OPR %04X\n", imm16); 73 | break; 74 | } 75 | } 76 | 77 | void printins(const instruction_t *ins) 78 | { 79 | uint8_t opcode = ins->opcode; 80 | uint16_t imm16 = ins->opt16; 81 | switch (opcode & 0xF) 82 | { 83 | case VM_LIT: 84 | printf("LIT %d\n", imm16); 85 | break; 86 | case VM_OPR: 87 | printalu(imm16); 88 | break; 89 | case VM_LOD: 90 | printf("LOD lvl:%d ofs:%d\n", opcode >> 4 ,imm16); 91 | break; 92 | case VM_STO: 93 | printf("STO lvl:%d ofs:%d\n", opcode >> 4 ,imm16); 94 | break; 95 | case VM_LODX: 96 | printf("LODX lvl:%d ofs:%d\n", opcode >> 4 ,imm16); 97 | break; 98 | case VM_STOX: 99 | printf("STOX lvl:%d ofs:%d\n", opcode >> 4 ,imm16); 100 | break; 101 | case VM_CAL: 102 | printf("CAL lvl:%d 0x%04X\n", opcode >> 4, imm16); 103 | break; 104 | case VM_INT: 105 | printf("INT %d\n", imm16); 106 | break; 107 | case VM_JMP: 108 | printf("JMP 0x%04X\n", imm16); 109 | break; 110 | case VM_JPC: 111 | printf("JPC 0x%04X\n", imm16); 112 | break; 113 | case VM_HALT: 114 | printf("HALT\n"); 115 | break; 116 | default: 117 | printf("??? opcode = 0x%02X\n", opcode); 118 | break; 119 | } 120 | } 121 | 122 | int main(int argc, char *argv[]) 123 | { 124 | printf("; p-code disassembler v0.1\n"); 125 | 126 | if (argc < 2) 127 | { 128 | printf("Usage: %s \n", argv[0]); 129 | return -1; 130 | } 131 | 132 | FILE *fin = fopen(argv[1],"rb"); 133 | if (fin == 0) 134 | { 135 | printf("Could not read file %s\n", argv[1]); 136 | return -1; 137 | } 138 | 139 | fseek(fin,0,SEEK_END); 140 | size_t bytes = ftell(fin); 141 | rewind(fin); 142 | 143 | printf("; Loading %lu bytes\n", bytes); 144 | 145 | char *src = malloc(bytes); 146 | if (fread(src, 1, bytes, fin) != bytes) 147 | { 148 | printf("Could not read file %s\n", argv[1]); 149 | return -1; 150 | } 151 | 152 | for(uint16_t ofs=0; ofs 9 | #include 10 | #include 11 | #include "lexer.h" 12 | 13 | // define PL/0 keywords 14 | // the order must be the same 15 | // as the TOK_ definitions starting at value 100 16 | #define NKEYWORDS 23 17 | const char* keywords[NKEYWORDS] = 18 | { 19 | "PROGRAM", 20 | "BEGIN", 21 | "END", 22 | "VAR", 23 | "WHILE", 24 | "DO", 25 | "PROCEDURE", 26 | "CALL", 27 | "CONST", 28 | "IF", 29 | "THEN", 30 | "ODD", 31 | "ELSE", 32 | "SHR", 33 | "SHL", 34 | "SAR", 35 | "FOR", 36 | "TO", 37 | "DOWNTO", 38 | "INTEGER", 39 | "CHAR", 40 | "OF", 41 | "ARRAY" 42 | }; 43 | 44 | void lexer_init(lexer_context_t *context, char *source) 45 | { 46 | context->src = source; 47 | context->tokstart = source; 48 | context->toklen = 0; 49 | context->token = TOK_NONE; 50 | context->state = LS_IDLE; 51 | context->linenum = 1; 52 | } 53 | 54 | bool isWhitespace(const char c) 55 | { 56 | return (c==' ') || (c=='\t'); 57 | } 58 | 59 | bool isAlpha(const char c) 60 | { 61 | return ((c>='A') && (c<='Z')) || ((c>='a') && (c<='z')); 62 | } 63 | 64 | bool isNumeric(const char c) 65 | { 66 | return (c>='0') && (c<='9'); 67 | } 68 | 69 | bool isAlphaNum(const char c) 70 | { 71 | return isAlpha(c) || isNumeric(c); 72 | } 73 | 74 | char lex_toupper(const char c) 75 | { 76 | if ((c >= 'a') && (c <='z')) 77 | { 78 | return c - 'a' + 'A'; 79 | } 80 | } 81 | 82 | // add the current character to the token string 83 | void lexer_accept(lexer_context_t *context) 84 | { 85 | context->toklen++; 86 | } 87 | 88 | // skip the current character and reset the token string 89 | void lexer_skip(lexer_context_t *context) 90 | { 91 | context->tokstart++; 92 | context->toklen = 0; 93 | } 94 | 95 | // set the token ID and return the lexer to the idle state 96 | void lexer_emit(lexer_context_t *context, token_t tok) 97 | { 98 | context->token = tok; 99 | context->state = LS_IDLE; 100 | #ifdef TOKDUMP 101 | printf("lex: "); 102 | for(int i=0; itoklen; i++) 103 | { 104 | putchar(context->tokstart[i]); 105 | } 106 | printf(" (%d)\n", context->token); 107 | #endif 108 | } 109 | 110 | // look-ahead the next character 111 | char lexer_peekNextChar(lexer_context_t *context) 112 | { 113 | return context->tokstart[context->toklen+1]; 114 | } 115 | 116 | // check if the current token string is a keyword 117 | // and set the token accordingly 118 | void lexer_checkKeyword(lexer_context_t *context) 119 | { 120 | for(int kwindex=0; kwindextoklen] != 0) 125 | { 126 | // keyword and token are not the same length 127 | // so skip this keyword 128 | continue; 129 | } 130 | 131 | bool fail = false; 132 | for(int cindex=0; cindex < context->toklen; cindex++) 133 | { 134 | if (kw[cindex] != lex_toupper(context->tokstart[cindex])) 135 | { 136 | fail = true; 137 | break; 138 | } 139 | } 140 | 141 | if (fail) 142 | continue; 143 | 144 | context->token = 100 + kwindex; 145 | return; 146 | } 147 | } 148 | 149 | // generate the next token 150 | bool lexer_next(lexer_context_t *context) 151 | { 152 | // advance past the previously emitted token 153 | context->tokstart += context->toklen; 154 | context->toklen = 0; 155 | context->number = 0; 156 | while(1) 157 | { 158 | char c = context->tokstart[context->toklen]; 159 | 160 | if (c == 0) 161 | { 162 | lexer_emit(context, TOK_EOF); 163 | return true; 164 | } 165 | 166 | switch(context->state) 167 | { 168 | case LS_IDLE: 169 | context->toklen = 0; 170 | 171 | // skip whitespace 172 | if (isWhitespace(c)) 173 | { 174 | lexer_skip(context); 175 | } 176 | else if (isAlpha(c)) 177 | { 178 | context->state = LS_IDENT; 179 | lexer_accept(context); 180 | } 181 | else if (isNumeric(c)) 182 | { 183 | context->number = c-'0'; 184 | context->state = LS_INTEGER; 185 | lexer_accept(context); 186 | } 187 | else if (c == 10) 188 | { 189 | context->linenum++; 190 | #ifdef SUPPORT_EOL 191 | lexer_accept(context); 192 | lexer_emit(context, TOK_EOL); 193 | return true; 194 | #else 195 | lexer_skip(context); 196 | #endif 197 | } 198 | else 199 | { 200 | // some kind of operator? 201 | switch(c) 202 | { 203 | case ';': 204 | lexer_accept(context); 205 | lexer_emit(context, TOK_SEMICOL); 206 | return true; 207 | case '-': 208 | lexer_accept(context); 209 | lexer_emit(context, TOK_MINUS); 210 | return true; 211 | case '+': 212 | lexer_accept(context); 213 | lexer_emit(context, TOK_PLUS); 214 | return true; 215 | case '*': 216 | lexer_accept(context); 217 | lexer_emit(context, TOK_STAR); 218 | return true; 219 | case '=': 220 | lexer_accept(context); 221 | lexer_emit(context, TOK_EQUAL); 222 | return true; 223 | case '(': 224 | lexer_accept(context); 225 | lexer_emit(context, TOK_LPAREN); 226 | return true; 227 | case ')': 228 | lexer_accept(context); 229 | lexer_emit(context, TOK_RPAREN); 230 | return true; 231 | case '[': 232 | lexer_accept(context); 233 | lexer_emit(context, TOK_LBRACKET); 234 | return true; 235 | case ']': 236 | lexer_accept(context); 237 | lexer_emit(context, TOK_RBRACKET); 238 | return true; 239 | case '.': 240 | if (lexer_peekNextChar(context) == '.') 241 | { 242 | lexer_accept(context); 243 | lexer_accept(context); 244 | lexer_emit(context, TOK_DOUBLEPERIOD); 245 | } 246 | else 247 | { 248 | lexer_accept(context); 249 | lexer_emit(context, TOK_PERIOD); 250 | } 251 | return true; 252 | case ',': 253 | lexer_accept(context); 254 | lexer_emit(context, TOK_COMMA); 255 | return true; 256 | case '/': 257 | // check if this is a line comment or a regular 258 | // single / 259 | if (lexer_peekNextChar(context) == '/') 260 | { 261 | lexer_skip(context); 262 | lexer_skip(context); 263 | context->state = LS_LINECOMMENT; 264 | } 265 | else 266 | { 267 | lexer_accept(context); 268 | lexer_emit(context, TOK_SLASH); 269 | return true; 270 | } 271 | break; 272 | case '#': 273 | lexer_accept(context); 274 | lexer_emit(context, TOK_HASH); 275 | return true; 276 | case '!': 277 | lexer_accept(context); 278 | lexer_emit(context, TOK_EXCLAMATION); 279 | return true; 280 | case '?': 281 | lexer_accept(context); 282 | lexer_emit(context, TOK_QUESTION); 283 | return true; 284 | case ':': 285 | // check if this is an assign operator 286 | if (lexer_peekNextChar(context) == '=') 287 | { 288 | lexer_accept(context); 289 | lexer_accept(context); 290 | lexer_emit(context, TOK_ASSIGN); 291 | return true; 292 | } 293 | else 294 | { 295 | lexer_accept(context); 296 | lexer_emit(context, TOK_COLON); 297 | return true; 298 | } 299 | break; 300 | case '<': 301 | // check if this < or <= 302 | if (lexer_peekNextChar(context) == '=') 303 | { 304 | lexer_accept(context); 305 | lexer_accept(context); 306 | lexer_emit(context, TOK_LEQ); 307 | return true; 308 | } 309 | else 310 | { 311 | lexer_accept(context); 312 | lexer_emit(context, TOK_LESS); 313 | return true; 314 | } 315 | break; 316 | case '>': 317 | // check if this > or >= 318 | if (lexer_peekNextChar(context) == '=') 319 | { 320 | lexer_accept(context); 321 | lexer_accept(context); 322 | lexer_emit(context, TOK_GEQ); 323 | return true; 324 | } 325 | else 326 | { 327 | lexer_accept(context); 328 | lexer_emit(context, TOK_GREATER); 329 | return true; 330 | } 331 | break; 332 | default: 333 | // skip everything else 334 | lexer_skip(context); 335 | } 336 | } 337 | break; 338 | case LS_IDENT: 339 | if (isAlphaNum(c)) 340 | { 341 | lexer_accept(context); 342 | } 343 | else 344 | { 345 | lexer_emit(context, TOK_IDENT); 346 | lexer_checkKeyword(context); 347 | context->state = LS_IDLE; 348 | return true; 349 | } 350 | break; 351 | case LS_INTEGER: 352 | if (isNumeric(c)) 353 | { 354 | uint16_t orig = context->number; 355 | context->number <<= 2; 356 | context->number += orig; 357 | context->number <<= 1; 358 | context->number += c - '0'; 359 | lexer_accept(context); 360 | } 361 | else 362 | { 363 | lexer_emit(context, TOK_NUMBER); 364 | context->state = LS_IDLE; 365 | return true; 366 | } 367 | break; 368 | case LS_LINECOMMENT: 369 | if (c == 10) 370 | { 371 | context->state = LS_IDLE; 372 | } 373 | else 374 | { 375 | lexer_skip(context); 376 | } 377 | break; 378 | default: 379 | return false; // incorrect lexer state 380 | } 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /src/lexer.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | PL/0 lexical analyser 4 | N.A. Moseley 2021 5 | 6 | */ 7 | 8 | #pragma once 9 | #include 10 | #include 11 | 12 | //#define TOKDUMP 13 | 14 | typedef enum 15 | { 16 | TOK_NONE = 0, 17 | TOK_EOF, 18 | TOK_IDENT, 19 | TOK_NUMBER, 20 | TOK_SEMICOL, 21 | TOK_COLON, 22 | TOK_LPAREN, 23 | TOK_RPAREN, 24 | TOK_STAR, 25 | TOK_PLUS, 26 | TOK_MINUS, 27 | TOK_EQUAL, 28 | TOK_PERIOD, 29 | TOK_COMMA, 30 | TOK_EXCLAMATION, 31 | TOK_QUESTION, 32 | TOK_ASSIGN, // := 33 | TOK_EOL, 34 | TOK_LEQ, // <= 35 | TOK_GEQ, // >= 36 | TOK_LESS, // < 37 | TOK_GREATER, // > 38 | TOK_SLASH, // / 39 | TOK_HASH, // # 40 | TOK_LBRACKET, 41 | TOK_RBRACKET, 42 | TOK_DOUBLEPERIOD, 43 | TOK_PROGRAM = 100, 44 | TOK_BEGIN, 45 | TOK_END, 46 | TOK_VAR, 47 | TOK_WHILE, 48 | TOK_DO, 49 | TOK_PROCEDURE, 50 | TOK_CALL, 51 | TOK_CONST, 52 | TOK_IF, 53 | TOK_THEN, 54 | TOK_ODD, 55 | TOK_ELSE, 56 | TOK_SHR, 57 | TOK_SHL, 58 | TOK_SAR, 59 | TOK_FOR, 60 | TOK_TO, 61 | TOK_DOWNTO, 62 | TOK_INTEGER, 63 | TOK_CHAR, 64 | TOK_OF, 65 | TOK_ARRAY 66 | } token_t; 67 | 68 | typedef enum 69 | { 70 | LS_IDLE = 0, 71 | LS_INTEGER, 72 | LS_IDENT, 73 | LS_LINECOMMENT 74 | } lexstate_t; 75 | 76 | /** lexical analyser context data */ 77 | typedef struct 78 | { 79 | char *src; ///< pointer to the source code 80 | char *tokstart; ///< pointer to start of current token string 81 | int16_t toklen; ///< length of current token 82 | token_t token; ///< current token type 83 | lexstate_t state; ///< analyser state 84 | int16_t linenum; ///< current line number 85 | uint16_t number; ///< value of integer literal 86 | } lexer_context_t; 87 | 88 | /** initialise the lexical analyser */ 89 | void lexer_init(lexer_context_t *context, char *source); 90 | 91 | /** generate next token */ 92 | bool lexer_next(lexer_context_t *context); -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Pascal to p-code compiler 4 | N.A. Moseley 2021 5 | 6 | */ 7 | 8 | #include 9 | #include 10 | //#include "lexer.h" 11 | #include "parser.h" 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | printf("; PL/0 to p-code compiler\n"); 16 | printf("; Compiled on " __DATE__ "\n\n"); 17 | 18 | if (argc < 2) 19 | { 20 | printf("Usage: %s \n", argv[0]); 21 | return -1; 22 | } 23 | 24 | FILE *fin = fopen(argv[1],"rb"); 25 | if (fin == 0) 26 | { 27 | printf("Could not read file %s\n", argv[1]); 28 | return -1; 29 | } 30 | 31 | fseek(fin,0,SEEK_END); 32 | size_t bytes = ftell(fin); 33 | rewind(fin); 34 | 35 | printf("; file = %s\n", argv[1]); 36 | printf("; Loading %lu bytes\n\n", bytes); 37 | 38 | char *src = malloc(bytes); 39 | if (fread(src, 1, bytes, fin) != bytes) 40 | { 41 | printf("Could not read file %s\n", argv[1]); 42 | return -1; 43 | } 44 | 45 | if (!parse(src)) 46 | { 47 | fprintf(stderr, "Parse failed!\n"); 48 | return -1; 49 | } 50 | else 51 | { 52 | fprintf(stderr, "Parse ok!\n"); 53 | } 54 | 55 | free(src); 56 | return 0; 57 | } 58 | -------------------------------------------------------------------------------- /src/parser.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | PL/0 parser 4 | N.A. Moseley 2021 5 | 6 | */ 7 | 8 | #include 9 | #include "parser.h" 10 | #include "lexer.h" 11 | #include "symtbl.h" 12 | #include "typestack.h" 13 | #include "opcodes.h" 14 | 15 | typedef struct 16 | { 17 | lexer_context_t lex; 18 | symtbl_t symtbl; ///< the symbol table 19 | char *matchstart; ///< pointer to string of last matched token 20 | int16_t matchlen; ///< string length of last matched token 21 | token_t matchtok; ///< matched token 22 | 23 | typestack_t typestack; ///< hold type information during expression parsing. 24 | 25 | uint16_t labelid; ///< id of next label to be emitted. 26 | uint16_t number; ///< last number emitted from the lexer 27 | uint8_t proclevel; ///< nesting level of procedure 28 | 29 | } parse_context_t; 30 | 31 | void emit_txt(const char *comment) 32 | { 33 | printf("%s", comment); 34 | } 35 | 36 | void emit_tokstr(const char *tokstr, uint16_t len) 37 | { 38 | for(uint16_t i=0; i> 4) & 0xF; 52 | op &= 0xF; 53 | switch(op) 54 | { 55 | case VM_JMP: 56 | printf("JMP @L%d\n",labelid); 57 | return true; 58 | case VM_JPC: 59 | printf("JPC @L%d\n",labelid); 60 | return true; 61 | case VM_CAL: 62 | printf("CAL %d @L%d\n",level,labelid); 63 | return true; 64 | default: 65 | printf("Cannot emit instruction type with label\n"); 66 | break; 67 | } 68 | return false; 69 | } 70 | 71 | void emit(parse_context_t *context, opcode_t op, opr_t aluop, uint8_t level, uint16_t imm16) 72 | { 73 | switch(op) 74 | { 75 | case VM_LIT: 76 | printf("LIT %d\n", imm16); 77 | break; 78 | case VM_OPR: // alu op 79 | switch(aluop) 80 | { 81 | case OPR_RET: 82 | printf("RET\n"); 83 | break; 84 | case OPR_ADD: 85 | printf("ADD\n"); 86 | break; 87 | case OPR_SUB: 88 | printf("SUB\n"); 89 | break; 90 | case OPR_MUL: 91 | printf("MUL\n"); 92 | break; 93 | case OPR_DIV: 94 | printf("DIV\n"); 95 | break; 96 | case OPR_NEG: 97 | printf("NEG\n"); 98 | break; 99 | case OPR_ODD: 100 | printf("ODD\n"); 101 | break; 102 | case OPR_LEQ: 103 | printf("LEQ\n"); 104 | break; 105 | case OPR_GEQ: 106 | printf("GEQ\n"); 107 | break; 108 | case OPR_LESS: 109 | printf("LES\n"); 110 | break; 111 | case OPR_GREATER: 112 | printf("GRE\n"); 113 | break; 114 | case OPR_NEQ: 115 | printf("NEQ\n"); 116 | break; 117 | case OPR_EQ: 118 | printf("EQU\n"); 119 | break; 120 | case OPR_ININT: 121 | printf("ININT\n"); 122 | break; 123 | case OPR_OUTINT: 124 | printf("OUTINT\n"); 125 | break; 126 | case OPR_INCHAR: 127 | printf("INCHAR\n"); 128 | break; 129 | case OPR_OUTCHAR: 130 | printf("OUTCHAR\n"); 131 | break; 132 | case OPR_SAR: 133 | printf("SAR\n"); 134 | break; 135 | case OPR_SHL: 136 | printf("SHL\n"); 137 | break; 138 | case OPR_SHR: 139 | printf("SHR\n"); 140 | break; 141 | default: 142 | printf("?? code:%d\n", aluop); 143 | break; 144 | } 145 | break; 146 | case VM_LOD: 147 | printf("LOD %d %d\n", level, imm16); 148 | break; 149 | case VM_STO: 150 | printf("STO %d %d\n", level, imm16); 151 | break; 152 | case VM_INT: 153 | printf("INT %d\n", imm16); 154 | break; 155 | case VM_JMP: 156 | printf("JMP $%04X\n", imm16); 157 | break; 158 | case VM_CAL: 159 | printf("CAL %d $%04X\n", level, imm16); 160 | break; 161 | case VM_JPC: 162 | printf("JPC $%04X\n", imm16); 163 | break; 164 | case VM_HALT: 165 | printf("HALT\n"); 166 | break; 167 | case VM_LODX: 168 | printf("LODX %d %d\n", level, imm16); 169 | break; 170 | case VM_STOX: 171 | printf("STOX %d %d\n", level, imm16); 172 | break; 173 | default: 174 | printf("??? code:%d\n", op); 175 | break; 176 | } 177 | } 178 | 179 | void check_tstack(const parse_context_t *context) 180 | { 181 | // check that the operation stack is empty 182 | if (context->typestack.stackptr != 0) 183 | { 184 | emit_txt("Expected typestack to be emtpy\n"); 185 | ts_dump(&context->typestack); 186 | } 187 | } 188 | 189 | // --======== LOCAL PARSER FUNCTIONS ========-- 190 | 191 | static void parse_error(const char *errstr, int16_t lineNum) 192 | { 193 | fprintf(stderr, "Line %d: %s", lineNum, errstr); 194 | } 195 | 196 | // Get the next token from the lexer 197 | static bool nextToken(parse_context_t *context) 198 | { 199 | return lexer_next(&(context->lex)); 200 | } 201 | 202 | // See if the specified token matches the one from the lexer 203 | // if it matched, save the token string in the parse context 204 | // and go to the next token. 205 | static bool match(parse_context_t *context, const token_t tok) 206 | { 207 | if (context->lex.token == tok) 208 | { 209 | context->matchstart = context->lex.tokstart; 210 | context->matchlen = context->lex.toklen; 211 | context->matchtok = context->lex.token; 212 | context->number = context->lex.number; 213 | nextToken(context); 214 | return true; 215 | } 216 | return false; 217 | } 218 | 219 | // --======== GRAMMAR/PRODUCTIONS ========-- 220 | 221 | // predeclarations 222 | bool parse_block(parse_context_t *context, uint16_t labelid); 223 | bool parse_expression(parse_context_t *context); 224 | 225 | bool parse_const_id(parse_context_t *context) 226 | { 227 | const sym_t* s = sym_lookup(&context->symtbl, context->matchstart, context->matchlen); 228 | if (s == NULL) 229 | { 230 | return false; 231 | } 232 | 233 | if (s->type == TYPE_CONST) 234 | { 235 | ts_push(&context->typestack, TYPE_CONST); 236 | emit(context, VM_LIT, 0, 0, s->offset); 237 | return true; 238 | } 239 | return false; 240 | } 241 | 242 | /** load the stack with the variable requested */ 243 | bool parse_variable_id(parse_context_t *context) 244 | { 245 | const sym_t* s = sym_lookup(&context->symtbl, context->matchstart, context->matchlen); 246 | if (s == NULL) 247 | { 248 | return false; 249 | } 250 | 251 | if ((s->type == TYPE_INT) || (s->type == TYPE_CHAR)) 252 | { 253 | // the offset is w.r.t. the base pointer 254 | // which holds T,B and the return address 255 | // so local variables are offset by an additional 3. 256 | ts_push(&context->typestack, s->type); 257 | emit(context, VM_LOD, 0, context->proclevel - s->level, s->offset + 3); 258 | return true; 259 | } 260 | return false; 261 | } 262 | 263 | /** load the stack with the array element requested */ 264 | bool parse_array_id(parse_context_t *context) 265 | { 266 | const sym_t* s = sym_lookup(&context->symtbl, context->matchstart, context->matchlen); 267 | if (s == NULL) 268 | { 269 | return false; 270 | } 271 | 272 | if (s->type == TYPE_ARRAY) 273 | { 274 | // expect [ number ] 275 | if (!match(context, TOK_LBRACKET)) 276 | { 277 | parse_error("Expected '['\n", context->lex.linenum); 278 | return false; 279 | } 280 | 281 | if (!parse_expression(context)) 282 | { 283 | parse_error("Expected an expression between '[ ]'\n", context->lex.linenum); 284 | } 285 | 286 | // check type is integer 287 | vartype_t op1 = ts_item(&context->typestack, 0); 288 | ts_pop(&context->typestack); 289 | 290 | if (!((op1 == TYPE_INT) || (op1 == TYPE_CONST))) 291 | { 292 | parse_error("Expected an INTEGER or CONST type between '[ ]'\n", context->lex.linenum); 293 | return false; 294 | } 295 | 296 | if (!match(context, TOK_RBRACKET)) 297 | { 298 | parse_error("Expected ']'\n", context->lex.linenum); 299 | return false; 300 | } 301 | 302 | ts_push(&context->typestack, s->subtype); 303 | 304 | // the offset is w.r.t. the base pointer 305 | // which holds T,B and the return address 306 | // so local variables are offset by an additional 3. 307 | emit(context, VM_LODX, 0, context->proclevel - s->level, s->offset + 3); 308 | return true; 309 | } 310 | return false; 311 | } 312 | 313 | bool parse_array_type(parse_context_t *context, uint16_t startSymbolId) 314 | { 315 | if (!match(context, TOK_LBRACKET)) 316 | { 317 | parse_error("Expected [ after array\n", context->lex.linenum); 318 | return false; 319 | } 320 | 321 | if (!match(context, TOK_NUMBER)) 322 | { 323 | parse_error("Expected NUMBER after [\n", context->lex.linenum); 324 | return false; 325 | } 326 | 327 | uint16_t arraylen = context->number; 328 | 329 | if (!match(context, TOK_RBRACKET)) 330 | { 331 | parse_error("Expected ] in array\n", context->lex.linenum); 332 | return false; 333 | } 334 | 335 | if (!match(context, TOK_OF)) 336 | { 337 | parse_error("Expected OF in array\n", context->lex.linenum); 338 | return false; 339 | } 340 | 341 | if (match(context, TOK_INTEGER) || match(context, TOK_CHAR)) 342 | { 343 | //FIXME: add array to sym table 344 | switch(context->matchtok) 345 | { 346 | case TOK_INTEGER: 347 | for(uint16_t id = startSymbolId; id < context->symtbl.Nsymbols; id++) 348 | sym_update(&context->symtbl, id, TYPE_ARRAY, TYPE_INT, arraylen); 349 | break; 350 | case TOK_CHAR: 351 | for(uint16_t id = startSymbolId; id < context->symtbl.Nsymbols; id++) 352 | sym_update(&context->symtbl, id, TYPE_ARRAY, TYPE_CHAR, arraylen); 353 | break; 354 | default: 355 | break; 356 | } 357 | 358 | return true; 359 | } 360 | else 361 | { 362 | parse_error("Expected INTEGER or CHAR in array\n", context->lex.linenum); 363 | return false; 364 | } 365 | } 366 | 367 | bool parse_factor(parse_context_t *context) 368 | { 369 | if (match(context, TOK_IDENT)) 370 | { 371 | // accept constants, variables or arrays 372 | 373 | if (parse_const_id(context)) 374 | { 375 | return true; 376 | } 377 | 378 | if (parse_variable_id(context)) 379 | { 380 | return true; 381 | } 382 | 383 | if (parse_array_id(context)) 384 | { 385 | return true; 386 | } 387 | 388 | parse_error("Incompatible type\n", context->lex.linenum); 389 | return false; 390 | } 391 | else if (match(context, TOK_NUMBER)) 392 | { 393 | // literal! 394 | ts_push(&context->typestack, TYPE_CONST); 395 | emit(context, VM_LIT, 0, 0, context->number); 396 | return true; 397 | } 398 | else if (match(context, TOK_LPAREN)) 399 | { 400 | if (!parse_expression(context)) 401 | { 402 | return false; 403 | } 404 | 405 | if (!match(context, TOK_RPAREN)) 406 | { 407 | parse_error("Expected )\n", context->lex.linenum); 408 | return false; 409 | } 410 | return true; 411 | } 412 | 413 | return false; 414 | } 415 | 416 | bool parse_term(parse_context_t *context) 417 | { 418 | if (!parse_factor(context)) 419 | { 420 | return false; 421 | } 422 | 423 | // optional * or / followed by another factor 424 | while (match(context, TOK_STAR) || match(context, TOK_SLASH)) 425 | { 426 | token_t optok = context->matchtok; 427 | 428 | if (!parse_factor(context)) 429 | { 430 | return false; 431 | } 432 | 433 | vartype_t op1 = ts_item(&context->typestack, 0); 434 | ts_pop(&context->typestack); 435 | 436 | vartype_t op2 = ts_item(&context->typestack, 0); 437 | ts_pop(&context->typestack); 438 | 439 | if (op1 == TYPE_CONST) op1 = TYPE_INT; 440 | if (op2 == TYPE_CONST) op2 = TYPE_INT; 441 | 442 | if ((op1 != TYPE_INT) || (op2 != TYPE_INT)) 443 | { 444 | parse_error("MUL/DIV expect integers as operands\n", context->lex.linenum); 445 | return false; 446 | } 447 | 448 | if (optok == TOK_STAR) 449 | { 450 | emit(context, VM_OPR, OPR_MUL,0,0); 451 | } 452 | else 453 | { 454 | emit(context, VM_OPR, OPR_DIV,0,0); 455 | } 456 | 457 | ts_push(&context->typestack, TYPE_INT); 458 | } 459 | 460 | return true; 461 | } 462 | 463 | bool parse_call(parse_context_t *context) 464 | { 465 | if (!match(context, TOK_IDENT)) 466 | { 467 | parse_error("Expected a procedure identifier\n", context->lex.linenum); 468 | return false; 469 | } 470 | 471 | sym_t *s = sym_lookup(&context->symtbl, context->matchstart, context->matchlen); 472 | if ((s==NULL) || (s->type != TYPE_PROCEDURE)) 473 | { 474 | parse_error("Cannot find procedure\n", context->lex.linenum); 475 | return false; 476 | } 477 | 478 | uint8_t opcode = VM_CAL | ((context->proclevel - s->level) << 4); 479 | 480 | emit_with_label(opcode, s->offset /* used as label id */); 481 | return true; 482 | } 483 | 484 | bool parse_assignment(parse_context_t *context, const char *identname, uint16_t identlen) 485 | { 486 | // check if the identifier is an array 487 | const sym_t* s = sym_lookup(&context->symtbl, identname, identlen); 488 | if (s == NULL) 489 | { 490 | parse_error("Cannot find symbol\n", context->lex.linenum); 491 | return false; 492 | } 493 | 494 | if (s->type == TYPE_ARRAY) 495 | { 496 | if (!match(context,TOK_LBRACKET)) 497 | { 498 | return false; 499 | } 500 | 501 | // parse the expression to get the index 502 | if (!parse_expression(context)) 503 | { 504 | return false; 505 | } 506 | 507 | if (!match(context,TOK_RBRACKET)) 508 | { 509 | return false; 510 | } 511 | 512 | if (!match(context, TOK_ASSIGN)) 513 | { 514 | parse_error("Expected :=\n", context->lex.linenum); 515 | return false; 516 | } 517 | 518 | // parse the expression to get the data to be stored 519 | if (!parse_expression(context)) 520 | { 521 | return false; 522 | } 523 | 524 | vartype_t op1 = ts_item(&context->typestack, 1); 525 | ts_pop(&context->typestack); 526 | ts_pop(&context->typestack); 527 | 528 | if ((op1 != TYPE_CONST) && (op1 != TYPE_INT)) 529 | { 530 | parse_error("Array index type must be INT or CONST\n", context->lex.linenum); 531 | return false; 532 | } 533 | 534 | emit(context, VM_STOX, 0, context->proclevel - s->level, s->offset+3); 535 | } 536 | else if (s->type == TYPE_INT) 537 | { 538 | if (!match(context, TOK_ASSIGN)) 539 | { 540 | parse_error("Expected :=\n", context->lex.linenum); 541 | return false; 542 | } 543 | 544 | if (!parse_expression(context)) 545 | { 546 | return false; 547 | } 548 | 549 | ts_pop(&context->typestack); 550 | emit(context, VM_STO, 0, context->proclevel - s->level, s->offset+3); 551 | } 552 | else 553 | { 554 | parse_error("Wrong type\n", context->lex.linenum); 555 | return false; 556 | } 557 | 558 | return true; 559 | } 560 | 561 | bool parse_expression(parse_context_t *context) 562 | { 563 | // SHR expression ? 564 | if (match(context, TOK_SHR)) 565 | { 566 | if (!parse_expression(context)) 567 | { 568 | parse_error("Expect an expression after SHR\n", context->lex.linenum); 569 | return false; 570 | } 571 | 572 | vartype_t op1 = ts_item(&context->typestack, 0); 573 | ts_pop(&context->typestack); 574 | 575 | if ((op1 != TYPE_INT) && (op1 != TYPE_CONST)) 576 | { 577 | parse_error("argument of SHR must be INTEGER or CONSTANT\n", context->lex.linenum); 578 | return false; 579 | } 580 | 581 | emit(context, VM_OPR, OPR_SHR, 0, 0); 582 | return true; 583 | } 584 | 585 | // SHL expression ? 586 | if (match(context, TOK_SHL)) 587 | { 588 | if (!parse_expression(context)) 589 | { 590 | parse_error("Expect an expression after SHL\n", context->lex.linenum); 591 | return false; 592 | } 593 | 594 | vartype_t op1 = ts_item(&context->typestack, 0); 595 | 596 | if ((op1 != TYPE_INT) && (op1 != TYPE_CONST)) 597 | { 598 | parse_error("argument of SHL must be INTEGER or CONSTANT\n", context->lex.linenum); 599 | return false; 600 | } 601 | 602 | emit(context, VM_OPR, OPR_SHL,0,0); 603 | return true; 604 | } 605 | 606 | // SAR expression ? 607 | if (match(context, TOK_SAR)) 608 | { 609 | if (!parse_expression(context)) 610 | { 611 | parse_error("Expect an expression after SAR\n", context->lex.linenum); 612 | return false; 613 | } 614 | 615 | vartype_t op1 = ts_item(&context->typestack, 0); 616 | 617 | if ((op1 != TYPE_INT) && (op1 != TYPE_CONST)) 618 | { 619 | parse_error("argument of SHL must be INTEGER or CONSTANT\n", context->lex.linenum); 620 | return false; 621 | } 622 | 623 | emit(context, VM_OPR, OPR_SAR,0,0); 624 | return true; 625 | } 626 | 627 | // check for unary + or - 628 | if (match(context, TOK_PLUS)) 629 | { 630 | // nothing. 631 | } 632 | else if (match(context, TOK_MINUS)) 633 | { 634 | vartype_t op1 = ts_item(&context->typestack, 0); 635 | 636 | if ((op1 != TYPE_INT) && (op1 != TYPE_CONST)) 637 | { 638 | parse_error("argument of unary minus must be INTEGER or CONSTANT\n", context->lex.linenum); 639 | return false; 640 | } 641 | 642 | emit(context, VM_OPR, OPR_NEG, 0, 0); 643 | } 644 | 645 | if (!parse_term(context)) 646 | { 647 | return false; 648 | } 649 | 650 | // more terms may follow 651 | while(match(context, TOK_PLUS) || match(context, TOK_MINUS)) 652 | { 653 | token_t optok = context->matchtok; 654 | 655 | if (!parse_term(context)) 656 | { 657 | return false; 658 | } 659 | 660 | if (optok == TOK_PLUS) 661 | { 662 | vartype_t op1 = ts_item(&context->typestack, 0); 663 | 664 | if ((op1 != TYPE_INT) && (op1 != TYPE_CONST)) 665 | { 666 | parse_error("argument of + be INTEGER or CONSTANT\n", context->lex.linenum); 667 | return false; 668 | } 669 | 670 | vartype_t op2 = ts_item(&context->typestack, 1); 671 | 672 | if ((op2 != TYPE_INT) && (op2 != TYPE_CONST)) 673 | { 674 | parse_error("argument of + be INTEGER or CONSTANT\n", context->lex.linenum); 675 | return false; 676 | } 677 | 678 | ts_pop(&context->typestack); 679 | 680 | emit(context, VM_OPR, OPR_ADD, 0,0); 681 | } 682 | else 683 | { 684 | vartype_t op1 = ts_item(&context->typestack, 0); 685 | 686 | if ((op1 != TYPE_INT) && (op1 != TYPE_CONST)) 687 | { 688 | parse_error("argument of - must be INTEGER or CONSTANT\n", context->lex.linenum); 689 | return false; 690 | } 691 | 692 | vartype_t op2 = ts_item(&context->typestack, 1); 693 | 694 | if ((op2 != TYPE_INT) && (op2 != TYPE_CONST)) 695 | { 696 | parse_error("argument of - must be INTEGER or CONSTANT\n", context->lex.linenum); 697 | return false; 698 | } 699 | 700 | ts_pop(&context->typestack); 701 | 702 | emit(context, VM_OPR, OPR_SUB, 0,0); 703 | } 704 | } 705 | 706 | return true; 707 | } 708 | 709 | bool parse_condition(parse_context_t *context) 710 | { 711 | if (match(context, TOK_ODD)) 712 | { 713 | if (!parse_expression(context)) 714 | { 715 | parse_error("Expected a statement\n", context->lex.linenum); 716 | return false; 717 | } 718 | 719 | vartype_t op1 = ts_item(&context->typestack, 0); 720 | 721 | if ((op1 != TYPE_INT) && (op1 != TYPE_CONST)) 722 | { 723 | parse_error("argument of ODD must be INTEGER or CONSTANT\n", context->lex.linenum); 724 | return false; 725 | } 726 | 727 | emit(context, VM_OPR, OPR_ODD, 0, 0); 728 | 729 | return true; // accept condition. 730 | } 731 | 732 | // try EXPRESSION op EXPRESSION 733 | if (!parse_expression(context)) 734 | { 735 | parse_error("Expected an expression in condition\n", context->lex.linenum); 736 | return false; 737 | } 738 | 739 | token_t condition; 740 | if (match(context, TOK_EQUAL)) 741 | { 742 | condition = context->matchtok; 743 | } 744 | else if (match(context, TOK_HASH)) 745 | { 746 | condition = context->matchtok; 747 | } 748 | else if (match(context, TOK_LESS)) 749 | { 750 | condition = context->matchtok; 751 | } 752 | else if (match(context, TOK_LEQ)) 753 | { 754 | condition = context->matchtok; 755 | } 756 | else if (match(context, TOK_GREATER)) 757 | { 758 | condition = context->matchtok; 759 | } 760 | else if (match(context, TOK_GEQ)) 761 | { 762 | condition = context->matchtok; 763 | } 764 | else 765 | { 766 | parse_error("Expected condition operator\n", context->lex.linenum); 767 | return false; 768 | } 769 | 770 | if (!parse_expression(context)) 771 | { 772 | parse_error("Expected an expression in condition\n", context->lex.linenum); 773 | return false; 774 | } 775 | 776 | vartype_t op1 = ts_item(&context->typestack, 0); 777 | vartype_t op2 = ts_item(&context->typestack, 1); 778 | 779 | ts_pop(&context->typestack); 780 | ts_pop(&context->typestack); 781 | 782 | if (op1 == TYPE_CONST) op1 = TYPE_INT; 783 | if (op2 == TYPE_CONST) op2 = TYPE_INT; 784 | 785 | if (op1 != op2) 786 | { 787 | parse_error("Types must be identical\n", context->lex.linenum); 788 | return false; 789 | } 790 | 791 | if (condition == TOK_EQUAL) 792 | { 793 | emit(context, VM_OPR, OPR_EQ,0,0); 794 | } 795 | else if (condition == TOK_HASH) 796 | { 797 | emit(context, VM_OPR, OPR_NEQ,0,0); 798 | } 799 | else if (condition == TOK_GEQ) 800 | { 801 | emit(context, VM_OPR, OPR_GEQ,0,0); 802 | } 803 | else if (condition == TOK_LEQ) 804 | { 805 | emit(context, VM_OPR, OPR_LEQ,0,0); 806 | } 807 | else if (condition == TOK_LESS) 808 | { 809 | emit(context, VM_OPR, OPR_LESS,0,0); 810 | } 811 | else if (condition == TOK_GREATER) 812 | { 813 | emit(context, VM_OPR, OPR_GREATER,0,0); 814 | } 815 | else 816 | { 817 | parse_error("Condition code not matched\n", context->lex.linenum); 818 | return false; 819 | } 820 | 821 | ts_push(&context->typestack, TYPE_INT); // condition result 822 | return true; 823 | } 824 | 825 | bool parse_statement(parse_context_t *context) 826 | { 827 | // IDENT := .. 828 | if (match(context, TOK_IDENT)) 829 | { 830 | if (!parse_assignment(context, context->matchstart, context->matchlen)) 831 | return false; 832 | } 833 | // CALL IDENT 834 | else if (match(context, TOK_CALL)) 835 | { 836 | if (!parse_call(context)) 837 | return false; 838 | } 839 | // ? IDENT 840 | else if (match(context, TOK_QUESTION)) 841 | { 842 | if (!match(context, TOK_IDENT)) 843 | { 844 | parse_error("Expected IDENT\n", context->lex.linenum); 845 | return false; 846 | } 847 | 848 | sym_t *s = sym_lookup(&context->symtbl, context->matchstart, context->matchlen); 849 | if (s == NULL) 850 | { 851 | parse_error("Cannot find variable\n", context->lex.linenum); 852 | return false; 853 | } 854 | 855 | switch(s->type) 856 | { 857 | case TYPE_INT: 858 | emit(context, VM_OPR, OPR_ININT,0,0); // read value onto stack 859 | break; 860 | case TYPE_CHAR: 861 | emit(context, VM_OPR, OPR_INCHAR,0,0); // read value onto stack 862 | break; 863 | default: 864 | parse_error("Expected INT or CHAR type\n", context->lex.linenum); 865 | return false; 866 | } 867 | emit(context, VM_STO,0, context->proclevel - s->level, s->offset+3); 868 | } 869 | // ! expression 870 | else if (match(context, TOK_EXCLAMATION)) 871 | { 872 | if (!parse_expression(context)) 873 | { 874 | parse_error("! expression invalid\n", context->lex.linenum); 875 | } 876 | 877 | vartype_t op = ts_item(&context->typestack, 0); 878 | ts_pop(&context->typestack); 879 | 880 | switch(op) 881 | { 882 | case TYPE_INT: 883 | case TYPE_CONST: 884 | emit(context, VM_OPR, OPR_OUTINT,0,0); // read value onto stack 885 | break; 886 | case TYPE_CHAR: 887 | emit(context, VM_OPR, OPR_OUTCHAR,0,0); // read value onto stack 888 | break; 889 | default: 890 | parse_error("Expected INT, CHAR or CONST type\n", context->lex.linenum); 891 | return false; 892 | } 893 | 894 | while(match(context, TOK_COMMA)) 895 | { 896 | if (!parse_expression(context)) 897 | { 898 | parse_error("! expression invalid\n", context->lex.linenum); 899 | } 900 | 901 | emit(context, VM_LIT, 0,0,32); 902 | emit(context, VM_OPR, OPR_OUTCHAR,0,0); 903 | 904 | vartype_t op = ts_item(&context->typestack, 0); 905 | ts_pop(&context->typestack); 906 | 907 | switch(op) 908 | { 909 | case TYPE_INT: 910 | case TYPE_CONST: 911 | emit(context, VM_OPR, OPR_OUTINT,0,0); // read value onto stack 912 | break; 913 | case TYPE_CHAR: 914 | emit(context, VM_OPR, OPR_OUTCHAR,0,0); // read value onto stack 915 | break; 916 | default: 917 | parse_error("Expected INT, CHAR or CONST type\n", context->lex.linenum); 918 | return false; 919 | } 920 | } 921 | 922 | // line feed 923 | emit(context, VM_LIT, 0,0,10); 924 | emit(context, VM_OPR, OPR_OUTCHAR,0,0); 925 | emit(context, VM_LIT, 0,0,13); 926 | emit(context, VM_OPR, OPR_OUTCHAR,0,0); 927 | } 928 | // BEGIN .. END 929 | else if (match(context, TOK_BEGIN)) 930 | { 931 | if (!parse_statement(context)) 932 | return false; 933 | 934 | // optional statements 935 | while(match(context, TOK_SEMICOL)) 936 | { 937 | if (!parse_statement(context)) 938 | return false; 939 | } 940 | 941 | if (!match(context, TOK_END)) 942 | { 943 | parse_error("Expected END\n", context->lex.linenum); 944 | return false; 945 | } 946 | } 947 | // IF .. THEN .. ELSE 948 | else if (match(context, TOK_IF)) 949 | { 950 | emit_txt("; IF\n"); 951 | if (!parse_condition(context)) 952 | { 953 | parse_error("Expected a condition in IF statement\n", context->lex.linenum); 954 | return false; 955 | } 956 | 957 | ts_pop(&context->typestack); 958 | 959 | // jump over the THEN code if condition is false 960 | // the jump address needs a fixup 961 | uint16_t jpc_label = context->labelid++; 962 | if (!emit_with_label(VM_JPC, jpc_label)) 963 | return false; 964 | 965 | emit_txt("; THEN\n"); 966 | if (!match(context, TOK_THEN)) 967 | { 968 | parse_error("Expected THEN in IF statement\n", context->lex.linenum); 969 | return false; 970 | } 971 | if (!parse_statement(context)) 972 | { 973 | parse_error("Expected statement after THEN\n", context->lex.linenum); 974 | return false; 975 | } 976 | 977 | // jump over the ELSE statements. 978 | uint16_t jmp_label = context->labelid++; 979 | if (!emit_with_label(VM_JMP, jmp_label)) 980 | return false; 981 | 982 | emit_label(jpc_label); 983 | 984 | // optional else statement 985 | if (match(context, TOK_ELSE)) 986 | { 987 | emit_txt("; ELSE\n"); 988 | if (!parse_statement(context)) 989 | { 990 | parse_error("Expected statement after ELSE\n", context->lex.linenum); 991 | return false; 992 | } 993 | } 994 | emit_label(jmp_label); 995 | emit_txt("; END IF\n"); 996 | } 997 | // WHILE .. DO 998 | else if (match(context, TOK_WHILE)) 999 | { 1000 | // save the address at this point 1001 | // so we can jump back to it at the end of 1002 | // the while block 1003 | 1004 | uint16_t jmp_label = context->labelid++; 1005 | emit_txt("; WHILE\n"); 1006 | emit_label(jmp_label); 1007 | 1008 | if (!parse_condition(context)) 1009 | { 1010 | parse_error("Expected a condition in IF statement\n", context->lex.linenum); 1011 | return false; 1012 | } 1013 | 1014 | ts_pop(&context->typestack); 1015 | 1016 | // forward jump 1017 | uint16_t jpc_label = context->labelid++; 1018 | emit_with_label(VM_JPC, jpc_label); 1019 | 1020 | if (!match(context, TOK_DO)) 1021 | { 1022 | parse_error("Expected DO in WHILE statement\n", context->lex.linenum); 1023 | return false; 1024 | } 1025 | if (!parse_statement(context)) 1026 | { 1027 | parse_error("Expected statement after DO\n", context->lex.linenum); 1028 | return false; 1029 | } 1030 | emit_with_label(VM_JMP, jmp_label); 1031 | emit_label(jpc_label); 1032 | emit_txt("; END WHILE\n"); 1033 | } 1034 | // FOR .. TO/DOWNTO .. DO 1035 | else if (match(context, TOK_FOR)) 1036 | { 1037 | emit_txt("; FOR\n"); 1038 | if (!match(context, TOK_IDENT)) 1039 | { 1040 | parse_error("Expected an identifier after FOR\n", context->lex.linenum); 1041 | return false; 1042 | } 1043 | 1044 | sym_t *ident = sym_lookup(&context->symtbl, context->matchstart, context->matchlen); 1045 | 1046 | if (ident == NULL) 1047 | { 1048 | parse_error("Cannot find symbol\n", context->lex.linenum); 1049 | return false; 1050 | } 1051 | 1052 | if (!match(context, TOK_ASSIGN)) 1053 | { 1054 | parse_error("Expected := after FOR \n", context->lex.linenum); 1055 | return false; 1056 | } 1057 | 1058 | // init expression 1059 | if (!parse_expression(context)) 1060 | { 1061 | parse_error("Expected an identifier after FOR :=\n", context->lex.linenum); 1062 | return false; 1063 | } 1064 | 1065 | vartype_t expr = ts_item(&context->typestack, 0); 1066 | ts_pop(&context->typestack); 1067 | 1068 | if (expr == TYPE_CONST) 1069 | { 1070 | expr = TYPE_INT; 1071 | } 1072 | 1073 | if (ident->type == expr) 1074 | { 1075 | emit(context, VM_STO, 0, context->proclevel - ident->level, ident->offset+3); 1076 | } 1077 | else 1078 | { 1079 | parse_error("Type mismatch after FOR\n", context->lex.linenum); 1080 | return false; 1081 | } 1082 | 1083 | if (!match(context, TOK_TO)) 1084 | { 1085 | parse_error("Expected TO in FOR\n", context->lex.linenum); 1086 | return false; 1087 | } 1088 | 1089 | uint16_t jmp_label = context->labelid++; 1090 | emit_label(jmp_label); 1091 | emit_txt("; FOR check expression\n"); 1092 | 1093 | // check expression 1094 | uint16_t exit_label = context->labelid++; 1095 | emit(context, VM_LOD, 0, context->proclevel - ident->level, ident->offset+3); 1096 | 1097 | if (!parse_expression(context)) 1098 | { 1099 | parse_error("Expected an identifier after TO\n", context->lex.linenum); 1100 | return false; 1101 | } 1102 | 1103 | expr = ts_item(&context->typestack, 0); 1104 | ts_pop(&context->typestack); 1105 | 1106 | if (expr == TYPE_CONST) 1107 | expr = TYPE_INT; 1108 | 1109 | if (ident->type != expr) 1110 | { 1111 | parse_error("Type mismatch after TO\n", context->lex.linenum); 1112 | return false; 1113 | } 1114 | 1115 | emit(context, VM_OPR, OPR_LEQ, 0,0); 1116 | emit_with_label(VM_JPC, exit_label); 1117 | 1118 | if (!match(context, TOK_DO)) 1119 | { 1120 | parse_error("Expected DO in FOR\n", context->lex.linenum); 1121 | return false; 1122 | } 1123 | 1124 | emit_txt("; FOR DO expression\n"); 1125 | 1126 | if (!parse_statement(context)) 1127 | { 1128 | parse_error("Expected an statement in FOR loop\n", context->lex.linenum); 1129 | return false; 1130 | } 1131 | 1132 | // increment the loop counter 1133 | emit(context, VM_LOD, 0, context->proclevel - ident->level, ident->offset+3); 1134 | emit(context, VM_LIT, 0,0,1); 1135 | emit(context, VM_OPR, OPR_ADD,0,0); 1136 | emit(context, VM_STO, 0, context->proclevel - ident->level, ident->offset+3); 1137 | emit_with_label(VM_JMP, jmp_label); 1138 | 1139 | emit_label(exit_label); 1140 | emit_txt("; end FOR\n"); 1141 | } 1142 | 1143 | // check that the operation stack is empty 1144 | check_tstack(context); 1145 | 1146 | // everything is optional, so we always return true. 1147 | return true; 1148 | } 1149 | 1150 | bool parse_const(parse_context_t *context) 1151 | { 1152 | if (!match(context, TOK_IDENT)) 1153 | { 1154 | parse_error("Expected IDENT\n", context->lex.linenum); 1155 | return false; 1156 | } 1157 | 1158 | const char *ident = context->matchstart; 1159 | uint16_t identlen = context->matchlen; 1160 | 1161 | if (!match(context, TOK_EQUAL)) 1162 | { 1163 | parse_error("Expected =\n", context->lex.linenum); 1164 | return false; 1165 | } 1166 | 1167 | if (!match(context, TOK_NUMBER)) 1168 | { 1169 | parse_error("Expected NUMBER\n", context->lex.linenum); 1170 | return false; 1171 | } 1172 | 1173 | sym_add(&context->symtbl, ident, identlen); 1174 | sym_set_const(&context->symtbl, context->symtbl.Nsymbols-1, context->number); 1175 | 1176 | while(match(context, TOK_COMMA)) 1177 | { 1178 | if (!match(context, TOK_IDENT)) 1179 | { 1180 | parse_error("Expected IDENT\n", context->lex.linenum); 1181 | return false; 1182 | } 1183 | 1184 | ident = context->matchstart; 1185 | identlen = context->matchlen; 1186 | 1187 | if (!match(context, TOK_EQUAL)) 1188 | { 1189 | parse_error("Expected =\n", context->lex.linenum); 1190 | return false; 1191 | } 1192 | if (!match(context, TOK_NUMBER)) 1193 | { 1194 | parse_error("Exptected NUMBER\n", context->lex.linenum); 1195 | return false; 1196 | } 1197 | 1198 | sym_add(&context->symtbl, ident, identlen); 1199 | sym_set_const(&context->symtbl, context->symtbl.Nsymbols-1, context->number); 1200 | } 1201 | 1202 | if (!match(context, TOK_SEMICOL)) 1203 | { 1204 | parse_error("Expected ;\n", context->lex.linenum); 1205 | return false; 1206 | } 1207 | return true; 1208 | } 1209 | 1210 | bool parse_var(parse_context_t *context) 1211 | { 1212 | if (!match(context, TOK_IDENT)) 1213 | { 1214 | parse_error("Expected IDENT\n", context->lex.linenum); 1215 | return false; 1216 | } 1217 | 1218 | const char *ident = context->matchstart; 1219 | uint16_t identlen = context->matchlen; 1220 | 1221 | // Add the symbol to the symbol table 1222 | // at this point, we don't know the type, 1223 | // so we specify TYPE_NONE. It will be 1224 | // changed/updated at the end of this function. 1225 | 1226 | uint16_t startSymbolId = context->symtbl.Nsymbols; 1227 | sym_add(&context->symtbl, ident, identlen); 1228 | 1229 | while(match(context, TOK_COMMA)) 1230 | { 1231 | if (!match(context, TOK_IDENT)) 1232 | { 1233 | parse_error("Expected IDENT\n", context->lex.linenum); 1234 | return false; 1235 | } 1236 | 1237 | ident = context->matchstart; 1238 | identlen = context->matchlen; 1239 | sym_add(&context->symtbl, ident, identlen); 1240 | } 1241 | 1242 | if (!match(context, TOK_COLON)) 1243 | { 1244 | parse_error("Expected :\n", context->lex.linenum); 1245 | return false; 1246 | } 1247 | 1248 | // parse the type information 1249 | 1250 | if (match(context, TOK_ARRAY)) 1251 | { 1252 | if (!parse_array_type(context, startSymbolId)) 1253 | { 1254 | return false; 1255 | } 1256 | } 1257 | else if (match(context, TOK_CHAR) || match(context, TOK_INTEGER)) 1258 | { 1259 | switch(context->matchtok) 1260 | { 1261 | case TOK_CHAR: 1262 | for(uint16_t id = startSymbolId; id < context->symtbl.Nsymbols; id++) 1263 | sym_update(&context->symtbl, id, TYPE_CHAR, TYPE_NONE, 1); 1264 | break; 1265 | case TOK_INTEGER: 1266 | for(uint16_t id = startSymbolId; id < context->symtbl.Nsymbols; id++) 1267 | sym_update(&context->symtbl, id, TYPE_INT, TYPE_NONE, 1); 1268 | break; 1269 | default: 1270 | // we should never end up here. 1271 | parse_error("parse_var: internal error\n", context->lex.linenum); 1272 | return false; 1273 | } 1274 | } 1275 | else 1276 | { 1277 | parse_error("Expected type name CHAR, INTEGER or ARRAY\n", context->lex.linenum); 1278 | return false; 1279 | } 1280 | 1281 | if (!match(context, TOK_SEMICOL)) 1282 | { 1283 | parse_error("Expected ;\n", context->lex.linenum); 1284 | return false; 1285 | } 1286 | 1287 | return true; 1288 | } 1289 | 1290 | bool parse_procedure(parse_context_t *context) 1291 | { 1292 | if (!match(context, TOK_IDENT)) 1293 | { 1294 | parse_error("Expected IDENT\n", context->lex.linenum); 1295 | return false; 1296 | } 1297 | 1298 | const char *procname = context->matchstart; 1299 | uint16_t procnamelen = context->matchlen; 1300 | 1301 | emit_txt("; PROCEDURE "); 1302 | emit_tokstr(procname, procnamelen); 1303 | emit_txt("\n"); 1304 | 1305 | if (!sym_add(&context->symtbl, procname, procnamelen)) 1306 | return false; 1307 | 1308 | uint16_t proc_label = context->labelid++; 1309 | 1310 | // add label id to the procedure symbol 1311 | //context->symtbl.syms[context->symtbl.Nsymbols-1].offset = proc_label; 1312 | sym_set_procedure(&context->symtbl, context->symtbl.Nsymbols-1, proc_label); 1313 | 1314 | sym_enter(&context->symtbl); 1315 | 1316 | if (context->proclevel == 15) 1317 | { 1318 | parse_error("Too many nested procedures\n", context->lex.linenum); 1319 | return false; 1320 | } 1321 | 1322 | context->proclevel++; 1323 | 1324 | if (!match(context, TOK_SEMICOL)) 1325 | { 1326 | parse_error("Expected ;\n", context->lex.linenum); 1327 | return false; 1328 | } 1329 | 1330 | if (!parse_block(context, proc_label)) 1331 | { 1332 | return false; 1333 | } 1334 | 1335 | if (!match(context, TOK_SEMICOL)) 1336 | { 1337 | parse_error("Expected ;", context->lex.linenum); 1338 | return false; 1339 | } 1340 | 1341 | emit(context, VM_OPR, OPR_RET,0,0); 1342 | 1343 | sym_dump(&context->symtbl); 1344 | 1345 | emit_txt("; ENDPROC\n\n"); 1346 | 1347 | sym_leave(&context->symtbl); 1348 | context->proclevel--; 1349 | return true; 1350 | } 1351 | 1352 | bool parse_block(parse_context_t *context, uint16_t labelid) 1353 | { 1354 | // zero or more const 1355 | while (match(context, TOK_CONST)) 1356 | { 1357 | if (!parse_const(context)) 1358 | return false; 1359 | } 1360 | 1361 | // zero or more var 1362 | while (match(context, TOK_VAR)) 1363 | { 1364 | if (!parse_var(context)) 1365 | return false; 1366 | } 1367 | 1368 | // zero or more procedures 1369 | while(match(context, TOK_PROCEDURE)) 1370 | { 1371 | if (!parse_procedure(context)) 1372 | return false; 1373 | } 1374 | 1375 | emit_label(labelid); 1376 | 1377 | // create space for local variables 1378 | uint16_t space_required = sym_get_local_space(&context->symtbl); 1379 | emit_txt("INT "); 1380 | printf("%d\n", space_required+3); // 3 for local call pointers? 1381 | 1382 | // one statement 1383 | if (!parse_statement(context)) 1384 | return false; 1385 | 1386 | // check that the operation stack is empty 1387 | check_tstack(context); 1388 | 1389 | return true; 1390 | } 1391 | 1392 | bool parse(char *src) 1393 | { 1394 | parse_context_t context; 1395 | context.matchlen = 0; 1396 | context.matchstart = src; 1397 | context.proclevel = 0; 1398 | context.labelid = 0; 1399 | 1400 | lexer_init(&context.lex, src); 1401 | sym_init(&context.symtbl); 1402 | ts_init(&context.typestack); 1403 | 1404 | // get first token 1405 | if (!nextToken(&context)) 1406 | { 1407 | return false; 1408 | } 1409 | 1410 | 1411 | uint16_t entry_label = context.labelid++; 1412 | emit_with_label(VM_JMP, entry_label); 1413 | 1414 | // parse program 1415 | if (!parse_block(&context, entry_label)) 1416 | { 1417 | parse_error("Parse error\n", context.lex.linenum); 1418 | return false; 1419 | } 1420 | 1421 | // expect '.' 1422 | if (!match(&context, TOK_PERIOD)) 1423 | { 1424 | parse_error("Expected .\n", context.lex.linenum); 1425 | return false; 1426 | } 1427 | 1428 | emit(&context,VM_HALT,0,0,0); 1429 | 1430 | sym_dump(&context.symtbl); 1431 | 1432 | return true; 1433 | } 1434 | -------------------------------------------------------------------------------- /src/parser.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | PL/0 parser 4 | N.A. Moseley 2021 5 | 6 | */ 7 | 8 | #pragma once 9 | #include 10 | #include 11 | 12 | 13 | bool parse(char *src); -------------------------------------------------------------------------------- /src/ptypes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef enum 4 | { 5 | TYPE_ERROR, 6 | TYPE_NONE, 7 | TYPE_CONST, 8 | TYPE_INT, 9 | TYPE_CHAR, 10 | TYPE_ARRAY, 11 | TYPE_PROCEDURE 12 | } vartype_t; 13 | -------------------------------------------------------------------------------- /src/symtbl.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "symtbl.h" 4 | 5 | bool sym_init(symtbl_t *tbl) 6 | { 7 | tbl->level = 0; 8 | tbl->offset = 0; 9 | tbl->Nsymbols = 0; 10 | } 11 | 12 | static void cpy(char *dst, const char *src, size_t len) 13 | { 14 | for(uint8_t idx=0; idx= MAX_SYMS) 35 | return false; // out of range 36 | 37 | sym_t *sym = &tbl->syms[id]; 38 | 39 | sym->subtype = subtp; 40 | sym->type = tp; 41 | sym->size = size; 42 | 43 | // only variables are stored in the local stack context. 44 | // type NONE is a temporary type, which will be replaced 45 | // later in the parse cycle. 46 | if ((tp == TYPE_INT) || (tp == TYPE_CHAR) || (tp == TYPE_ARRAY) || (tp == TYPE_NONE)) 47 | { 48 | sym->offset = tbl->offset; 49 | tbl->offset += size; 50 | } 51 | else 52 | { 53 | sym->offset = 0; 54 | } 55 | 56 | return true; 57 | } 58 | 59 | bool sym_set_const(symtbl_t *tbl, const uint16_t id, const uint16_t value) 60 | { 61 | if (id >= MAX_SYMS) 62 | return false; // out of range 63 | 64 | sym_t *sym = &tbl->syms[id]; 65 | 66 | sym->subtype = TYPE_NONE; 67 | sym->type = TYPE_CONST; 68 | sym->size = 0; 69 | sym->offset = value; 70 | 71 | return true; 72 | } 73 | 74 | bool sym_set_procedure(symtbl_t *tbl, const uint16_t id, const uint16_t label) 75 | { 76 | if (id >= MAX_SYMS) 77 | return false; // out of range 78 | 79 | sym_t *sym = &tbl->syms[id]; 80 | 81 | sym->subtype = TYPE_NONE; 82 | sym->type = TYPE_PROCEDURE; 83 | sym->size = 0; 84 | sym->offset = label; 85 | 86 | return true; 87 | } 88 | 89 | bool sym_add(symtbl_t *tbl, const char *name, uint16_t namelen) 90 | { 91 | //FIXME: check if the symbol is already in the table? 92 | sym_t *newsym = &tbl->syms[tbl->Nsymbols]; 93 | 94 | newsym->level = tbl->level; 95 | newsym->subtype = TYPE_NONE; 96 | newsym->type = TYPE_NONE; 97 | newsym->size = 0; 98 | newsym->offset = 0; 99 | newsym->name = (char*)malloc(namelen); 100 | newsym->namelen= namelen; 101 | cpy(newsym->name, name, namelen); 102 | 103 | tbl->Nsymbols++; 104 | 105 | return true; 106 | } 107 | 108 | sym_t* sym_lookup(symtbl_t *tbl, const char *name, uint16_t namelen) 109 | { 110 | // backward search for the symbol 111 | uint16_t idx = tbl->Nsymbols; 112 | 113 | bool found = false; 114 | while(idx != 0) 115 | { 116 | idx--; 117 | if (tbl->syms[idx].namelen == namelen) 118 | { 119 | if (cmp(tbl->syms[idx].name, name, namelen)) 120 | { 121 | // match! 122 | found = true; 123 | break; 124 | } 125 | } 126 | } 127 | 128 | if (found) 129 | { 130 | return &tbl->syms[idx]; 131 | } 132 | else 133 | { 134 | return NULL; 135 | } 136 | } 137 | 138 | bool sym_enter(symtbl_t *tbl) 139 | { 140 | tbl->offset=0; 141 | tbl->level++; 142 | if (tbl->level > 15) 143 | { 144 | // error! too many levels. 145 | } 146 | return true; 147 | } 148 | 149 | void free_sym(sym_t *s) 150 | { 151 | free(s->name); 152 | } 153 | 154 | bool sym_leave(symtbl_t *tbl) 155 | { 156 | // do backward search to find previous stack level 157 | uint16_t idx = tbl->Nsymbols; 158 | 159 | if (idx == 0) 160 | { 161 | // can't leave top-level context 162 | return false; 163 | } 164 | 165 | while(idx != 0) 166 | { 167 | idx--; 168 | if (tbl->syms[idx].level == tbl->level) 169 | { 170 | free_sym(&(tbl->syms[idx])); 171 | tbl->Nsymbols--; 172 | } 173 | else 174 | { 175 | // new context found, so exit 176 | break; 177 | } 178 | }; 179 | 180 | if (tbl->level > 0) 181 | { 182 | tbl->level--; 183 | } 184 | else 185 | { 186 | // error! 187 | return false; 188 | } 189 | return true; 190 | } 191 | 192 | void sym_dump(symtbl_t *tbl) 193 | { 194 | printf("; Dumping symbol table\n"); 195 | for(uint16_t idx=0; idxNsymbols; idx++) 196 | { 197 | printf("; "); 198 | const sym_t *s = &(tbl->syms[idx]); 199 | for(uint8_t L=0; Llevel; L++) 200 | { 201 | putchar(' '); 202 | } 203 | for(uint8_t i=0; inamelen; i++) 204 | { 205 | putchar(s->name[i]); 206 | } 207 | 208 | const uint8_t remainder = 20-s->namelen; 209 | for(uint8_t i=0; itype) 215 | { 216 | case TYPE_CONST: 217 | printf("CONST %d\n", s->offset); 218 | break; 219 | case TYPE_INT: 220 | printf("INT %d\n", s->offset); 221 | break; 222 | case TYPE_CHAR: 223 | printf("CHAR %d (%c)\n", s->offset, (char)s->offset); 224 | break; 225 | case TYPE_ARRAY: 226 | printf("ARRAY %d [%d]\n", s->offset, s->size); 227 | break; 228 | case TYPE_PROCEDURE: 229 | printf("PROC @L%d\n", s->offset); 230 | break; 231 | default: 232 | printf("?????"); 233 | break; 234 | } 235 | } 236 | } 237 | 238 | uint16_t sym_numvariables(symtbl_t *tbl) 239 | { 240 | uint16_t idx = tbl->Nsymbols; 241 | uint16_t count = 0; 242 | while(idx > 0) 243 | { 244 | idx--; 245 | if (tbl->syms[idx].level != tbl->level) 246 | { 247 | return count; 248 | } 249 | else 250 | { 251 | if (tbl->syms[idx].type == TYPE_INT) 252 | count++; 253 | } 254 | } 255 | return count; 256 | } 257 | 258 | uint16_t sym_get_local_space(symtbl_t *tbl) 259 | { 260 | uint16_t idx = tbl->Nsymbols; 261 | uint16_t space = 0; 262 | while(idx > 0) 263 | { 264 | idx--; 265 | if (tbl->syms[idx].level != tbl->level) 266 | { 267 | return space; 268 | } 269 | else 270 | { 271 | if ((tbl->syms[idx].type == TYPE_INT) || (tbl->syms[idx].type == TYPE_CHAR) 272 | || (tbl->syms[idx].type == TYPE_ARRAY)) 273 | { 274 | space += tbl->syms[idx].size; 275 | } 276 | } 277 | } 278 | return space; 279 | } 280 | 281 | void sym_settype(symtbl_t *tbl, const vartype_t tp, const vartype_t subtp, const uint16_t size) 282 | { 283 | uint16_t idx = tbl->Nsymbols; 284 | while(idx > 0) 285 | { 286 | idx--; 287 | if (tbl->syms[idx].type == TYPE_NONE) 288 | { 289 | tbl->syms[idx].type = tp; 290 | tbl->syms[idx].subtype = subtp; 291 | tbl->syms[idx].size = size; 292 | } 293 | else 294 | { 295 | return; 296 | } 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /src/symtbl.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Symbol table 4 | 5 | */ 6 | 7 | #pragma once 8 | #include 9 | #include 10 | #include 11 | #include "ptypes.h" 12 | 13 | /** symbol table entry */ 14 | typedef struct 15 | { 16 | vartype_t type; // symbol type 17 | vartype_t subtype; // sub type (arrays only) 18 | uint8_t level; // nesting level 19 | uint16_t offset; // offset into local stack, or label id 20 | uint16_t size; // size of variable in 16-bit words 21 | char *name; // name of symbol 22 | uint16_t namelen; // length of name 23 | } sym_t; 24 | 25 | #define MAX_SYMS 200 26 | 27 | /** symbol table */ 28 | typedef struct 29 | { 30 | uint16_t Nsymbols; ///< number of symbols in the table 31 | uint8_t level; ///< current max. nexting level of the table 32 | uint16_t offset; ///< current offset into local stack 33 | sym_t syms[MAX_SYMS]; ///< storage for symbols 34 | } symtbl_t; 35 | 36 | bool sym_init(symtbl_t *tbl); 37 | 38 | /** add a symbol to the symbol table in name only. 39 | * no space will be allocated on the stack until 40 | * sym_allocate is called 41 | * 42 | * the type will be set to TYPE_NONE. 43 | * 44 | * fill in the rest of the symbol information using 45 | * sym_settype and symbol offset is set to zero. 46 | * */ 47 | bool sym_add(symtbl_t *tbl, const char *name, uint16_t namelen); 48 | 49 | /** enter a new procedure/block level - increments level */ 50 | bool sym_enter(symtbl_t *tbl); 51 | 52 | /** leave a new procedure/block level - decrements level */ 53 | bool sym_leave(symtbl_t *tbl); 54 | 55 | /** set type information and update the offsets */ 56 | bool sym_update(symtbl_t *tbl, const uint16_t id, const vartype_t tp, const vartype_t subtp, 57 | const uint16_t size); 58 | 59 | /** set type information of a constant */ 60 | bool sym_set_const(symtbl_t *tbl, const uint16_t id, const uint16_t value); 61 | 62 | /** set type information of a procedure */ 63 | bool sym_set_procedure(symtbl_t *tbl, const uint16_t id, const uint16_t label); 64 | 65 | /** calculate the number of local variables */ 66 | uint16_t sym_numvariables(symtbl_t *tbl); 67 | 68 | /** calculate the number of 16-bit cells for local variables */ 69 | uint16_t sym_get_local_space(symtbl_t *tbl); 70 | 71 | sym_t* sym_lookup(symtbl_t *tbl, const char *name, uint16_t namelen); 72 | 73 | void sym_dump(symtbl_t *tbl); -------------------------------------------------------------------------------- /src/typestack.c: -------------------------------------------------------------------------------- 1 | #include "typestack.h" 2 | 3 | void ts_error(const char *txt) 4 | { 5 | fprintf(stderr,"typestack: %s\n",txt); 6 | } 7 | 8 | void ts_init(typestack_t *stk) 9 | { 10 | stk->stackptr = 0; 11 | } 12 | 13 | bool ts_push(typestack_t *stk, vartype_t t) 14 | { 15 | if (stk == NULL) 16 | return false; 17 | 18 | if (stk->stackptr >= STACKMAXDEPTH) 19 | { 20 | ts_error("stack overflow!"); 21 | return false; 22 | } 23 | else 24 | { 25 | stk->stack[stk->stackptr] = t; 26 | stk->stackptr++; 27 | return true; 28 | } 29 | } 30 | 31 | bool ts_pop(typestack_t *stk) 32 | { 33 | if (stk == NULL) 34 | return false; 35 | 36 | if (stk->stackptr == 0) 37 | { 38 | ts_error("stack underflow!"); 39 | return false; 40 | } 41 | else 42 | { 43 | stk->stackptr--; 44 | return true; 45 | } 46 | } 47 | 48 | vartype_t ts_item(const typestack_t *stk, uint8_t offset) 49 | { 50 | if (stk == NULL) 51 | return TYPE_ERROR; 52 | 53 | if ((stk->stackptr - offset - 1) < STACKMAXDEPTH) 54 | { 55 | return stk->stack[stk->stackptr - offset - 1]; 56 | } 57 | else 58 | { 59 | ts_error("stack item under/overflow!"); 60 | return TYPE_ERROR; 61 | } 62 | } 63 | 64 | void ts_dump(const typestack_t *stk) 65 | { 66 | uint8_t idx = stk->stackptr; 67 | while(idx > 0) 68 | { 69 | idx--; 70 | switch(stk->stack[stk->stackptr - idx]) 71 | { 72 | case TYPE_INT: 73 | printf(" INT\n"); 74 | break; 75 | case TYPE_CONST: 76 | printf(" CONST\n"); 77 | break; 78 | case TYPE_CHAR: 79 | printf(" CHAR\n"); 80 | break; 81 | case TYPE_ARRAY: 82 | printf(" ARRAY\n"); 83 | break; 84 | default: 85 | printf(" ???\n"); 86 | break; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/typestack.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Type stack - for expression type checking 4 | 5 | */ 6 | 7 | #pragma once 8 | #include 9 | #include 10 | #include 11 | #include "ptypes.h" 12 | 13 | #define STACKMAXDEPTH 32 14 | 15 | typedef struct 16 | { 17 | uint8_t stackptr; 18 | vartype_t stack[STACKMAXDEPTH]; 19 | } typestack_t; 20 | 21 | void ts_init(typestack_t *stk); 22 | void ts_error(const char *txt); 23 | 24 | bool ts_push(typestack_t *stk, vartype_t t); 25 | bool ts_pop(typestack_t *stk); 26 | 27 | vartype_t ts_item(const typestack_t *stk, uint8_t offset); 28 | 29 | /** dump typestack */ 30 | void ts_dump(const typestack_t *stk); -------------------------------------------------------------------------------- /tests/arraytest.pl0: -------------------------------------------------------------------------------- 1 | // Array test 2 | 3 | var arr : array [100] of integer; 4 | var I : integer; 5 | 6 | begin 7 | for I:=0 to 99 do 8 | begin 9 | arr[I] := I*I; 10 | ! I, arr[I] 11 | end; 12 | 13 | end. 14 | 15 | -------------------------------------------------------------------------------- /tests/fortest.pl0: -------------------------------------------------------------------------------- 1 | VAR i : INTEGER; 2 | 3 | BEGIN 4 | FOR i:=0 TO 10 DO 5 | BEGIN 6 | ! i 7 | END; 8 | 9 | END. -------------------------------------------------------------------------------- /tests/int2bin.pl0: -------------------------------------------------------------------------------- 1 | var x,i : integer; 2 | 3 | begin 4 | i := 8; 5 | ? x; 6 | while i # 0 do 7 | begin 8 | if odd x then 9 | begin 10 | ! 1 11 | end 12 | else 13 | begin 14 | ! 0 15 | end; 16 | x := SHR x; 17 | i := i - 1 18 | end; 19 | end. -------------------------------------------------------------------------------- /tests/lextest.pl0: -------------------------------------------------------------------------------- 1 | VAR x, squ : integer; 2 | 3 | PROCEDURE square; 4 | BEGIN 5 | squ := x * x 6 | END; 7 | 8 | BEGIN 9 | x := 1; 10 | WHILE x <= 10 DO 11 | BEGIN 12 | CALL square; 13 | ! squ; 14 | x := x + 1 15 | END 16 | END. -------------------------------------------------------------------------------- /tests/passembler/test1.p: -------------------------------------------------------------------------------- 1 | ; 2 | ; This is a comment 3 | ; 4 | 5 | jmp @entry 6 | jmp $1234 7 | 8 | @entry: 9 | halt 10 | 11 | CAL 2 12 | JMP 2 ; ignored 13 | INT 0 5 14 | LOD 1 3 15 | STO 0 3 16 | LOD 1 4 17 | STO 0 4 18 | LIT 0 0 19 | STO 1 5 20 | LOD 0 4 21 | LIT 0 0 22 | OPR 0 12 23 | JPC 29 24 | LOD 0 4 25 | OPR 0 6 26 | JPC 20 27 | LOD 1 5 28 | LOD 0 3 29 | OPR 0 2 30 | STO 1 5 31 | LIT 0 2 32 | LOD 0 3 33 | OPR 0 4 34 | STO 0 3 35 | LOD 0 4 36 | LIT 0 2 37 | OPR 0 5 38 | STO 0 4 39 | JMP 9 40 | OPR 0 0 41 | -------------------------------------------------------------------------------- /tests/primes1000.pl0: -------------------------------------------------------------------------------- 1 | const max = 1000; 2 | var arg, ret : integer; 3 | 4 | procedure isprime; 5 | var i : integer; 6 | begin 7 | ret := 1; 8 | i := 2; 9 | while i < arg do 10 | begin 11 | if arg / i * i = arg then 12 | begin 13 | ret := 0; 14 | i := arg 15 | end; 16 | i := i + 1 17 | end 18 | end; 19 | 20 | procedure primes; 21 | begin 22 | arg := 2; 23 | while arg < max do 24 | begin 25 | call isprime; 26 | if ret = 1 then ! arg; 27 | arg := arg + 1 28 | end 29 | end; 30 | 31 | call primes 32 | . -------------------------------------------------------------------------------- /tests/primes4096.pl0: -------------------------------------------------------------------------------- 1 | const max = 4096; 2 | var arg, ret : integer; 3 | 4 | procedure isprime; 5 | var i : integer; 6 | begin 7 | ret := 1; 8 | i := 2; 9 | while i < arg do 10 | begin 11 | if arg / i * i = arg then 12 | begin 13 | ret := 0; 14 | i := arg 15 | end; 16 | i := i + 1 17 | end 18 | end; 19 | 20 | procedure primes; 21 | begin 22 | arg := 2; 23 | while arg < max do 24 | begin 25 | call isprime; 26 | if ret = 1 then ! arg; 27 | arg := arg + 1 28 | end 29 | end; 30 | 31 | call primes 32 | . -------------------------------------------------------------------------------- /tests/recursion.pl0: -------------------------------------------------------------------------------- 1 | VAR a1, recur : integer; 2 | 3 | PROCEDURE p2; 4 | VAR a2 : integer; 5 | 6 | PROCEDURE p3; 7 | VAR a3 : integer; 8 | 9 | BEGIN // p3 10 | a3 := 3; 11 | ! 3333; 12 | ! a1; 13 | ! a2; 14 | ! a3; 15 | END; 16 | 17 | BEGIN // p2 18 | a2 := 2; 19 | ! 2222; 20 | ! a1; 21 | ! a2; 22 | call p3; 23 | END; 24 | 25 | BEGIN // main 26 | a1 := 1; 27 | recur := 2; 28 | ! 1111; 29 | ! a1; 30 | CALL p2; 31 | ! 9999; 32 | END. 33 | 34 | -------------------------------------------------------------------------------- /tests/simple0.pl0: -------------------------------------------------------------------------------- 1 | // comments until the end of the line 2 | VAR L, R, X, T, C, V : INTEGER; 3 | 4 | BEGIN 5 | L := 0; 6 | R := 125; 7 | X := 6359; 8 | V := 0; 9 | C := 0; 10 | 11 | WHILE C < 20 DO 12 | BEGIN 13 | T := (L+R)/2; 14 | IF T*T <= X THEN 15 | BEGIN 16 | L := T; 17 | V := T; 18 | END 19 | ELSE 20 | BEGIN 21 | R := T; 22 | V := T; 23 | END; 24 | 25 | C := C + 1; 26 | ! L,R 27 | END; 28 | END. 29 | -------------------------------------------------------------------------------- /tests/simple1.pl0: -------------------------------------------------------------------------------- 1 | Var local1 : INTEGER; 2 | 3 | PROCEDURE PROC1; 4 | VAR test1,test2 : INTEGER; 5 | BEGIN 6 | test1 := 1; 7 | test2 := 2; 8 | ! local1; 9 | END; 10 | 11 | PROCEDURE PROC2; 12 | BEGIN 13 | ! local1; 14 | END; 15 | 16 | BEGIN 17 | local1 := 6; 18 | CALL PROC1; 19 | local1 := 10; 20 | CALL PROC2; 21 | local1 := 20; 22 | CALL PROC1; 23 | local1 := 10; 24 | ! local1; 25 | END. 26 | -------------------------------------------------------------------------------- /tests/simple2.pl0: -------------------------------------------------------------------------------- 1 | CONST xx = 10; 2 | VAR x, y, z, q, r, n, f : INTEGER; 3 | 4 | PROCEDURE multiply; 5 | VAR a, b : INTEGER; 6 | BEGIN 7 | a := x; 8 | b := y; 9 | z := 0; 10 | WHILE b > 0 DO 11 | BEGIN 12 | IF ODD b THEN z := z + a; 13 | a := SHL a; 14 | b := SHR b; 15 | END 16 | END; 17 | 18 | BEGIN 19 | ?x; ?y; 20 | !x; !y; 21 | CALL multiply; !z; 22 | END. -------------------------------------------------------------------------------- /tests/simple3.pl0: -------------------------------------------------------------------------------- 1 | const max = 100; 2 | var arg, ret : INTEGER; 3 | 4 | procedure isprime; 5 | var i : INTEGER; 6 | begin 7 | ret := 1; 8 | i := 2; 9 | while i < arg do 10 | begin 11 | if arg / i * i = arg then 12 | begin 13 | ret := 0; 14 | i := arg 15 | end; 16 | i := i + 1 17 | end 18 | end; 19 | 20 | procedure primes; 21 | begin 22 | arg := 2; 23 | while arg < max do 24 | begin 25 | call isprime; 26 | if ret = 1 then ! arg; 27 | arg := arg + 1 28 | end 29 | end; 30 | 31 | call primes 32 | . -------------------------------------------------------------------------------- /tests/simple4.pl0: -------------------------------------------------------------------------------- 1 | CONST xx = 10; 2 | VAR x, y, z, q, r, n, f : INTEGER; 3 | 4 | PROCEDURE multiply; 5 | VAR a, b : INTEGER; 6 | BEGIN 7 | a := x; 8 | b := y; 9 | z := 0; 10 | WHILE b > 0 DO 11 | BEGIN 12 | IF ODD b THEN z := z + a; 13 | a := 2 * a; 14 | b := b / 2 15 | END 16 | END; 17 | 18 | PROCEDURE divide; 19 | VAR w : INTEGER; 20 | BEGIN 21 | r := x; 22 | q := 0; 23 | w := y; 24 | WHILE w <= r DO w := 2 * w; 25 | WHILE w > y DO 26 | BEGIN 27 | q := 2 * q; 28 | w := w / 2; 29 | IF w <= r THEN 30 | BEGIN 31 | r := r - w; 32 | q := q + 1 33 | END 34 | END 35 | END; 36 | 37 | PROCEDURE gcd; 38 | VAR f, g : INTEGER; 39 | BEGIN 40 | f := x; 41 | g := y; 42 | WHILE f # g DO 43 | BEGIN 44 | IF f < g THEN g := g - f; 45 | IF g < f THEN f := f - g 46 | END; 47 | z := f 48 | END; 49 | 50 | PROCEDURE fact; 51 | BEGIN 52 | IF n > 1 THEN 53 | BEGIN 54 | f := n * f; 55 | n := n - 1; 56 | CALL fact 57 | END 58 | END; 59 | 60 | BEGIN 61 | ?x; ?y; CALL multiply; !z; 62 | ?x; ?y; CALL divide; !q; !r; 63 | ?x; ?y; CALL gcd; !z; 64 | ?n; f := 1; CALL fact; !f 65 | END. -------------------------------------------------------------------------------- /tests/typerror1.pl0: -------------------------------------------------------------------------------- 1 | var x : char; 2 | var y : integer; 3 | 4 | begin 5 | x := 'C'; 6 | y := 123; 7 | ! x; 8 | ! y; 9 | end. 10 | -------------------------------------------------------------------------------- /virtualmachine/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | A virtual machine reference implementation 4 | for the Nano Pascal compiler 5 | 6 | Copyright Niels A. Moseley 2017 - 2021 7 | Moseley Instruments 8 | http://www.moseleyinstruments.com 9 | 10 | 11 | */ 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "vm.h" 19 | 20 | int main(int argc, char *argv[]) 21 | { 22 | printf("P-code virtual machine 0.1\n"); 23 | printf("Instruction size is %lu bytes\n\n", sizeof(instruction_t)); 24 | 25 | uint8_t *mem = NULL; 26 | 27 | size_t bytes = 0; 28 | if (argc < 2) 29 | { 30 | printf("Usage: %s \n", argv[0]); 31 | return -1; 32 | } 33 | else 34 | { 35 | FILE *fin = fopen(argv[1],"rb"); 36 | if (fin == NULL) 37 | { 38 | printf("Cannot open file %s\n", argv[1]); 39 | return -1; 40 | } 41 | 42 | fseek(fin, 0, SEEK_END); 43 | bytes = ftell(fin); 44 | rewind(fin); 45 | 46 | mem = malloc(bytes); 47 | if (fread(mem, 1, bytes, fin) != bytes) 48 | { 49 | printf("Cannot read file %s\n", argv[1]); 50 | return -1; 51 | } 52 | 53 | fclose(fin); 54 | } 55 | 56 | vm_context_t vm; 57 | vm_init(&vm, mem, bytes); 58 | 59 | while(1) 60 | { 61 | if (vm_execute(&vm) == false) 62 | break; 63 | } 64 | 65 | printf("Executed %lu instructions\n", vm.inscount); 66 | vm_free(&vm); 67 | free(mem); 68 | return 0; 69 | } 70 | -------------------------------------------------------------------------------- /virtualmachine/vm.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include "vm.h" 6 | 7 | void vm_init(vm_context_t *c, uint8_t *memptr, uint16_t memsize) 8 | { 9 | c->dstack = calloc(16384, sizeof(uint16_t)); 10 | c->mem = memptr; 11 | c->memsize = memsize; 12 | c->t = 0; 13 | c->b = 1; 14 | c->pc = 0; 15 | c->dstack[1] = 0; // DL, base address 16 | c->dstack[2] = 0; // old base 17 | c->dstack[3] = 0; // return address 18 | c->inscount = 0; 19 | } 20 | 21 | void vm_free(vm_context_t *c) 22 | { 23 | free(c->dstack); 24 | } 25 | 26 | uint16_t base(vm_context_t *c, uint16_t l) 27 | { 28 | uint16_t b1 = c->b; 29 | while(l > 0) 30 | { 31 | b1 = c->dstack[b1]; 32 | l--; 33 | } 34 | return b1; 35 | } 36 | 37 | int16_t readInt() 38 | { 39 | int v; 40 | printf("> "); 41 | scanf("%d", &v); 42 | return v; 43 | } 44 | 45 | void writeInt(int16_t v) 46 | { 47 | printf("%d", v); 48 | fflush(stdout); 49 | } 50 | 51 | int16_t readChar() 52 | { 53 | char v; 54 | scanf("%c", &v); 55 | return v; 56 | } 57 | 58 | void writeChar(int16_t v) 59 | { 60 | printf("%c", (char)v); 61 | fflush(stdout); 62 | } 63 | 64 | void vm_push(vm_context_t *c, uint16_t v) 65 | { 66 | c->t++; 67 | c->dstack[c->t] = v; 68 | } 69 | 70 | bool vm_execute(vm_context_t *c) 71 | { 72 | instruction_t *ins = (instruction_t *)(c->mem + c->pc*sizeof(instruction_t)); 73 | uint16_t level = 0; 74 | uint16_t imm16 = ins->opt16; 75 | uint16_t idx = 0; // index for array operations 76 | 77 | c->pc++; 78 | c->inscount++; 79 | switch(ins->opcode & 0xF) 80 | { 81 | case VM_LIT: // load literal constant 0,n 82 | c->t++; 83 | c->dstack[c->t] = imm16; 84 | break; 85 | case VM_OPR: // arithmetic or logical operation 0,n 86 | switch(imm16) 87 | { 88 | case OPR_RET: // return from procedure 89 | c->t = c->b-1; 90 | c->pc = c->dstack[c->t+3]; 91 | c->b = c->dstack[c->t+2]; 92 | break; 93 | case OPR_NEG: 94 | c->dstack[c->t] = -c->dstack[c->t]; 95 | break; 96 | case OPR_ADD: 97 | c->t--; 98 | c->dstack[c->t] += c->dstack[c->t+1]; 99 | break; 100 | case OPR_SUB: 101 | c->t--; 102 | c->dstack[c->t] -= c->dstack[c->t+1]; 103 | break; 104 | case OPR_MUL: 105 | c->t--; 106 | c->dstack[c->t] *= c->dstack[c->t+1]; 107 | break; 108 | case OPR_DIV: 109 | c->t--; 110 | c->dstack[c->t] /= c->dstack[c->t+1]; 111 | break; 112 | case OPR_ODD: 113 | c->dstack[c->t] = c->dstack[c->t] & 1; 114 | break; 115 | case OPR_EQ: 116 | c->t--; 117 | c->dstack[c->t] = (c->dstack[c->t] == c->dstack[c->t+1]) ? 1 : 0; 118 | break; 119 | case OPR_NEQ: 120 | c->t--; 121 | c->dstack[c->t] = (c->dstack[c->t] != c->dstack[c->t+1]) ? 1 : 0; 122 | break; 123 | case OPR_LESS: 124 | c->t--; 125 | c->dstack[c->t] = (c->dstack[c->t] < c->dstack[c->t+1]) ? 1 : 0; 126 | break; 127 | case OPR_LEQ: 128 | c->t--; 129 | c->dstack[c->t] = (c->dstack[c->t] <= c->dstack[c->t+1]) ? 1 : 0; 130 | break; 131 | case OPR_GREATER: 132 | c->t--; 133 | c->dstack[c->t] = (c->dstack[c->t] > c->dstack[c->t+1]) ? 1 : 0; 134 | break; 135 | case OPR_GEQ: 136 | c->t--; 137 | c->dstack[c->t] = (c->dstack[c->t] >= c->dstack[c->t+1]) ? 1 : 0; 138 | break; 139 | case OPR_OUTCHAR: 140 | writeChar(c->dstack[c->t]); 141 | c->t--; 142 | break; 143 | case OPR_OUTINT: 144 | writeInt(c->dstack[c->t]); 145 | c->t--; 146 | break; 147 | case OPR_INCHAR: 148 | c->t++; 149 | c->dstack[c->t] = readChar(); 150 | break; 151 | case OPR_ININT: 152 | c->t++; 153 | c->dstack[c->t] = readInt(); 154 | break; 155 | case OPR_SHR: 156 | c->dstack[c->t] = ((uint16_t)c->dstack[c->t]) >> 1; 157 | break; 158 | case OPR_SAR: 159 | c->dstack[c->t] >>= 1; 160 | break; 161 | case OPR_SHL: 162 | c->dstack[c->t] <<= 1; 163 | break; 164 | default: 165 | //error 166 | break; 167 | } 168 | break; 169 | case VM_LOD: // load variable l,d 170 | level = (ins->opcode >> 4); // level 171 | //printf("Level B = %d\n", base(c,level)); 172 | c->t++; 173 | c->dstack[c->t] = c->dstack[(uint16_t)(base(c,level) + (int16_t)imm16)]; 174 | //if (level != 0) 175 | //{ 176 | // printf("Loading from dstack address %d\n", (uint16_t)(base(c,level) + (int16_t)imm16)); 177 | //} 178 | break; 179 | case VM_STO: // load indexed variable l,d 180 | level = (ins->opcode >> 4); // level 181 | //printf("0x%04X\t Level B = %d -> ofs = %d\n", c->pc, base(c,level), imm16); 182 | c->dstack[(uint16_t)(base(c,level) + (int16_t)imm16)] = c->dstack[c->t]; 183 | c->t--; 184 | //if (level != 0) 185 | //{ 186 | // printf("Storing to dstack address %d\n", (uint16_t)(base(c,level) + (int16_t)imm16)); 187 | //} 188 | break; 189 | case VM_LODX: 190 | idx = c->dstack[c->t]; // get index / offset 191 | level = (ins->opcode >> 4); // level 192 | c->dstack[c->t] = c->dstack[(uint16_t)(base(c,level) + (int16_t)imm16) + idx]; 193 | break; 194 | case VM_STOX: 195 | idx = c->dstack[c->t-1]; // get index / offset 196 | level = (ins->opcode >> 4); // level 197 | c->dstack[(uint16_t)(base(c,level) + (int16_t)imm16) + idx] = c->dstack[c->t]; 198 | c->t-=2; 199 | break; 200 | case VM_CAL: // call procedure or function v,a 201 | { 202 | level = (ins->opcode >> 4); // level 203 | uint16_t prevbp = base(c,level); 204 | c->dstack[c->t+1] = prevbp; 205 | c->dstack[c->t+2] = c->b; 206 | c->dstack[c->t+3] = c->pc; 207 | c->b=c->t+1; 208 | c->pc=imm16; 209 | } 210 | break; 211 | case VM_INT: // increment stack pointer 0,n 212 | c->t += (int16_t)imm16; 213 | break; 214 | case VM_JMP: // unconditional jump 0,a 215 | c->pc = imm16; 216 | break; 217 | case VM_JPC: // conditional jump 0,a 218 | if (c->dstack[c->t] == 0) 219 | { 220 | c->pc = imm16; 221 | } 222 | c->t--; 223 | break; 224 | case VM_HALT: 225 | return false; 226 | default: 227 | // error! 228 | return false; 229 | } 230 | return true; 231 | } 232 | -------------------------------------------------------------------------------- /virtualmachine/vm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "opcodes.h" 5 | 6 | void vm_init(vm_context_t *c, uint8_t *memptr, uint16_t memsize); 7 | void vm_free(vm_context_t *c); 8 | void vm_push(vm_context_t *c, uint16_t v); 9 | bool vm_execute(vm_context_t *c); 10 | 11 | -------------------------------------------------------------------------------- /vmdbgui/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.7.0) 2 | 3 | project(vmdbgui) 4 | 5 | set(CMAKE_CXX_STANDARD 17) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | 8 | set(CMAKE_AUTOMOC ON) 9 | set(CMAKE_AUTORCC ON) 10 | set(CMAKE_AUTOUIC ON) 11 | 12 | find_package(Qt5 COMPONENTS Widgets Core REQUIRED) 13 | 14 | include_directories(${PROJECT_SOURCE_DIR}/../common) 15 | include_directories(${PROJECT_SOURCE_DIR}/../virtualmachine) 16 | 17 | add_executable(vmdbgui 18 | ${PROJECT_SOURCE_DIR}/../virtualmachine/vm.c 19 | src/mainwindow.cpp 20 | src/vmwrapper.cpp 21 | src/regmodel.cpp 22 | src/stackmodel.cpp 23 | src/instrmodel.cpp 24 | src/main.cpp 25 | ) 26 | 27 | target_link_libraries(vmdbgui PRIVATE Qt5::Widgets) 28 | -------------------------------------------------------------------------------- /vmdbgui/src/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class SettingsKeys 6 | { 7 | public: 8 | inline static const char* LAST_DIR_KEY = "last_dir"; 9 | }; 10 | -------------------------------------------------------------------------------- /vmdbgui/src/instrmodel.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include "instrmodel.h" 6 | 7 | InstrModel::InstrModel(VMWrapper *vm, QObject *parent) : m_vm(vm), QAbstractTableModel(parent) 8 | { 9 | } 10 | 11 | int InstrModel::rowCount(const QModelIndex &parent) const 12 | { 13 | Q_UNUSED(parent); 14 | 15 | if (m_vm == nullptr) 16 | return 0; 17 | 18 | return m_vm->m_context->memsize / sizeof(instruction_t); 19 | } 20 | 21 | int InstrModel::columnCount(const QModelIndex &parent) const 22 | { 23 | Q_UNUSED(parent); 24 | return 3; 25 | } 26 | 27 | QVariant InstrModel::data(const QModelIndex &index, int role) const 28 | { 29 | if (m_vm == nullptr) 30 | { 31 | return QVariant(); 32 | } 33 | 34 | if (!index.isValid()) 35 | { 36 | return QVariant(); 37 | } 38 | 39 | if (index.row() >= (m_vm->m_context->memsize / sizeof(instruction_t))) 40 | { 41 | return QVariant(); 42 | } 43 | 44 | if (role == Qt::BackgroundColorRole) 45 | { 46 | if (index.row() == m_vm->m_context->pc) 47 | { 48 | return QColor(128,128,128); 49 | } 50 | else 51 | { 52 | return QColor(255,255,255); 53 | } 54 | } 55 | else if (role == Qt::DisplayRole) 56 | { 57 | QString numStr; 58 | switch(index.column()) 59 | { 60 | case 0: 61 | return QStringLiteral("0x%1").arg(index.row(), 4, 16, QLatin1Char('0')); 62 | case 1: 63 | { 64 | const uint16_t pc = index.row(); 65 | const instruction_t* ins = (const instruction_t*)&m_vm->m_context->mem[pc*sizeof(instruction_t)]; 66 | return QStringLiteral("0x%1 0x%2").arg(ins->opcode, 2, 16, QLatin1Char('0')) 67 | .arg(ins->opt16, 4, 16,QLatin1Char('0')); 68 | } 69 | case 2: 70 | return disasm(index.row()); 71 | default: 72 | break; 73 | } 74 | } 75 | 76 | 77 | return QVariant(); 78 | } 79 | 80 | QVariant InstrModel::headerData(int section, Qt::Orientation orientation, int role) const 81 | { 82 | if (role == Qt::DisplayRole && orientation == Qt::Horizontal) 83 | { 84 | switch(section) 85 | { 86 | case 0: 87 | return QString("Address"); 88 | case 1: 89 | return QString("Opcode"); 90 | case 2: 91 | return QString("Instruction"); 92 | default: 93 | return QVariant(); 94 | } 95 | } 96 | return QVariant(); 97 | } 98 | 99 | void InstrModel::update() 100 | { 101 | beginResetModel(); 102 | endResetModel(); 103 | } 104 | 105 | QString decodealu(uint16_t imm16) 106 | { 107 | switch(imm16) 108 | { 109 | case OPR_RET: 110 | return QString("RET"); 111 | 112 | case OPR_NEG: 113 | return QString("NEG"); 114 | 115 | case OPR_ADD: 116 | return QString("ADD"); 117 | 118 | case OPR_SUB: 119 | return QString("SUB"); 120 | 121 | case OPR_MUL: 122 | return QString("MUL"); 123 | 124 | case OPR_DIV: 125 | return QString("DIV"); 126 | 127 | case OPR_ODD: 128 | return QString("ODD"); 129 | 130 | case OPR_EQ: 131 | return QString("EQU"); 132 | 133 | case OPR_NEQ: 134 | return QString("NEQ"); 135 | 136 | case OPR_LESS: 137 | return QString("LES"); 138 | 139 | case OPR_LEQ: 140 | return QString("LEQ"); 141 | 142 | case OPR_GREATER: 143 | return QString("GRE"); 144 | 145 | case OPR_GEQ: 146 | return QString("GEQ"); 147 | 148 | case OPR_SHL: 149 | return QString("SHL"); 150 | 151 | case OPR_SHR: 152 | return QString("SHR"); 153 | 154 | case OPR_SAR: 155 | return QString("SAR"); 156 | 157 | case OPR_OUTCHAR: 158 | return QString("OUTCHAR"); 159 | 160 | case OPR_OUTINT: 161 | return QString("OUTINT"); 162 | 163 | case OPR_INCHAR: 164 | return QString("OUTCHAR"); 165 | 166 | case OPR_ININT: 167 | return QString("ININT"); 168 | 169 | default: 170 | return QString("???\n"); 171 | 172 | } 173 | } 174 | 175 | QString InstrModel::disasm(uint16_t pc) const 176 | { 177 | const instruction_t* ins = (const instruction_t*)&m_vm->m_context->mem[pc*sizeof(instruction_t)]; 178 | uint8_t opcode = ins->opcode; 179 | uint16_t imm16 = ins->opt16; 180 | switch (opcode & 0xF) 181 | { 182 | case VM_LIT: 183 | return QString::asprintf("LIT\t%d", imm16); 184 | 185 | case VM_OPR: 186 | return decodealu(imm16); 187 | 188 | case VM_LOD: 189 | return QString::asprintf("LOD\tlvl:%d\tofs:%d", opcode >> 4 ,imm16); 190 | 191 | case VM_STO: 192 | return QString::asprintf("STO\tlvl:%d\tofs:%d", opcode >> 4 ,imm16); 193 | 194 | case VM_CAL: 195 | return QString::asprintf("CAL\tlvl:%d\t0x%04X", opcode >> 4, imm16); 196 | 197 | case VM_INT: 198 | return QString::asprintf("INT\t%d", imm16); 199 | 200 | case VM_JMP: 201 | return QString::asprintf("JMP\t0x%04X", imm16); 202 | 203 | case VM_JPC: 204 | return QString::asprintf("JPC\t0x%04X", imm16); 205 | 206 | case VM_HALT: 207 | return QString::asprintf("HALT"); 208 | 209 | case VM_LODX: 210 | return QString::asprintf("LODX\tlvl:%d\tofs:%d", opcode >> 4 ,imm16); 211 | 212 | case VM_STOX: 213 | return QString::asprintf("STOX\tlvl:%d\tofs:%d", opcode >> 4 ,imm16); 214 | 215 | default: 216 | return QString::asprintf("???\n"); 217 | 218 | } 219 | } -------------------------------------------------------------------------------- /vmdbgui/src/instrmodel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "vmwrapper.h" 5 | 6 | class InstrModel : public QAbstractTableModel 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | InstrModel(VMWrapper *vm, QObject *parent = 0); 12 | 13 | int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; 14 | int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; 15 | 16 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; 17 | QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; 18 | 19 | void update(); 20 | 21 | protected: 22 | QString disasm(uint16_t pc) const; 23 | VMWrapper *m_vm; 24 | }; 25 | -------------------------------------------------------------------------------- /vmdbgui/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | QApplication a(argc, argv); 7 | 8 | a.setApplicationName("vmdbgui"); 9 | a.setOrganizationName("MoseleyInstruments"); 10 | a.setOrganizationDomain("moseleyinstruments.com"); 11 | 12 | MainWindow w; 13 | w.show(); 14 | 15 | return a.exec(); 16 | } 17 | -------------------------------------------------------------------------------- /vmdbgui/src/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #include "common.h" 15 | #include "mainwindow.h" 16 | 17 | MainWindow::MainWindow(QWidget *parent) : 18 | QMainWindow(parent) 19 | { 20 | setWindowTitle("VM Debugger"); 21 | setMinimumSize(800,450); 22 | 23 | QMenuBar * menuBar = new QMenuBar(this); 24 | setMenuBar(menuBar); 25 | 26 | // ------------------------------------------------------------ 27 | // File menu 28 | // ------------------------------------------------------------ 29 | 30 | QMenu * menu = menuBar->addMenu("&File"); 31 | 32 | // New 33 | QAction * action = new QAction("&New", this); 34 | connect(action, &QAction::triggered, this, &MainWindow::OnFileNew); 35 | menu->addAction(action); 36 | 37 | // Open 38 | action = new QAction("&Open", this); 39 | connect(action, &QAction::triggered, this, &MainWindow::OnFileOpen); 40 | menu->addAction(action); 41 | 42 | menu->addSeparator(); 43 | 44 | // Quit 45 | action = new QAction("E&xit", this); 46 | connect(action, &QAction::triggered, qApp, &QCoreApplication::quit, Qt::QueuedConnection); 47 | menu->addAction(action); 48 | 49 | // ------------------------------------------------------------ 50 | // Debug menu 51 | // ------------------------------------------------------------ 52 | 53 | menu = menuBar->addMenu("&Debug"); 54 | // Step 55 | action = new QAction("Step", this); 56 | action->setShortcut(QKeySequence(Qt::Key_F10)); 57 | connect(action, &QAction::triggered, this, &MainWindow::OnStep); 58 | menu->addAction(action); 59 | 60 | QStatusBar * statusBar = new QStatusBar(this); 61 | setStatusBar(statusBar); 62 | 63 | // ------------------------------------------------------------ 64 | // build the GUI 65 | // ------------------------------------------------------------ 66 | 67 | QWidget *centralWidget = new QWidget(this); 68 | setCentralWidget(centralWidget); 69 | 70 | QHBoxLayout *mainLayout = new QHBoxLayout(); 71 | 72 | QVBoxLayout *cpuinfoLayout = new QVBoxLayout(); 73 | m_regTable = new QTableView(centralWidget); 74 | m_regTable->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); 75 | m_regTable->horizontalHeader()->setStretchLastSection(true); 76 | cpuinfoLayout->addWidget(m_regTable,0); 77 | 78 | m_stackTable = new QTableView(centralWidget); 79 | m_stackTable->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); 80 | m_stackTable->horizontalHeader()->setStretchLastSection(true); 81 | cpuinfoLayout->addWidget(m_stackTable,2); 82 | 83 | mainLayout->addLayout(cpuinfoLayout); 84 | 85 | m_codeTable = new QTableView(centralWidget); 86 | m_codeTable->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); 87 | m_codeTable->horizontalHeader()->setStretchLastSection(true); 88 | mainLayout->addWidget(m_codeTable,1); 89 | 90 | // setup models 91 | m_regmodel = new RegModel(&m_vm); 92 | m_stackmodel = new StackModel(&m_vm); 93 | m_codemodel = new InstrModel(&m_vm); 94 | 95 | m_regTable->setModel(m_regmodel); 96 | m_stackTable->setModel(m_stackmodel); 97 | m_codeTable->setModel(m_codemodel); 98 | 99 | centralWidget->setLayout(mainLayout); 100 | } 101 | 102 | MainWindow::~MainWindow() 103 | { 104 | m_regTable->setModel(nullptr); 105 | m_stackTable->setModel(nullptr); 106 | m_codeTable->setModel(nullptr); 107 | } 108 | 109 | void MainWindow::OnFileNew() 110 | { 111 | 112 | } 113 | 114 | void MainWindow::OnFileOpen() 115 | { 116 | 117 | QSettings MySettings; // Will be using application informations 118 | // for correct location of your settings 119 | 120 | QString SelectedFile = QFileDialog::getOpenFileName( 121 | this, 122 | tr("Open binary"), 123 | MySettings.value(SettingsKeys::LAST_DIR_KEY).toString(), 124 | tr("Binary files (*.bin);;All files (*.*)"), 125 | 0, QFileDialog::DontUseNativeDialog); 126 | 127 | if (!SelectedFile.isEmpty()) 128 | { 129 | QDir CurrentDir; 130 | MySettings.setValue(SettingsKeys::LAST_DIR_KEY, 131 | CurrentDir.absoluteFilePath(SelectedFile)); 132 | 133 | qDebug() << "Loading " << SelectedFile; 134 | 135 | FILE *fin = fopen(SelectedFile.toUtf8().data(), "rb"); 136 | if (fin == nullptr) 137 | { 138 | qDebug() << "Error loading file " << SelectedFile; 139 | return; 140 | } 141 | 142 | fseek(fin,0,SEEK_END); 143 | size_t bytes = ftell(fin); 144 | rewind(fin); 145 | 146 | std::unique_ptr data(new uint8_t[bytes]); 147 | if (fread(data.get(), 1, bytes, fin) != bytes) 148 | { 149 | qDebug() << "Error loading file " << SelectedFile; 150 | fclose(fin); 151 | return; 152 | } 153 | 154 | m_vm.clear(); 155 | m_vm.load(data.get(), bytes); 156 | 157 | m_regmodel->update(); 158 | m_stackmodel->update(); 159 | m_codemodel->update(); 160 | 161 | fclose(fin); 162 | } 163 | } 164 | 165 | void MainWindow::OnStep() 166 | { 167 | m_vm.step(); 168 | m_codemodel->update(); 169 | m_regmodel->update(); 170 | m_stackmodel->update(); 171 | } 172 | -------------------------------------------------------------------------------- /vmdbgui/src/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include 6 | #include "opcodes.h" 7 | #include "regmodel.h" 8 | #include "stackmodel.h" 9 | #include "instrmodel.h" 10 | #include "vmwrapper.h" 11 | 12 | namespace Ui { 13 | class MainWindow; 14 | } 15 | 16 | class MainWindow : public QMainWindow 17 | { 18 | Q_OBJECT 19 | 20 | public: 21 | explicit MainWindow(QWidget *parent = 0); 22 | ~MainWindow(); 23 | 24 | protected: 25 | QTableView *m_regTable; 26 | QTableView *m_stackTable; 27 | QTableView *m_codeTable; 28 | 29 | RegModel *m_regmodel; 30 | StackModel *m_stackmodel; 31 | InstrModel *m_codemodel; 32 | VMWrapper m_vm; 33 | 34 | private: 35 | 36 | private slots: 37 | void OnStep(); 38 | void OnFileNew(); 39 | void OnFileOpen(); 40 | }; 41 | 42 | #endif // MAINWINDOW_H 43 | -------------------------------------------------------------------------------- /vmdbgui/src/regmodel.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "regmodel.h" 3 | 4 | RegModel::RegModel(VMWrapper *vm, QObject *parent) : m_vm(vm), QAbstractTableModel(parent) 5 | { 6 | 7 | } 8 | 9 | int RegModel::rowCount(const QModelIndex &parent) const 10 | { 11 | Q_UNUSED(parent); 12 | return 5; 13 | } 14 | 15 | int RegModel::columnCount(const QModelIndex &parent) const 16 | { 17 | Q_UNUSED(parent); 18 | return 2; 19 | } 20 | 21 | void RegModel::update() 22 | { 23 | beginResetModel(); 24 | endResetModel(); 25 | } 26 | 27 | QVariant RegModel::data(const QModelIndex &index, int role) const 28 | { 29 | const QStringList regs = {"PC","T","B","ST0","ST1"}; 30 | 31 | if (m_vm == nullptr) 32 | { 33 | return QVariant(); 34 | } 35 | 36 | if (index.row() >= regs.length()) 37 | { 38 | return QVariant(); 39 | } 40 | 41 | if (!index.isValid() || role != Qt::DisplayRole) 42 | { 43 | return QVariant(); 44 | } 45 | 46 | QString numStr; 47 | switch(index.column()) 48 | { 49 | case 0: 50 | return regs[index.row()]; 51 | case 1: 52 | switch(index.row()) 53 | { 54 | case 0: 55 | numStr = QStringLiteral("0x%1").arg(m_vm->m_context->pc, 4, 16, QLatin1Char('0')); 56 | return numStr; 57 | case 1: 58 | numStr = QStringLiteral("0x%1").arg(m_vm->m_context->t, 4, 16, QLatin1Char('0')); 59 | return numStr; 60 | case 2: 61 | numStr = QStringLiteral("0x%1").arg(m_vm->m_context->b, 4, 16, QLatin1Char('0')); 62 | return numStr; 63 | case 3: 64 | return m_vm->m_context->dstack[m_vm->m_context->t]; 65 | break; 66 | case 4: 67 | return m_vm->m_context->dstack[m_vm->m_context->t-1]; 68 | break; 69 | default: 70 | return QVariant(); 71 | } 72 | } 73 | 74 | return QVariant(); 75 | } 76 | 77 | QVariant RegModel::headerData(int section, Qt::Orientation orientation, int role) const 78 | { 79 | if (role == Qt::DisplayRole && orientation == Qt::Horizontal) 80 | { 81 | switch(section) 82 | { 83 | case 0: 84 | return QString("Register"); 85 | case 1: 86 | return QString("Value"); 87 | default: 88 | return QVariant(); 89 | } 90 | } 91 | return QVariant(); 92 | } 93 | 94 | -------------------------------------------------------------------------------- /vmdbgui/src/regmodel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "vmwrapper.h" 5 | 6 | class RegModel : public QAbstractTableModel 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | RegModel(VMWrapper *vm, QObject *parent = 0); 12 | 13 | int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; 14 | int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; 15 | 16 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; 17 | QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; 18 | 19 | void update(); 20 | 21 | protected: 22 | VMWrapper *m_vm; 23 | }; 24 | -------------------------------------------------------------------------------- /vmdbgui/src/stackmodel.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include "stackmodel.h" 6 | 7 | StackModel::StackModel(VMWrapper *vm, QObject *parent) : m_vm(vm), QAbstractTableModel(parent) 8 | { 9 | } 10 | 11 | int StackModel::rowCount(const QModelIndex &parent) const 12 | { 13 | Q_UNUSED(parent); 14 | return memsize; 15 | } 16 | 17 | int StackModel::columnCount(const QModelIndex &parent) const 18 | { 19 | Q_UNUSED(parent); 20 | return 2; 21 | } 22 | 23 | void StackModel::update() 24 | { 25 | beginResetModel(); 26 | endResetModel(); 27 | } 28 | 29 | QVariant StackModel::data(const QModelIndex &index, int role) const 30 | { 31 | if (m_vm == nullptr) 32 | { 33 | return QVariant(); 34 | } 35 | 36 | if (!index.isValid()) 37 | { 38 | return QVariant(); 39 | } 40 | 41 | if (index.row() >= memsize) 42 | { 43 | return QVariant(); 44 | } 45 | 46 | if (role == Qt::BackgroundColorRole) 47 | { 48 | if (index.row() == m_vm->m_context->t) 49 | { 50 | return QColor(128,128,128); 51 | } 52 | else if (index.row() == m_vm->m_context->b) 53 | { 54 | return QColor(128,255,255); 55 | } 56 | else 57 | { 58 | return QColor(255,255,255); 59 | } 60 | } 61 | else if (role == Qt::DisplayRole) 62 | { 63 | QString numStr; 64 | switch(index.column()) 65 | { 66 | case 0: 67 | return QStringLiteral("0x%1").arg(index.row(), 4, 16, QLatin1Char('0')); 68 | case 1: 69 | return QStringLiteral("0x%1\t%2").arg(m_vm->m_context->dstack[index.row()], 4, 16, QLatin1Char('0')).arg(m_vm->m_context->dstack[index.row()]); 70 | default: 71 | break; 72 | } 73 | } 74 | 75 | 76 | return QVariant(); 77 | } 78 | 79 | QVariant StackModel::headerData(int section, Qt::Orientation orientation, int role) const 80 | { 81 | if (role == Qt::DisplayRole && orientation == Qt::Horizontal) 82 | { 83 | switch(section) 84 | { 85 | case 0: 86 | return QString("Address"); 87 | case 1: 88 | return QString("Value"); 89 | default: 90 | return QVariant(); 91 | } 92 | } 93 | return QVariant(); 94 | } 95 | -------------------------------------------------------------------------------- /vmdbgui/src/stackmodel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "vmwrapper.h" 5 | 6 | class StackModel : public QAbstractTableModel 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | StackModel(VMWrapper *vm, QObject *parent = 0); 12 | 13 | int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; 14 | int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; 15 | 16 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; 17 | QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; 18 | 19 | void update(); 20 | 21 | protected: 22 | static constexpr const size_t memsize = 1024; 23 | VMWrapper *m_vm; 24 | }; 25 | -------------------------------------------------------------------------------- /vmdbgui/src/vmwrapper.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "vmwrapper.h" 4 | 5 | VMWrapper::VMWrapper() 6 | { 7 | m_context = new vm_context_t; 8 | m_context->mem = nullptr; 9 | m_context->memsize = 0; 10 | m_context->dstack = nullptr; 11 | clear(); 12 | } 13 | 14 | VMWrapper::~VMWrapper() 15 | { 16 | delete m_context; 17 | } 18 | 19 | void VMWrapper::reset() 20 | { 21 | m_context->pc = 0; 22 | m_context->t = 0; 23 | m_context->b = 1; 24 | m_context->inscount = 0; 25 | m_halted = true; 26 | } 27 | 28 | void VMWrapper::clear() 29 | { 30 | if (m_context->mem != nullptr) 31 | delete m_context->mem; 32 | 33 | if (m_context->dstack != nullptr) 34 | vm_free(m_context); 35 | 36 | vm_init(m_context, nullptr, 0); 37 | } 38 | 39 | void VMWrapper::load(const uint8_t *code, uint16_t bytes) 40 | { 41 | clear(); 42 | m_context->mem = new uint8_t[bytes]; 43 | m_context->memsize = bytes; 44 | memcpy(m_context->mem, code, bytes); 45 | reset(); 46 | } 47 | 48 | bool VMWrapper::step() 49 | { 50 | return vm_execute(m_context); 51 | } -------------------------------------------------------------------------------- /vmdbgui/src/vmwrapper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | extern "C" 5 | { 6 | #include "vm.h" 7 | } 8 | 9 | class VMWrapper 10 | { 11 | public: 12 | VMWrapper(); 13 | virtual ~VMWrapper(); 14 | 15 | /** reset cpu / warm boot*/ 16 | void reset(); 17 | 18 | /** clear memory and reset */ 19 | void clear(); 20 | 21 | /** load code into the VM and reset + halt */ 22 | void load(const uint8_t *code, uint16_t bytes); 23 | 24 | constexpr bool isHalted() const 25 | { 26 | return m_halted; 27 | } 28 | 29 | void halt(bool halt) 30 | { 31 | m_halted = halt; 32 | } 33 | 34 | bool step(); 35 | 36 | vm_context_t *m_context; 37 | 38 | protected: 39 | bool m_halted; 40 | }; 41 | --------------------------------------------------------------------------------