├── .drone.yml ├── .github └── workflows │ ├── CI.yml │ └── release.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── README.md ├── include ├── stdio.h ├── stdlib.h └── string.h ├── src ├── ast │ ├── mod.rs │ ├── operations.rs │ ├── span.rs │ ├── tree.rs │ └── types.rs ├── generator │ ├── cast_inst.rs │ ├── expr.rs │ ├── func_def.rs │ ├── gen.rs │ ├── mod.rs │ ├── out.rs │ ├── stmt.rs │ └── utils.rs ├── lib.rs ├── main.rs ├── parse │ ├── declaration.rs │ ├── expression.rs │ ├── literal.rs │ ├── mod.rs │ ├── parse.pest │ └── statement.rs ├── preprocess │ ├── mod.rs │ ├── phase2.rs │ ├── phase3.pest │ ├── phase3.rs │ ├── phase4.pest │ ├── phase4.rs │ ├── phase6.pest │ └── phase6.rs ├── utils │ ├── error.rs │ └── mod.rs └── visual │ └── mod.rs ├── tests ├── auto-advisor │ ├── auto-advisor-advanced.c │ ├── auto-advisor-without-array2ptr.c │ ├── auto-advisor-without-struct.c │ └── auto-advisor.c ├── basic │ └── basic.c ├── binary │ └── binary.c ├── condition │ └── condition.c ├── function │ ├── function.c │ └── recursive.c ├── global │ ├── decl.c │ └── decl2.c ├── loop │ └── loop.c ├── main.rs ├── matrix-multiplication │ └── matrix-multiplication.c ├── quicksort │ ├── quicksort-simple.c │ └── quicksort.c ├── struct │ └── struct.c ├── type │ ├── cast.c │ └── type.c └── unary │ └── unary.c └── web ├── web-backend ├── .gitignore ├── biz │ ├── handler │ │ ├── gen │ │ │ └── gen.go │ │ ├── ping │ │ │ └── ping.go │ │ ├── run │ │ │ └── run.go │ │ └── visual │ │ │ └── visual.go │ ├── model │ │ ├── model_gen │ │ │ └── gen.go │ │ ├── model_run │ │ │ └── run.go │ │ └── model_visual │ │ │ └── visual.go │ ├── router.go │ └── service │ │ ├── service_gen │ │ └── gen.go │ │ ├── service_run │ │ └── run.go │ │ └── service_visual │ │ └── visual.go ├── define │ ├── consts.go │ └── st.go ├── go.mod ├── go.sum ├── main.go ├── mw │ ├── cors.go │ ├── recover.go │ └── response.go └── utils │ ├── Rand │ └── rand.go │ └── response │ ├── failed.go │ ├── file.go │ ├── image.go │ ├── json.go │ ├── redirect.go │ ├── response.go │ └── type.go └── web-frontend ├── .env ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo.svg ├── manifest.json └── robots.txt └── src ├── App.css ├── App.js ├── App.test.js ├── components ├── AceEditor.js └── AntVG6.js ├── data └── example.js ├── index.css ├── index.js ├── logo.svg ├── reportWebVitals.js ├── setupTests.js └── utils ├── AST2Vis.js └── logger.js /.drone.yml: -------------------------------------------------------------------------------- 1 | kind: pipeline 2 | type: docker 3 | name: build 4 | concurrency: 5 | limit: 1 6 | platform: 7 | os: linux 8 | arch: amd64 9 | steps: 10 | - name: build 11 | image: docker 12 | commands: 13 | - docker build -t cc99 . 14 | volumes: 15 | - name: cache 16 | path: /var/run/docker.sock 17 | trigger: 18 | branch: 19 | - main 20 | event: 21 | - push 22 | - rollback 23 | volumes: 24 | - name: cache 25 | host: 26 | path: /var/run/docker.sock 27 | --- 28 | kind: pipeline 29 | type: exec 30 | name: deploy 31 | platform: 32 | os: linux 33 | arch: amd64 34 | clone: 35 | disable: true 36 | steps: 37 | - name: deploy 38 | commands: 39 | - echo hello 40 | - cd /app/cc99 41 | - docker-compose up -d 42 | - docker image prune -f 43 | - docker cp cc99-app-1:/srv/cc99 /srv/ 44 | - nginx -s reload 45 | depends_on: 46 | - build 47 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | test: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | 19 | - name: install minimal stable with rustfmt and clippy 20 | uses: actions-rs/toolchain@v1 21 | with: 22 | profile: minimal 23 | toolchain: stable 24 | components: rustfmt, clippy 25 | 26 | - name: install LLVM and Clang 27 | uses: KyleMayes/install-llvm-action@v1 28 | with: 29 | version: "13.0" 30 | directory: ${{ runner.temp }}/llvm 31 | 32 | - name: automated tests 33 | uses: actions-rs/cargo@v1 34 | with: 35 | command: test 36 | args: --color always 37 | 38 | - name: clippy check 39 | uses: actions-rs/clippy-check@v1 40 | with: 41 | token: ${{ secrets.GITHUB_TOKEN }} 42 | args: --features "local" 43 | 44 | - name: rustfmt check 45 | uses: mbrobbel/rustfmt-check@master 46 | with: 47 | token: ${{ secrets.GITHUB_TOKEN }} 48 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - name: install minimal stable 19 | uses: actions-rs/toolchain@v1 20 | with: 21 | profile: minimal 22 | toolchain: stable 23 | 24 | - name: install LLVM and Clang 25 | uses: KyleMayes/install-llvm-action@v1 26 | with: 27 | version: "13.0" 28 | directory: ${{ runner.temp }}/llvm 29 | 30 | - name: build 31 | uses: actions-rs/cargo@v1 32 | with: 33 | command: build 34 | args: --release 35 | 36 | - name: release 37 | uses: marvinpinto/action-automatic-releases@latest 38 | with: 39 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 40 | prerelease: false 41 | files: | 42 | ./target/release/cc99 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea 3 | .vscode 4 | /pkg 5 | *.s 6 | *.bc 7 | *.asm 8 | *.ll 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cc99" 3 | version = "0.2.0" 4 | authors = ["TO/GA", "RalXYZ", "Raynor"] 5 | edition = "2021" 6 | description = "A C-like language compiler" 7 | readme = "README.md" 8 | repository = "https://github.com/RalXYZ/cc99" 9 | license = "GPL-3.0" 10 | 11 | 12 | [lib] 13 | name = "cc99" 14 | path = "src/lib.rs" 15 | crate-type = ["cdylib", "lib"] 16 | 17 | [features] 18 | default = ["local"] 19 | local = ["clap","inkwell"] 20 | 21 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 22 | 23 | [dependencies] 24 | pest = "2.1.3" 25 | pest_derive = "2.1.0" 26 | serde = { version = "1.0", features = ["derive"] } 27 | walkdir = "2.3.2" 28 | serde_json = "1.0.59" 29 | clap = { version = "3.1.8", features = ["derive"], optional = true } 30 | inkwell = { version="0.1.0-beta.4" , features = ["llvm13-0"], optional = true} 31 | anyhow = "1.0" 32 | thiserror = "1.0" 33 | typed-arena = "2.0.1" 34 | escape_string = "0.1.2" 35 | codespan-reporting = "0.11.1" 36 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM registry.cn-hangzhou.aliyuncs.com/raynor/rust-npm:1.0.0 as builder 2 | 3 | WORKDIR /app 4 | COPY . . 5 | ENV LLVM_SYS_130_PREFIX /usr 6 | RUN apt install libz-dev -y 7 | RUN cargo build --package cc99 --bin cc99 --release 8 | RUN cd web/web-frontend && npm install && npm run build && mv build /srv && mv /srv/build /srv/cc99 9 | 10 | 11 | FROM golang:1.18-bullseye as prod 12 | EXPOSE 5001 13 | RUN mkdir /backend && mkdir /app 14 | WORKDIR /backend 15 | RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list 16 | RUN apt update 17 | # cache deps before building and copying source so that we don't need to re-download as muchw 18 | # and so that source changes don't invalidate our downloaded layer 19 | ENV GO111MODULE=on \ 20 | GOPROXY=https://goproxy.cn,direct 21 | COPY ./web/web-backend/go.mod go.mod 22 | COPY ./web/web-backend/go.sum go.sum 23 | RUN go mod download 24 | RUN go mod tidy 25 | # src code 26 | COPY ./web/web-backend . 27 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o cc99-backend . 28 | RUN chmod +x cc99-backend 29 | 30 | 31 | #copy frontend and cc99 and header file 32 | WORKDIR /app 33 | COPY --from=builder /srv/cc99 /srv/cc99 34 | COPY --from=builder /app/target/release/cc99 . 35 | COPY --from=builder /app/include ./include 36 | 37 | WORKDIR /backend 38 | RUN mv /app/cc99 /backend 39 | RUN mv /app/include /backend 40 | RUN apt install -y clang-11 41 | RUN ln -s /usr/bin/clang-11 /usr/bin/clang 42 | ENV PATH "$PATH:/backend" 43 | ENV TZ=Asia/Shanghai 44 | ENTRYPOINT ["/backend/cc99-backend"] 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cc99 2 | 3 | *cc99* (not [cc98.org](https://cc98.org)) is a C-like language compiler, which is the final project of ZJU Compiler Principle course. It supports many of the C99 language syntax, and can compile source code to executable file, assembly language, abstract syntax tree (json style) and so on. 4 | 5 | cc99 can be used on Linux, Mac, even windows (unofficial support), anyone can build from source code or download binary file to have a try! 6 | 7 | ![cc99](https://raw.githubusercontent.com/RalXYZ/repo-pictures/main/cc99/cc99.png) 8 | 9 | ## Supported Syntax 10 | 11 | ### Types 12 | - void 13 | - char, short, int, long, long long 14 | - unsigned char, unsigned short, unsigned int, unsigned long, unsigned long long 15 | - \_Bool 16 | - float, double 17 | - pointer (any basic type) 18 | - array (any basic type, any dimension) 19 | - function 20 | - struct 21 | 22 | ### Statements 23 | - compound (which means `{}`) 24 | - if, else 25 | - while, dowhile, for, break, continue 26 | - return 27 | 28 | ### Expressions 29 | - assignment: `= += -= *= /= %= &= |= ^= >>= <<=` 30 | - unary: `++a --a a++ a-- +a -a |a ^a *a &a sizeof(a)` 31 | - binary: `a+b a-b a*b a/b a%b a|b a^b a^b a>>b a<b a<=b a>=b a,b` 32 | - function call: `a(10,20,30)` 33 | - type cast: `identifier as T` 34 | - conditional: `a>10?1:0` 35 | - sizeof: `sizeof(a), sizeof(int), sizeof(int*)` 36 | - member of struct: `struct course c; c.name` 37 | - array subscript: `int a[10]; a[0]` 38 | - identifier: `int a` 39 | - literal (any base type): `123, 123.123, 123l, "123", '1'` 40 | 41 | 42 | 43 | ## Get cc99 44 | 45 | Before we start, make sure you have already installed [gcc](https://gcc.gnu.org/) or [clang](https://clang.llvm.org/), because cc99 need one the them to link object files. You can click href and install one of them. 46 | 47 | There are three ways to get cc99: 48 | 49 | 1. Download from [releases](https://github.com/RalXYZ/cc99/releases). We just provide Linux (x86_64) and MacOS (Intel chip) version. As you know, they can cover almost all of develop situations. 50 | 51 | 2. Build with [Docker](https://www.docker.com/). We provide a `Dockerfile` at root directory which can build cc99. It includes dir, web-frontend, web-backend into a Ubuntu image. You can get your own image by following steps: 52 | 53 | ~~~bash 54 | git clone https://github.com/RalXYZ/cc99.git 55 | cd cc99 56 | docker build . -t cc99:latest 57 | # now this image contains all target files, you can use `docker cp` to copy them out or start a container! 58 | 59 | # start a container named cc99_all, bind port and mount volumes 60 | docker run --name cc99_all -p 6001:5001 -v ./data/runtime:/backend/runtime -d cc99:latest 61 | 62 | # get executable file 63 | docker cp cc99_all:/backend/cc99 . 64 | # get include files 65 | docker cp cc99_all:/backend/include . 66 | ~~~ 67 | 68 | 3. Compile from source code. Here is a sample (**on ubuntu:20.04**): 69 | 70 | ~~~bash 71 | git clone https://github.com/RalXYZ/cc99.git 72 | cd cc99 73 | sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \ 74 | apt-get update && apt install build-essential git curl libssl-dev wget libz-dev -y 75 | 76 | # install rust toolchains 77 | curl https://sh.rustup.rs -sSf | sh -s -- -y 78 | 79 | # use tuna registry to speed up 80 | echo '[source.crates-io]\n\ 81 | registry = "https://github.com/rust-lang/crates.io-index"\n\ 82 | replace-with = "tuna"\n\ 83 | [source.tuna]\n\ 84 | registry = "https://mirrors.tuna.tsinghua.edu.cn/git/crates.io-index.git"' > /root/.cargo/config 85 | 86 | # add llvm registry 87 | echo 'deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-13 main' >> /etc/apt/sources.list 88 | 89 | # optional 90 | curl -LO http://archive.ubuntu.com/ubuntu/pool/main/libf/libffi/libffi6_3.2.1-8_amd64.deb 91 | dpkg -i libffi6_3.2.1-8_amd64.deb 92 | 93 | # install llvm 94 | wget -O a https://apt.llvm.org/llvm-snapshot.gpg.key && apt-key add a 95 | apt update -y && apt upgrade -y && apt install -y llvm-13 llvm-13-dev libllvm13 llvm-13-runtime libclang-common-13-dev 96 | 97 | # a necessary ENV 98 | export LLVM_SYS_130_PREFIX=/usr 99 | 100 | # build it! 101 | cargo build --package cc99 --bin cc99 --release 102 | ~~~ 103 | 104 | 105 | 106 | ## Usage 107 | 108 | At first you need write a source code file (in cc99 syntax, we will call it `a.c` in the following description). Then you can simply run: 109 | 110 | ~~~bash 111 | cc99 a.c 112 | # or 113 | cc99 a.c -o a 114 | 115 | # you will get an executable file named `a`, just run it! 116 | ./a 117 | # happy coding! 118 | ~~~ 119 | 120 | cc99 supports many command line arguments, you can simply use `cc99 --help` to find out: 121 | 122 | - without any extra options: executable file, using clang(default) or gcc to link 123 | - `-c` or `--assemble`: Compile and assemble, but do not link 124 | - `-S` or `--compile`: Compile only; do not assemble or link 125 | - `-b` or `--bitcode`: Generate LLVM bitcode only 126 | - `-p` or `--parse`: Preprocess and parse; do not compile, assemble or link 127 | - `-V` or `--visual`: Convert stdin as code file and generate AST format to stdout 128 | - `-E` or `--expand`: Preprocess only; do not parse, compile, assemble or link 129 | 130 | In addition, we provide some useful options: 131 | 132 | - `-O` or `--opt-level ` : Optimization level, from 0 to 3 [default: 0]. Like `gcc` or `clang `provided, even have a more aggressive strategy than `gcc` and `clang` 133 | - `-i` or `--include`: Add directories `, , `(from left to right) to the list of directories, and cc99 will search for header files during preprocessing. Absolute paths are strongly recommended. 134 | 135 | ### Compile/Run Online, Visualize AST 136 | 137 | We provide a [demo playground](https://cc99.raynor.top), you can play with your source code online and compile/run with a provided backend. Also you can observe the AST (abstract syntax tree) of yout source code. 138 | 139 | `web-frontend` uses `react.js` and `antv`, `web-backend` uses `golang` and `gin`. These two modules can be found in `./web` directory. 140 | 141 | 142 | 143 | ## Compare with C99 Standard 144 | 145 | To be honest, cc99 can parse almost all of the standard C99 language syntax, but we cannot generate some of them to bitcode. What's more, there exists some differences between our language standard and C99 standard. It is strongly recommended to read this chapter before having a try. 146 | 147 | ### Preprocessing 148 | 149 | ~~~c 150 | #include 151 | #ifdef _DEF_CC99_1 152 | #define CC99 C99 153 | #endif 154 | typedef double REAL; 155 | ~~~ 156 | 157 | We use four preprocess steps: 158 | 159 | - First pass: Process all line continuation characters. Add a newline if there is no newline at the end of the file. 160 | 161 | - Second pass: Delete all comments. 162 | 163 | - Third pass: Process all preprocessing directives like `#include`, `#define`, `#if` etc. 164 | 165 | - Fourth pass: Merge adjacent string literals, 166 | 167 | > E.g. char s[] = “\033[0m””Hello”; => char s[] = “\033[0mHello” 168 | 169 | We provide three simple **header files**, which can be found in `/inlcude` directory. These files can cover most situations and you can try them as you like, but don't forget to include them using `#include ` ! 170 | 171 | You can also add other C runtime functions to `/include` dir, all your need is add a function signature, but there are something you need to notice: 172 | 173 | - Support variable parameter, like `scanf` and `printf` 174 | - Support parameter qualifier, you can add any standard qualifiers like `const `, `atomic` and so on 175 | - Don't support `size_t`, you must change `size_t` to `long (8 bytes)` 176 | - Function must be contained in standard glibc runtime 177 | - Welcome to submit PR to add them! 178 | 179 | ### Multidimensional Arrays and Multidimensional Pointers 180 | 181 | ~~~c 182 | int a[10][20][30]; 183 | a[0][0][1] = 123; 184 | 185 | int *p = a[0][0]; 186 | p++; 187 | int **pp = &(p + 1); 188 | pp = malloc(sizeof(int*) * 10); 189 | pp[0] = malloc(sizeof(int) * 10); 190 | pp[0][0] = 1; 191 | ~~~ 192 | 193 | You can use arrays and pointers as you like, since we already support most of arrays and pointer operations. But be attention: 194 | 195 | - Don't convert multidimensional arrays directly into multidimensional pointers, it's illegal 196 | 197 | ~~~c 198 | int array[10][10]; 199 | int **ptr = array; // illegal!! 200 | ~~~ 201 | 202 | - Best Practices for multidimensional pointer 203 | 204 | ~~~c 205 | #include 206 | int ***p; 207 | p = malloc(sizeof(int**) * 10); 208 | for(int i = 0; i < 10; i++){ 209 | p[i] = malloc(sizeof(int*) * 10); 210 | } 211 | for(int i = 0; i < 10; i++){ 212 | for(int j = 0; j < 10; j++){ 213 | p[i][j] = malloc(sizeof(int) * 10); 214 | } 215 | } 216 | p[i][j][k] = 123; 217 | ~~~ 218 | 219 | ### Struct Support (Partial) 220 | 221 | You can define a struct in global scope, but can't define it in function!(maybe later we will support). Here is a usage: 222 | 223 | ~~~c 224 | #include 225 | struct course { 226 | char *name; 227 | int credit; 228 | char ***pre_course; 229 | int pre_status_num; 230 | int *pre_course_num; 231 | int grade; 232 | }; 233 | int main(){ 234 | struct course c; 235 | c.name = malloc(sizeof(char) * 10); 236 | c.credit = 10; 237 | 238 | int credit = c.credit; 239 | } 240 | ~~~ 241 | 242 | In addition, we also support **access variable from pointer**, which means the following code is legal: 243 | 244 | ~~~c 245 | struct course c; 246 | struct course *ptr = &c; 247 | char *t = ptr->name; 248 | ~~~ 249 | 250 | But, we don't support **struct initial list** now! 251 | 252 | ~~~c 253 | struct course c = {"123",123,....} // illegal! 254 | ~~~ 255 | 256 | ### Type Cast 257 | 258 | In standard c syntax, it use truncation to deal it, like following 259 | 260 | ~~~c 261 | long long int a = 114514114514; 262 | int b = a; // lose information but allowed 263 | ~~~ 264 | 265 | But in cc99, every basic type has a rank, and we deny **implicit type cast** from a high rank to a low rank. Here is the rank table: 266 | 267 | | Name | Rank | 268 | | ----------------------------- | ---- | 269 | | void | 0 | 270 | | _Bool | 1 | 271 | | char, unsigned char | 2 | 272 | | short, unsigned shot | 3 | 273 | | int, unsigned int | 4 | 274 | | long, unsigned long | 5 | 275 | | long long, unsigned long long | 6 | 276 | | float | 7 | 277 | | double | 8 | 278 | 279 | You can use explicit type cast to convert high rank to low rank 280 | 281 | ~~~c 282 | double a = 123.123; 283 | float b = a as float; // we use `var as type` syntax 284 | ~~~ 285 | 286 | Yes, we use `var as type` syntax for explicit type cast 287 | 288 | ~~~c 289 | double a = 123.123; 290 | float b = (float)a; // illegal! 291 | float c = float(a); // illegal! 292 | float d = a as float; // legal 293 | ~~~ 294 | 295 | ### Function Hoisting and Global Variable Promotion 296 | 297 | ~~~c 298 | int main(){ 299 | int s = sum(1, 2); // legal, all funcitons will hoist to top 300 | int e = d + 10; // legal, all global variables will hoist to top 301 | } 302 | int sum(int a, int b){ 303 | return a + b; 304 | } 305 | int d = 10; 306 | ~~~ 307 | -------------------------------------------------------------------------------- /include/stdio.h: -------------------------------------------------------------------------------- 1 | #ifndef _STDIO_H 2 | #define _STDIO_H 1 3 | 4 | extern int scanf(const char *__format, ...); 5 | extern int printf(const char *__format, ...); 6 | 7 | #endif /* included. */ 8 | -------------------------------------------------------------------------------- /include/stdlib.h: -------------------------------------------------------------------------------- 1 | #ifndef _STDLIB_H 2 | #define _STDLIB_H 1 3 | 4 | void *malloc(long size); 5 | 6 | #endif /* included. */ 7 | -------------------------------------------------------------------------------- /include/string.h: -------------------------------------------------------------------------------- 1 | #ifndef _STRING_H 2 | #define _STRING_H 1 3 | 4 | void *memchr(const void *str, int c, long n); 5 | 6 | int memcmp(const void *str1, const void *str2, long n); 7 | 8 | void *memcpy(void *dest, const void *src, long n); 9 | 10 | void *memmove(void *dest, const void *src, long n); 11 | 12 | void *memset(void *str, int c, long n); 13 | 14 | char *strcat(char *dest, const char *src); 15 | 16 | char *strncat(char *dest, const char *src, long n); 17 | 18 | char *strchr(const char *str, int c); 19 | 20 | int strcmp(const char *str1, const char *str2); 21 | 22 | int strncmp(const char *str1, const char *str2, long n); 23 | 24 | int strcoll(const char *str1, const char *str2); 25 | 26 | char *strcpy(char *dest, const char *src); 27 | 28 | char *strncpy(char *dest, const char *src, long n); 29 | 30 | long strcspn(const char *str1, const char *str2); 31 | 32 | char *strerror(int errnum); 33 | 34 | long strlen(const char *str); 35 | 36 | char *strpbrk(const char *str1, const char *str2); 37 | 38 | char *strrchr(const char *str, int c); 39 | 40 | long strspn(const char *str1, const char *str2); 41 | 42 | char *strstr(const char *haystack, const char *needle); 43 | 44 | char *strtok(char *str, const char *delim); 45 | 46 | long strxfrm(char *dest, const char *src, long n); 47 | 48 | #endif /* included. */ -------------------------------------------------------------------------------- /src/ast/mod.rs: -------------------------------------------------------------------------------- 1 | mod operations; 2 | mod span; 3 | mod tree; 4 | mod types; 5 | 6 | pub use operations::*; 7 | pub use span::*; 8 | pub use tree::*; 9 | pub use types::*; 10 | -------------------------------------------------------------------------------- /src/ast/operations.rs: -------------------------------------------------------------------------------- 1 | use super::span::*; 2 | use serde::{Serialize, Serializer}; 3 | 4 | #[derive(Debug, PartialEq, Clone)] 5 | pub struct AssignOperation { 6 | pub node: AssignOperationEnum, 7 | pub span: Span, 8 | } 9 | 10 | #[derive(Serialize, Debug, PartialEq, Clone)] 11 | pub enum AssignOperationEnum { 12 | Naive, 13 | Addition, 14 | Subtraction, 15 | Multiplication, 16 | Division, 17 | Modulo, 18 | BitwiseAnd, 19 | BitwiseOr, 20 | BitwiseXor, 21 | LeftShift, 22 | RightShift, 23 | } 24 | 25 | #[derive(Debug, PartialEq, Clone)] 26 | pub struct UnaryOperation { 27 | pub node: UnaryOperationEnum, 28 | pub span: Span, 29 | } 30 | 31 | #[derive(Serialize, Debug, PartialEq, Clone)] 32 | pub enum UnaryOperationEnum { 33 | // increment, decrement 34 | PrefixIncrement, 35 | PrefixDecrement, 36 | PostfixIncrement, 37 | PostfixDecrement, 38 | // arithmetic 39 | UnaryPlus, 40 | UnaryMinus, 41 | BitwiseNot, 42 | // logical 43 | LogicalNot, 44 | // member access 45 | Reference, 46 | Dereference, 47 | // other 48 | SizeofExpr, 49 | } 50 | 51 | #[derive(Debug, PartialEq, Clone)] 52 | pub struct BinaryOperation { 53 | pub node: BinaryOperationEnum, 54 | pub span: Span, 55 | } 56 | 57 | #[derive(Serialize, Debug, PartialEq, Clone)] 58 | pub enum BinaryOperationEnum { 59 | // arithmetic 60 | Addition, 61 | Subtraction, 62 | Multiplication, 63 | Division, 64 | Modulo, 65 | BitwiseAnd, 66 | BitwiseOr, 67 | BitwiseXor, 68 | LeftShift, 69 | RightShift, 70 | // logical 71 | LogicalAnd, 72 | LogicalOr, 73 | // comparison 74 | Equal, 75 | NotEqual, 76 | LessThan, 77 | LessThanOrEqual, 78 | GreaterThan, 79 | GreaterThanOrEqual, 80 | // other 81 | Comma, 82 | } 83 | 84 | impl Default for AssignOperation { 85 | fn default() -> Self { 86 | AssignOperation { 87 | node: AssignOperationEnum::Naive, 88 | span: Default::default(), 89 | } 90 | } 91 | } 92 | 93 | impl Default for UnaryOperation { 94 | fn default() -> Self { 95 | UnaryOperation { 96 | node: UnaryOperationEnum::PrefixIncrement, 97 | span: Default::default(), 98 | } 99 | } 100 | } 101 | 102 | impl Default for BinaryOperation { 103 | fn default() -> Self { 104 | BinaryOperation { 105 | node: BinaryOperationEnum::Comma, 106 | span: Default::default(), 107 | } 108 | } 109 | } 110 | 111 | impl Serialize for AssignOperation { 112 | fn serialize(&self, serializer: S) -> Result 113 | where 114 | S: Serializer, 115 | { 116 | self.node.serialize(serializer) 117 | } 118 | } 119 | 120 | impl Serialize for UnaryOperation { 121 | fn serialize(&self, serializer: S) -> Result 122 | where 123 | S: Serializer, 124 | { 125 | self.node.serialize(serializer) 126 | } 127 | } 128 | 129 | impl Serialize for BinaryOperation { 130 | fn serialize(&self, serializer: S) -> Result 131 | where 132 | S: Serializer, 133 | { 134 | self.node.serialize(serializer) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/ast/span.rs: -------------------------------------------------------------------------------- 1 | use std::convert::From; 2 | 3 | #[derive(Default, Debug, Clone, Copy, PartialEq)] 4 | pub struct Span { 5 | pub start: usize, 6 | pub end: usize, 7 | } 8 | 9 | impl Span { 10 | pub fn new(start: usize, end: usize) -> Span { 11 | Span { start, end } 12 | } 13 | } 14 | 15 | impl From> for Span { 16 | fn from(other: pest::Span<'_>) -> Self { 17 | Span { 18 | start: other.start(), 19 | end: other.end(), 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/ast/tree.rs: -------------------------------------------------------------------------------- 1 | use serde::{Serialize, Serializer}; 2 | use std::fmt; 3 | 4 | use super::operations::*; 5 | use super::span::*; 6 | use super::types::*; 7 | 8 | #[derive(Serialize, Debug, PartialEq, Clone)] 9 | pub enum AST { 10 | GlobalDeclaration(Vec), 11 | } 12 | 13 | #[derive(Debug, PartialEq, Clone)] 14 | pub struct Declaration { 15 | pub node: DeclarationEnum, 16 | pub span: Span, 17 | } 18 | 19 | #[derive(Serialize, Debug, PartialEq, Clone)] 20 | pub enum DeclarationEnum { 21 | Declaration( 22 | Type, 23 | /// identifier (if it's a struct/union declaration, it might be None) 24 | Option, 25 | /// initializer 26 | Option>, 27 | ), 28 | FunctionDefinition( 29 | Vec, 30 | StorageClassSpecifier, 31 | /// return type 32 | Box, 33 | /// identifier 34 | String, 35 | /// parameters and their names 36 | Vec<(BasicType, Option)>, 37 | /// is variadic 38 | bool, 39 | /// body 40 | Statement, 41 | ), 42 | } 43 | 44 | #[derive(Debug, PartialEq, Clone)] 45 | pub struct Statement { 46 | pub node: StatementEnum, 47 | pub span: Span, 48 | } 49 | 50 | #[derive(Serialize, Debug, PartialEq, Clone)] 51 | pub enum StatementEnum { 52 | // labeled statement 53 | Labeled(String, Box), 54 | Case( 55 | /// None represents default case 56 | Option>, 57 | Box, 58 | ), 59 | // compound statement 60 | Compound(Vec), 61 | // expression statement 62 | Expression(Box), 63 | // selection statement 64 | If( 65 | Box, 66 | /// true statement 67 | Box, 68 | /// false statement 69 | Option>, 70 | ), 71 | Switch(Box, Box), 72 | // iteration statement 73 | While(Box, Box), 74 | DoWhile(Box, Box), 75 | For( 76 | /// initialize clause 77 | Option>, 78 | /// condition expression 79 | Option>, 80 | /// iteration expression 81 | Option>, 82 | /// loop statement 83 | Box, 84 | ), 85 | // jump statement 86 | Break, 87 | Continue, 88 | Return(Option>), 89 | Goto(String), 90 | } 91 | 92 | #[derive(Debug, PartialEq, Clone)] 93 | pub struct Expression { 94 | pub node: ExpressionEnum, 95 | pub span: Span, 96 | } 97 | 98 | #[derive(Serialize, Debug, PartialEq, Clone)] 99 | pub enum ExpressionEnum { 100 | Assignment( 101 | AssignOperation, 102 | /// left hand side 103 | Box, 104 | /// right hand side 105 | Box, 106 | ), 107 | Unary(UnaryOperation, Box), 108 | Binary( 109 | BinaryOperation, 110 | /// left hand side 111 | Box, 112 | /// right hand side 113 | Box, 114 | ), 115 | FunctionCall( 116 | /// function 117 | Box, 118 | /// arguments 119 | Vec, 120 | ), 121 | TypeCast(BasicType, Box), 122 | Conditional( 123 | /// condition 124 | Box, 125 | /// true expression 126 | Box, 127 | /// false expression 128 | Box, 129 | ), 130 | SizeofType(BasicType), 131 | MemberOfObject( 132 | /// object 133 | Box, 134 | /// member name 135 | String, 136 | ), 137 | MemberOfPointer( 138 | /// pointer 139 | Box, 140 | /// member name 141 | String, 142 | ), 143 | ArraySubscript( 144 | /// array 145 | Box, 146 | /// index 147 | Vec, 148 | ), 149 | 150 | Identifier(String), 151 | IntegerConstant(i32), 152 | UnsignedIntegerConstant(u32), 153 | LongConstant(i64), 154 | UnsignedLongConstant(u64), 155 | LongLongConstant(i64), 156 | UnsignedLongLongConstant(u64), 157 | CharacterConstant(char), 158 | FloatConstant(f32), 159 | DoubleConstant(f64), 160 | StringLiteral(String), 161 | Empty, 162 | } 163 | 164 | #[derive(Debug, PartialEq, Clone)] 165 | pub struct ForInitClause { 166 | pub node: ForInitClauseEnum, 167 | pub span: Span, 168 | } 169 | 170 | #[derive(Serialize, Debug, PartialEq, Clone)] 171 | pub enum ForInitClauseEnum { 172 | Expression(Expression), 173 | ForDeclaration(Vec), 174 | } 175 | 176 | #[derive(Debug, PartialEq, Clone)] 177 | pub struct StatementOrDeclaration { 178 | pub node: StatementOrDeclarationEnum, 179 | pub span: Span, 180 | } 181 | 182 | #[derive(Serialize, Debug, PartialEq, Clone)] 183 | pub enum StatementOrDeclarationEnum { 184 | Statement(Statement), 185 | LocalDeclaration(Declaration), 186 | } 187 | 188 | impl Default for Statement { 189 | fn default() -> Self { 190 | Statement { 191 | node: StatementEnum::Expression(Box::new(Default::default())), 192 | span: Default::default(), 193 | } 194 | } 195 | } 196 | 197 | impl Default for Expression { 198 | fn default() -> Self { 199 | Expression { 200 | node: ExpressionEnum::Empty, 201 | span: Default::default(), 202 | } 203 | } 204 | } 205 | 206 | impl fmt::Display for Expression { 207 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 208 | write!(f, "{:?}", self) 209 | } 210 | } 211 | 212 | impl Serialize for Declaration { 213 | fn serialize(&self, serializer: S) -> Result 214 | where 215 | S: Serializer, 216 | { 217 | self.node.serialize(serializer) 218 | } 219 | } 220 | 221 | impl Serialize for Statement { 222 | fn serialize(&self, serializer: S) -> Result 223 | where 224 | S: Serializer, 225 | { 226 | self.node.serialize(serializer) 227 | } 228 | } 229 | 230 | impl Serialize for Expression { 231 | fn serialize(&self, serializer: S) -> Result 232 | where 233 | S: Serializer, 234 | { 235 | self.node.serialize(serializer) 236 | } 237 | } 238 | 239 | impl Serialize for ForInitClause { 240 | fn serialize(&self, serializer: S) -> Result 241 | where 242 | S: Serializer, 243 | { 244 | self.node.serialize(serializer) 245 | } 246 | } 247 | 248 | impl Serialize for StatementOrDeclaration { 249 | fn serialize(&self, serializer: S) -> Result 250 | where 251 | S: Serializer, 252 | { 253 | self.node.serialize(serializer) 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/ast/types.rs: -------------------------------------------------------------------------------- 1 | use super::super::utils::CompileErr as CE; 2 | use super::*; 3 | use std::collections::HashMap; 4 | use std::fmt; 5 | 6 | use serde::Serialize; 7 | 8 | #[derive(Serialize, Debug, PartialEq, Clone, Default)] 9 | pub struct Type { 10 | pub function_specifier: Vec, 11 | pub storage_class_specifier: StorageClassSpecifier, 12 | pub basic_type: BasicType, 13 | } 14 | 15 | #[derive(Serialize, Debug, PartialEq, Clone)] 16 | pub enum StorageClassSpecifier { 17 | Typedef, 18 | Extern, 19 | Static, 20 | ThreadLocal, 21 | Auto, 22 | Register, 23 | } 24 | 25 | #[derive(Serialize, Debug, PartialEq, Clone)] 26 | pub enum TypeQualifier { 27 | Const, 28 | Volatile, 29 | Restrict, 30 | Atomic, 31 | } 32 | 33 | #[derive(Serialize, Debug, PartialEq, Clone)] 34 | pub enum FunctionSpecifier { 35 | Inline, 36 | Noreturn, 37 | } 38 | 39 | #[derive(Serialize, Debug, PartialEq, Clone, Default)] 40 | pub struct BasicType { 41 | pub qualifier: Vec, 42 | pub base_type: BaseType, 43 | } 44 | 45 | #[derive(Serialize, Debug, PartialEq, Clone)] 46 | pub enum BaseType { 47 | Void, 48 | SignedInteger(IntegerType), 49 | UnsignedInteger(IntegerType), 50 | Bool, 51 | Float, 52 | Double, 53 | Pointer(Box), 54 | Array( 55 | /// element type 56 | Box, 57 | /// array length, from high-dimension to low-dimension 58 | Vec, 59 | ), 60 | Function( 61 | /// return type 62 | Box, 63 | /// parameters' types 64 | Vec, 65 | /// is variadic or not 66 | bool, 67 | ), 68 | Struct( 69 | /// struct name 70 | Option, 71 | /// struct members 72 | Option>, 73 | ), 74 | Union( 75 | /// union name 76 | Option, 77 | /// union members 78 | Option>, 79 | ), 80 | /// a name introduced by typedef/struct... 81 | Identifier(String), 82 | } 83 | 84 | #[derive(Serialize, Debug, PartialEq, Clone)] 85 | pub enum IntegerType { 86 | Short, 87 | Char, 88 | Int, 89 | Long, 90 | LongLong, 91 | } 92 | 93 | #[derive(Serialize, Debug, PartialEq, Clone)] 94 | pub struct StructMember { 95 | pub member_name: String, 96 | pub member_type: BasicType, 97 | } 98 | 99 | impl Default for BaseType { 100 | fn default() -> Self { 101 | BaseType::SignedInteger(IntegerType::Int) 102 | } 103 | } 104 | 105 | impl<'ctx> BasicType { 106 | pub fn is_const(&self) -> bool { 107 | self.qualifier 108 | .iter() 109 | .any(|x| matches!(x, TypeQualifier::Const)) 110 | } 111 | } 112 | 113 | impl<'ctx> BaseType { 114 | fn cast_rank(&self, typedef_map: &HashMap) -> i32 { 115 | let true_self = match *self { 116 | BaseType::Identifier(ref name) => { 117 | if let Some(typedef) = typedef_map.get(name) { 118 | &typedef.base_type 119 | } else { 120 | unreachable!() 121 | } 122 | } 123 | _ => self, 124 | }; 125 | match *true_self { 126 | BaseType::Void => 0, 127 | BaseType::Bool => 1, 128 | BaseType::SignedInteger(IntegerType::Char) => 2, 129 | BaseType::UnsignedInteger(IntegerType::Char) => 2, 130 | BaseType::SignedInteger(IntegerType::Short) => 3, 131 | BaseType::UnsignedInteger(IntegerType::Short) => 3, 132 | BaseType::SignedInteger(IntegerType::Int) => 4, 133 | BaseType::UnsignedInteger(IntegerType::Int) => 4, 134 | BaseType::SignedInteger(IntegerType::Long) => 5, 135 | BaseType::UnsignedInteger(IntegerType::Long) => 5, 136 | BaseType::SignedInteger(IntegerType::LongLong) => 6, 137 | BaseType::UnsignedInteger(IntegerType::LongLong) => 6, 138 | BaseType::Float => 7, 139 | BaseType::Double => 8, 140 | _ => panic!(), 141 | } 142 | } 143 | 144 | pub(crate) fn upcast( 145 | lhs: &BaseType, 146 | rhs: &BaseType, 147 | typedef_map: &HashMap, 148 | ) -> Result { 149 | if lhs.cast_rank(typedef_map) >= rhs.cast_rank(typedef_map) { 150 | Ok(lhs.clone()) 151 | } else { 152 | Ok(rhs.clone()) 153 | } 154 | } 155 | 156 | pub(crate) fn equal_discarding_qualifiers( 157 | &self, 158 | rhs: &BaseType, 159 | typedef_map: &HashMap, 160 | ) -> bool { 161 | let true_self = match *self { 162 | BaseType::Identifier(ref name) => { 163 | if let Some(typedef) = typedef_map.get(name) { 164 | &typedef.base_type 165 | } else { 166 | unreachable!() 167 | } 168 | } 169 | _ => self, 170 | }; 171 | let true_rhs = match *rhs { 172 | BaseType::Identifier(ref name) => { 173 | if let Some(typedef) = typedef_map.get(name) { 174 | &typedef.base_type 175 | } else { 176 | unreachable!() 177 | } 178 | } 179 | _ => rhs, 180 | }; 181 | 182 | if true_self == true_rhs { 183 | return true; 184 | } 185 | 186 | if let BaseType::Pointer(lhs_inner) = true_self { 187 | if let BaseType::Pointer(rhs_inner) = true_rhs { 188 | return lhs_inner 189 | .base_type 190 | .equal_discarding_qualifiers(&rhs_inner.base_type, typedef_map); 191 | } 192 | } 193 | 194 | false 195 | } 196 | 197 | pub fn test_cast( 198 | &self, 199 | dest: &BaseType, 200 | span: Span, 201 | typedef_map: &HashMap, 202 | ) -> Result<(), CE> { 203 | let true_self = match *self { 204 | BaseType::Identifier(ref name) => { 205 | if let Some(typedef) = typedef_map.get(name) { 206 | &typedef.base_type 207 | } else { 208 | unreachable!() 209 | } 210 | } 211 | _ => self, 212 | }; 213 | let true_dest = match *dest { 214 | BaseType::Identifier(ref name) => { 215 | if let Some(typedef) = typedef_map.get(name) { 216 | &typedef.base_type 217 | } else { 218 | unreachable!() 219 | } 220 | } 221 | _ => dest, 222 | }; 223 | 224 | // same type, directly cast 225 | if true_self == true_dest { 226 | return Ok(()); 227 | } 228 | 229 | if let (BaseType::Pointer(_), BaseType::Pointer(_)) = (true_self, true_dest) { 230 | // TODO: handle const 231 | return Ok(()); 232 | } 233 | 234 | if let (BaseType::Array(lhs_type, lhs_expr), BaseType::Pointer(rhs_ptr)) = 235 | (true_self, true_dest) 236 | { 237 | if lhs_expr.len() != 1 { 238 | return Err(CE::invalid_default_cast( 239 | self.to_string(), 240 | dest.to_string(), 241 | span, 242 | )); 243 | } 244 | //make sure they are both basic type(not pointer or array) 245 | lhs_type.base_type.cast_rank(typedef_map); 246 | rhs_ptr.base_type.cast_rank(typedef_map); 247 | return Ok(()); 248 | } 249 | 250 | if let BaseType::Pointer(_) = true_self { 251 | return Err(CE::invalid_default_cast( 252 | self.to_string(), 253 | dest.to_string(), 254 | span, 255 | )); 256 | } 257 | if let BaseType::Pointer(_) = true_dest { 258 | return Err(CE::invalid_default_cast( 259 | self.to_string(), 260 | dest.to_string(), 261 | span, 262 | )); 263 | } 264 | 265 | if self.cast_rank(typedef_map) < dest.cast_rank(typedef_map) { 266 | return Ok(()); 267 | } 268 | 269 | Err(CE::invalid_default_cast( 270 | self.to_string(), 271 | dest.to_string(), 272 | span, 273 | )) 274 | } 275 | } 276 | 277 | impl fmt::Display for BaseType { 278 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 279 | match self { 280 | BaseType::Void => write!(f, "void"), 281 | BaseType::SignedInteger(IntegerType::Char) => write!(f, "char"), 282 | BaseType::UnsignedInteger(IntegerType::Char) => write!(f, "unsigned char"), 283 | BaseType::SignedInteger(IntegerType::Short) => write!(f, "short"), 284 | BaseType::UnsignedInteger(IntegerType::Short) => write!(f, "unsigned short"), 285 | BaseType::SignedInteger(IntegerType::Int) => write!(f, "int"), 286 | BaseType::UnsignedInteger(IntegerType::Int) => write!(f, "unsigned int"), 287 | BaseType::SignedInteger(IntegerType::Long) => write!(f, "long"), 288 | BaseType::UnsignedInteger(IntegerType::Long) => write!(f, "unsigned long"), 289 | BaseType::SignedInteger(IntegerType::LongLong) => write!(f, "long long"), 290 | BaseType::UnsignedInteger(IntegerType::LongLong) => write!(f, "unsigned long long"), 291 | BaseType::Bool => write!(f, "_Bool"), 292 | BaseType::Float => write!(f, "float"), 293 | BaseType::Double => write!(f, "double"), 294 | BaseType::Pointer(inner) => { 295 | write!(f, "{}", inner.base_type)?; 296 | write!(f, "*") 297 | } 298 | _ => write!(f, "{:?}", self), 299 | } 300 | } 301 | } 302 | 303 | impl Default for StorageClassSpecifier { 304 | fn default() -> Self { 305 | StorageClassSpecifier::Auto 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /src/generator/cast_inst.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::{BaseType as Ty, IntegerType as IT, Span}; 2 | use crate::generator::Generator; 3 | use crate::utils::CompileErr as CE; 4 | use inkwell::values::InstructionOpcode as Op; 5 | 6 | impl<'ctx> Generator<'ctx> { 7 | pub fn gen_cast_llvm_instruction(&self, curr: &Ty, dest: &Ty, span: Span) -> Result { 8 | let instruction = match curr { 9 | // char 10 | Ty::UnsignedInteger(IT::Char) => match dest { 11 | Ty::SignedInteger(_) => Op::SExt, 12 | Ty::UnsignedInteger(_) => Op::ZExt, 13 | Ty::Float | Ty::Double => Op::SIToFP, 14 | Ty::Pointer(_) => Op::IntToPtr, 15 | _ => return Err(CE::invalid_cast(curr.to_string(), dest.to_string(), span)), 16 | }, 17 | Ty::SignedInteger(IT::Char) => match dest { 18 | Ty::SignedInteger(IT::Char) => Op::BitCast, 19 | Ty::SignedInteger(_) | Ty::UnsignedInteger(_) => Op::ZExt, 20 | Ty::Float | Ty::Double => Op::UIToFP, 21 | Ty::Pointer(_) => Op::IntToPtr, 22 | _ => return Err(CE::invalid_cast(curr.to_string(), dest.to_string(), span)), 23 | }, 24 | 25 | // short 26 | Ty::SignedInteger(IT::Short) => match dest { 27 | Ty::SignedInteger(IT::Char) | Ty::UnsignedInteger(IT::Char) => Op::Trunc, 28 | Ty::UnsignedInteger(IT::Short) => Op::BitCast, 29 | Ty::SignedInteger(_) => Op::SExt, 30 | Ty::UnsignedInteger(_) => Op::ZExt, 31 | Ty::Float | Ty::Double => Op::SIToFP, 32 | Ty::Pointer(_) => Op::IntToPtr, 33 | _ => return Err(CE::invalid_cast(curr.to_string(), dest.to_string(), span)), 34 | }, 35 | Ty::UnsignedInteger(IT::Short) => match dest { 36 | Ty::SignedInteger(IT::Char) | Ty::UnsignedInteger(IT::Char) => Op::Trunc, 37 | Ty::SignedInteger(IT::Short) => Op::BitCast, 38 | Ty::SignedInteger(_) | Ty::UnsignedInteger(_) => Op::ZExt, 39 | Ty::Float | Ty::Double => Op::UIToFP, 40 | Ty::Pointer(_) => Op::IntToPtr, 41 | _ => return Err(CE::invalid_cast(curr.to_string(), dest.to_string(), span)), 42 | }, 43 | 44 | // int 45 | Ty::SignedInteger(IT::Int) => match dest { 46 | Ty::SignedInteger(IT::Char) 47 | | Ty::UnsignedInteger(IT::Char) 48 | | Ty::SignedInteger(IT::Short) 49 | | Ty::UnsignedInteger(IT::Short) => Op::Trunc, 50 | Ty::UnsignedInteger(IT::Int) => Op::BitCast, 51 | Ty::SignedInteger(_) => Op::SExt, 52 | Ty::UnsignedInteger(_) => Op::ZExt, 53 | Ty::Float | Ty::Double => Op::SIToFP, 54 | Ty::Pointer(_) => Op::IntToPtr, 55 | _ => return Err(CE::invalid_cast(curr.to_string(), dest.to_string(), span)), 56 | }, 57 | Ty::UnsignedInteger(IT::Int) => match dest { 58 | Ty::SignedInteger(IT::Char) 59 | | Ty::UnsignedInteger(IT::Char) 60 | | Ty::SignedInteger(IT::Short) 61 | | Ty::UnsignedInteger(IT::Short) => Op::Trunc, 62 | Ty::SignedInteger(IT::Int) => Op::BitCast, 63 | Ty::SignedInteger(_) | Ty::UnsignedInteger(_) => Op::ZExt, 64 | Ty::Float | Ty::Double => Op::UIToFP, 65 | Ty::Pointer(_) => Op::IntToPtr, 66 | _ => return Err(CE::invalid_cast(curr.to_string(), dest.to_string(), span)), 67 | }, 68 | 69 | // long 70 | Ty::SignedInteger(IT::Long) => match dest { 71 | Ty::SignedInteger(IT::Char) 72 | | Ty::UnsignedInteger(IT::Char) 73 | | Ty::SignedInteger(IT::Short) 74 | | Ty::UnsignedInteger(IT::Short) 75 | | Ty::SignedInteger(IT::Int) 76 | | Ty::UnsignedInteger(IT::Int) => Op::Trunc, 77 | Ty::UnsignedInteger(IT::Long) => Op::BitCast, 78 | Ty::SignedInteger(_) => Op::SExt, 79 | Ty::UnsignedInteger(_) => Op::ZExt, 80 | Ty::Float | Ty::Double => Op::SIToFP, 81 | Ty::Pointer(_) => Op::IntToPtr, 82 | _ => return Err(CE::invalid_cast(curr.to_string(), dest.to_string(), span)), 83 | }, 84 | Ty::UnsignedInteger(IT::Long) => match dest { 85 | Ty::SignedInteger(IT::Char) 86 | | Ty::UnsignedInteger(IT::Char) 87 | | Ty::SignedInteger(IT::Short) 88 | | Ty::UnsignedInteger(IT::Short) 89 | | Ty::SignedInteger(IT::Int) 90 | | Ty::UnsignedInteger(IT::Int) => Op::Trunc, 91 | Ty::SignedInteger(IT::Long) => Op::BitCast, 92 | Ty::SignedInteger(_) | Ty::UnsignedInteger(_) => Op::ZExt, 93 | Ty::Float | Ty::Double => Op::UIToFP, 94 | Ty::Pointer(_) => Op::IntToPtr, 95 | _ => return Err(CE::invalid_cast(curr.to_string(), dest.to_string(), span)), 96 | }, 97 | 98 | // long long 99 | Ty::SignedInteger(IT::LongLong) => match dest { 100 | Ty::SignedInteger(IT::Char) 101 | | Ty::UnsignedInteger(IT::Char) 102 | | Ty::SignedInteger(IT::Short) 103 | | Ty::UnsignedInteger(IT::Short) 104 | | Ty::SignedInteger(IT::Int) 105 | | Ty::UnsignedInteger(IT::Int) 106 | | Ty::SignedInteger(IT::Long) 107 | | Ty::UnsignedInteger(IT::Long) => Op::Trunc, 108 | Ty::UnsignedInteger(IT::LongLong) => Op::BitCast, 109 | Ty::Float | Ty::Double => Op::SIToFP, 110 | Ty::Pointer(_) => Op::IntToPtr, 111 | _ => return Err(CE::invalid_cast(curr.to_string(), dest.to_string(), span)), 112 | }, 113 | Ty::UnsignedInteger(IT::LongLong) => match dest { 114 | Ty::SignedInteger(IT::Char) 115 | | Ty::UnsignedInteger(IT::Char) 116 | | Ty::SignedInteger(IT::Short) 117 | | Ty::UnsignedInteger(IT::Short) 118 | | Ty::SignedInteger(IT::Int) 119 | | Ty::UnsignedInteger(IT::Int) 120 | | Ty::SignedInteger(IT::Long) 121 | | Ty::UnsignedInteger(IT::Long) => Op::Trunc, 122 | Ty::SignedInteger(IT::LongLong) => Op::BitCast, 123 | Ty::Float | Ty::Double => Op::UIToFP, 124 | Ty::Pointer(_) => Op::IntToPtr, 125 | _ => return Err(CE::invalid_cast(curr.to_string(), dest.to_string(), span)), 126 | }, 127 | 128 | Ty::Bool => match dest { 129 | Ty::SignedInteger(_) | Ty::UnsignedInteger(_) => Op::ZExt, 130 | Ty::Float | Ty::Double => Op::UIToFP, 131 | _ => return Err(CE::invalid_cast(curr.to_string(), dest.to_string(), span)), 132 | }, 133 | 134 | Ty::Float => match dest { 135 | Ty::SignedInteger(_) => Op::FPToSI, 136 | Ty::UnsignedInteger(_) => Op::FPToUI, 137 | Ty::Double => Op::FPExt, 138 | _ => return Err(CE::invalid_cast(curr.to_string(), dest.to_string(), span)), 139 | }, 140 | Ty::Double => match dest { 141 | Ty::SignedInteger(_) => Op::FPToSI, 142 | Ty::UnsignedInteger(_) => Op::FPToUI, 143 | Ty::Float => Op::FPTrunc, 144 | _ => return Err(CE::invalid_cast(curr.to_string(), dest.to_string(), span)), 145 | }, 146 | 147 | Ty::Pointer(_) => match dest { 148 | Ty::SignedInteger(_) | Ty::UnsignedInteger(_) => Op::PtrToInt, 149 | Ty::Pointer(_) => Op::BitCast, 150 | _ => return Err(CE::invalid_cast(curr.to_string(), dest.to_string(), span)), 151 | }, 152 | Ty::Array(_, _) => match dest { 153 | Ty::Pointer(_) => Op::BitCast, 154 | _ => return Err(CE::invalid_cast(curr.to_string(), dest.to_string(), span)), 155 | }, 156 | _ => return Err(CE::invalid_cast(curr.to_string(), dest.to_string(), span)), 157 | }; 158 | Ok(instruction) 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/generator/func_def.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::{ 2 | AssignOperation, AssignOperationEnum, BaseType, BasicType, Declaration, DeclarationEnum, 3 | Expression, ExpressionEnum, Span, Statement, StatementEnum, StatementOrDeclarationEnum, 4 | }; 5 | use crate::generator::Generator; 6 | use crate::utils::CompileErr as CE; 7 | use inkwell::values::{BasicValue, PointerValue}; 8 | use std::collections::HashMap; 9 | 10 | impl<'ctx> Generator<'ctx> { 11 | pub(crate) fn gen_func_def( 12 | &mut self, 13 | return_type: &BasicType, 14 | func_name: &str, 15 | func_param: &[(BasicType, Option)], 16 | func_body: &Statement, 17 | span: Span, 18 | ) -> Result<(), Vec> { 19 | let func = self.module.get_function(func_name).unwrap(); 20 | self.val_map_block_stack.push(HashMap::new()); 21 | 22 | let func_ty = self.function_map.get(func_name).unwrap().to_owned(); 23 | self.current_function = Some((func, func_ty.0)); 24 | 25 | let mut func_param_alloca = Vec::new(); 26 | let mut errors: Vec = Vec::new(); 27 | 28 | // create function block 29 | let func_block = self.context.append_basic_block(func, "entry"); 30 | self.builder.position_at_end(func_block); 31 | 32 | for (i, param) in func.get_param_iter().enumerate() { 33 | // TODO: validate param type 34 | 35 | if func_param[i].1.is_some() { 36 | param.set_name(func_param[i].1.as_ref().unwrap().as_str()); 37 | } 38 | 39 | let builder = self.context.create_builder(); 40 | let func_entry = func.get_first_basic_block().unwrap(); 41 | 42 | match func_entry.get_first_instruction() { 43 | Some(first_inst) => builder.position_before(&first_inst), 44 | None => builder.position_at_end(func_entry), 45 | } 46 | 47 | let llvm_type = match self.convert_llvm_type(&func_param[i].0.base_type, span) { 48 | Ok(t) => t, 49 | Err(e) => { 50 | errors.push(e); 51 | continue; 52 | } 53 | }; 54 | let alloca = builder.build_alloca( 55 | llvm_type, 56 | func_param[i] 57 | .1 58 | .as_ref() 59 | .unwrap_or(&("__param__".to_string() + func_name + &i.to_string())) 60 | .as_str(), 61 | ); 62 | 63 | func_param_alloca.push(alloca); 64 | 65 | if func_param[i].1.is_some() { 66 | if let Err(e) = self.insert_to_val_map( 67 | &func_param[i].0, 68 | func_param[i].1.as_ref().unwrap(), 69 | alloca, 70 | span, 71 | ) { 72 | errors.push(e); 73 | } 74 | } 75 | } 76 | 77 | // store params on the stack 78 | for (i, param) in func.get_param_iter().enumerate() { 79 | self.builder.build_store(func_param_alloca[i], param); 80 | } 81 | 82 | // generate IR for each statement or declaration in function body 83 | if let StatementEnum::Compound(ref state_or_decl) = func_body.node { 84 | errors.extend( 85 | state_or_decl 86 | .iter() 87 | .map(|element| match element.node { 88 | StatementOrDeclarationEnum::Statement(ref state) => { 89 | self.gen_statement(state) 90 | } 91 | StatementOrDeclarationEnum::LocalDeclaration(ref decl) => { 92 | self.gen_decl_in_fn(decl) 93 | } 94 | }) 95 | .filter_map(|result| if result.is_err() { result.err() } else { None }), 96 | ); 97 | } else { 98 | panic!("internal error: func_body is not Statement::Compound"); 99 | } 100 | 101 | // build terminator for any block that is not terminated 102 | let mut iter_block = func.get_first_basic_block(); 103 | while iter_block.is_some() { 104 | let block = iter_block.unwrap(); 105 | if block.get_terminator().is_none() { 106 | let terminator_builder = self.context.create_builder(); 107 | terminator_builder.position_at_end(block); 108 | match return_type.base_type { 109 | BaseType::Void => { 110 | terminator_builder.build_return(None); 111 | } 112 | _ => { 113 | let null_val = self.context.i32_type().const_zero(); 114 | terminator_builder.build_return(Some(&null_val)); 115 | } 116 | } 117 | } 118 | iter_block = block.get_next_basic_block(); 119 | } 120 | 121 | if !func.verify(true) { 122 | func.print_to_stderr(); 123 | panic!() 124 | } 125 | 126 | self.val_map_block_stack.pop(); 127 | self.current_function = None; 128 | 129 | if errors.is_empty() { 130 | Ok(()) 131 | } else { 132 | Err(errors) 133 | } 134 | } 135 | 136 | fn insert_to_val_map( 137 | &mut self, 138 | var_type: &BasicType, 139 | identifier: &str, 140 | ptr: PointerValue<'ctx>, 141 | span: Span, 142 | ) -> Result<(), CE> { 143 | let local_map = self.val_map_block_stack.last_mut().unwrap(); 144 | 145 | if local_map.contains_key(identifier) { 146 | return Err(CE::duplicated_variable(identifier.to_string(), span)); 147 | } 148 | 149 | local_map.insert(identifier.to_string(), (var_type.clone(), ptr)); 150 | Ok(()) 151 | } 152 | 153 | pub(crate) fn gen_decl_in_fn(&mut self, decl: &Declaration) -> Result<(), CE> { 154 | if let DeclarationEnum::Declaration(ref var_type, ref identifier, ref expr) = decl.node { 155 | let llvm_type = self.convert_llvm_type( 156 | &self.extend_struct_type(var_type.basic_type.base_type.to_owned(), decl.span)?, 157 | decl.span, 158 | )?; 159 | let p_val = self 160 | .builder 161 | .build_alloca(llvm_type, &identifier.to_owned().unwrap()); 162 | self.insert_to_val_map( 163 | &var_type.basic_type, 164 | &identifier.to_owned().unwrap(), 165 | p_val, 166 | decl.span, 167 | )?; 168 | if let Some(ref expr) = expr { 169 | self.gen_assignment( 170 | &AssignOperation { 171 | node: AssignOperationEnum::Naive, 172 | span: expr.span, 173 | }, 174 | &Box::new(Expression { 175 | node: ExpressionEnum::Identifier(identifier.to_owned().unwrap()), 176 | span: expr.span, 177 | }), 178 | expr, 179 | decl.span, 180 | )?; 181 | } 182 | Ok(()) 183 | } else { 184 | Err(CE::plain_error( 185 | "FunctionDefinition cannot exist in function".to_string(), 186 | decl.span, 187 | )) 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/generator/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::BasicType as BT; 2 | use crate::ast::StructMember; 3 | use codespan_reporting::files::SimpleFiles; 4 | use inkwell::basic_block::BasicBlock; 5 | use inkwell::builder::Builder; 6 | use inkwell::context::Context; 7 | use inkwell::module::Module; 8 | use inkwell::values::{FunctionValue, PointerValue}; 9 | use std::collections::{HashMap, VecDeque}; 10 | 11 | mod cast_inst; 12 | mod expr; 13 | mod func_def; 14 | pub mod gen; 15 | mod out; 16 | mod stmt; 17 | mod utils; 18 | 19 | pub struct Generator<'ctx> { 20 | files: SimpleFiles<&'ctx str, &'ctx str>, 21 | module_name: &'ctx str, 22 | context: &'ctx Context, 23 | module: Module<'ctx>, 24 | builder: Builder<'ctx>, 25 | 26 | //>>>>>>>>>>>>>>>>>>>>>>>> 27 | // LLVM Blocks 28 | //<<<<<<<<<<<<<<<<<<<<<<<< 29 | 30 | // value -> (type, pointer) map in a LLVM basic block 31 | val_map_block_stack: Vec)>>, 32 | // struct name -> type map globally 33 | global_struct_map: HashMap>, 34 | // current function block 35 | current_function: Option<(FunctionValue<'ctx>, BT)>, 36 | // break labels (in loop statements) 37 | break_labels: VecDeque>, 38 | // continue labels (in loop statements) 39 | continue_labels: VecDeque>, 40 | // hashset for functions 41 | function_map: HashMap, bool)>, 42 | // hashset for global variable 43 | global_variable_map: HashMap)>, 44 | // hashset for typedef 45 | typedef_map: HashMap, 46 | } 47 | -------------------------------------------------------------------------------- /src/generator/out.rs: -------------------------------------------------------------------------------- 1 | use crate::generator::Generator; 2 | use anyhow::Result; 3 | use inkwell::passes::{PassManager, PassManagerBuilder}; 4 | use inkwell::targets::{ 5 | CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetMachine, 6 | }; 7 | use inkwell::OptimizationLevel; 8 | use std::path::PathBuf; 9 | 10 | impl<'ctx> Generator<'ctx> { 11 | /// * `output_file` - None if use default filename 12 | pub fn out_bc(&mut self, output_file: Option) -> bool { 13 | let mut target_path; 14 | if let Some(output_file) = output_file { 15 | target_path = PathBuf::from(output_file); 16 | } else { 17 | target_path = PathBuf::from(self.module_name); 18 | target_path.set_extension("bc"); 19 | } 20 | self.module.write_bitcode_to_path(target_path.as_path()) 21 | } 22 | 23 | /// * `is_obj` - true if the output file is an object file, false if it is a asm file 24 | /// * `output_file` - None if use default filename 25 | pub fn out_asm_or_obj( 26 | &mut self, 27 | is_obj: bool, 28 | output_file: Option, 29 | opt: OptimizationLevel, 30 | ) -> Result<()> { 31 | Target::initialize_native(&InitializationConfig::default()).unwrap(); 32 | 33 | let triple = TargetMachine::get_default_triple(); 34 | let cpu = match opt { 35 | OptimizationLevel::None => std::env::consts::ARCH.to_string().replace('_', "-"), 36 | _ => TargetMachine::get_host_cpu_name().to_string(), 37 | }; 38 | let features = match opt { 39 | OptimizationLevel::None => "".to_string(), 40 | _ => TargetMachine::get_host_cpu_features().to_string(), 41 | }; 42 | let pass_manager = match opt { 43 | OptimizationLevel::None => { 44 | let fpm = PassManager::create(()); 45 | let pb = PassManagerBuilder::create(); 46 | pb.set_optimization_level(opt); 47 | pb.set_disable_unroll_loops(true); 48 | pb.populate_module_pass_manager(&fpm); 49 | fpm 50 | } 51 | OptimizationLevel::Aggressive => { 52 | let fpm = PassManager::create(()); 53 | let pb = PassManagerBuilder::create(); 54 | pb.set_optimization_level(opt); 55 | pb.set_disable_unroll_loops(false); 56 | pb.populate_module_pass_manager(&fpm); 57 | fpm.add_strip_symbol_pass(); 58 | fpm.add_merged_load_store_motion_pass(); 59 | fpm.add_loop_deletion_pass(); 60 | fpm.add_loop_idiom_pass(); 61 | fpm.add_partially_inline_lib_calls_pass(); 62 | fpm.add_simplify_lib_calls_pass(); 63 | fpm.add_correlated_value_propagation_pass(); 64 | fpm.add_early_cse_pass(); 65 | fpm 66 | } 67 | _ => { 68 | let fpm = PassManager::create(()); 69 | let pb = PassManagerBuilder::create(); 70 | pb.set_optimization_level(opt); 71 | pb.set_disable_unroll_loops(false); 72 | pb.populate_module_pass_manager(&fpm); 73 | fpm 74 | } 75 | }; 76 | 77 | let machine = Target::from_triple(&triple) 78 | .unwrap() 79 | .create_target_machine( 80 | &triple, 81 | &cpu, 82 | &features, 83 | opt, 84 | RelocMode::Default, 85 | CodeModel::Default, 86 | ) 87 | .unwrap(); 88 | machine.add_analysis_passes(&pass_manager); 89 | 90 | let mut target_path; 91 | if let Some(output_file) = output_file { 92 | target_path = PathBuf::from(output_file); 93 | } else { 94 | target_path = PathBuf::from(self.module_name); 95 | target_path.set_extension(match is_obj { 96 | true => "o", 97 | false => "s", 98 | }); 99 | } 100 | 101 | machine 102 | .write_to_file( 103 | &self.module, 104 | match is_obj { 105 | true => FileType::Object, 106 | false => FileType::Assembly, 107 | }, 108 | target_path.as_ref(), 109 | ) 110 | .unwrap(); 111 | 112 | Ok(()) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/generator/stmt.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::{ 2 | Expression, ExpressionEnum, ForInitClause, ForInitClauseEnum, Span, Statement, StatementEnum, 3 | StatementOrDeclaration, StatementOrDeclarationEnum, 4 | }; 5 | use crate::generator::Generator; 6 | use crate::utils::CompileErr as CE; 7 | use std::collections::HashMap; 8 | 9 | impl<'ctx> Generator<'ctx> { 10 | pub(crate) fn gen_statement(&mut self, statement: &Statement) -> Result<(), CE> { 11 | match statement.node { 12 | StatementEnum::Compound(ref state_or_decl) => { 13 | self.gen_compound_statement(state_or_decl)? 14 | } 15 | StatementEnum::While(ref cond, ref body) => { 16 | self.gen_while_statement(cond, body, false)? 17 | } 18 | StatementEnum::DoWhile(ref body, ref cond) => { 19 | self.gen_while_statement(cond, body, true)? 20 | } 21 | StatementEnum::For(ref init, ref cond, ref iter, ref body) => { 22 | self.gen_for_statement(init, cond, iter, body)? 23 | } 24 | StatementEnum::Break => self.gen_break_statement(statement.span)?, 25 | StatementEnum::Continue => self.gen_continue_statement(statement.span)?, 26 | StatementEnum::If(ref cond, ref then_stmt, ref else_stmt) => { 27 | self.gen_if_statement(cond, then_stmt, else_stmt)? 28 | } 29 | StatementEnum::Return(ref expr) => self.gen_return_statement(expr)?, 30 | StatementEnum::Expression(ref expr) => { 31 | self.gen_expression(expr)?; 32 | } 33 | _ => { 34 | dbg!(statement); 35 | unimplemented!() 36 | } 37 | }; 38 | Ok(()) 39 | } 40 | 41 | fn gen_compound_statement(&mut self, statements: &[StatementOrDeclaration]) -> Result<(), CE> { 42 | self.val_map_block_stack.push(HashMap::new()); 43 | 44 | // generate IR for each statement or declaration in function body 45 | for element in statements { 46 | match element.node { 47 | StatementOrDeclarationEnum::Statement(ref state) => { 48 | self.gen_statement(state)?; 49 | } 50 | StatementOrDeclarationEnum::LocalDeclaration(ref decl) => { 51 | self.gen_decl_in_fn(decl)?; 52 | } 53 | } 54 | } 55 | 56 | self.val_map_block_stack.pop(); 57 | Ok(()) 58 | } 59 | 60 | fn gen_while_statement( 61 | &mut self, 62 | cond: &Expression, 63 | body: &Statement, 64 | is_do_while: bool, 65 | ) -> Result<(), CE> { 66 | let func_val = self.current_function.as_ref().unwrap().0; 67 | 68 | let before_while_block = self.context.append_basic_block(func_val, "before_while"); 69 | let while_block = self 70 | .context 71 | .append_basic_block(func_val, if is_do_while { "do_while" } else { "while" }); 72 | let after_while_block = self.context.append_basic_block(func_val, "after_loop"); 73 | 74 | self.continue_labels.push_back(after_while_block); 75 | self.break_labels.push_back(after_while_block); 76 | 77 | self.builder.build_unconditional_branch(before_while_block); 78 | self.builder.position_at_end(before_while_block); 79 | let condition_val_int_val = self.gen_expression(cond)?.1.into_int_value(); 80 | if self.no_terminator() { 81 | if is_do_while { 82 | self.builder.build_unconditional_branch(while_block); 83 | } else { 84 | self.builder.build_conditional_branch( 85 | condition_val_int_val, 86 | while_block, 87 | after_while_block, 88 | ); 89 | } 90 | } 91 | 92 | self.builder.position_at_end(while_block); 93 | 94 | // body must be Statement::Compound 95 | self.gen_statement(body)?; 96 | if self.no_terminator() { 97 | if is_do_while { 98 | self.builder.build_conditional_branch( 99 | condition_val_int_val, 100 | before_while_block, 101 | after_while_block, 102 | ); 103 | } else { 104 | self.builder.build_unconditional_branch(before_while_block); 105 | } 106 | } 107 | 108 | self.builder.position_at_end(after_while_block); 109 | 110 | self.break_labels.pop_back(); 111 | self.continue_labels.pop_back(); 112 | 113 | Ok(()) 114 | } 115 | 116 | fn gen_for_statement( 117 | &mut self, 118 | init: &Option>, 119 | cond: &Option>, 120 | iter: &Option>, 121 | body: &Statement, 122 | ) -> Result<(), CE> { 123 | let mut new_block: Vec = vec![]; 124 | if let Some(ref init) = init { 125 | match &init.node { 126 | ForInitClauseEnum::Expression(ref expr) => { 127 | new_block.push(StatementOrDeclaration { 128 | node: StatementOrDeclarationEnum::Statement(Statement { 129 | node: StatementEnum::Expression(Box::new(expr.to_owned())), 130 | span: init.span, 131 | }), 132 | span: init.span, 133 | }); 134 | } 135 | ForInitClauseEnum::ForDeclaration(decl) => { 136 | new_block.append( 137 | decl.iter() 138 | .map(|d| StatementOrDeclaration { 139 | node: StatementOrDeclarationEnum::LocalDeclaration(d.to_owned()), 140 | span: d.span, 141 | }) 142 | .collect::>() 143 | .as_mut(), 144 | ); 145 | } 146 | } 147 | } 148 | let mut new_body = vec![StatementOrDeclaration { 149 | node: StatementOrDeclarationEnum::Statement(body.to_owned()), 150 | span: body.span, 151 | }]; 152 | if let Some(iter) = iter { 153 | new_body.push(StatementOrDeclaration { 154 | node: StatementOrDeclarationEnum::Statement(Statement { 155 | node: StatementEnum::Expression(iter.to_owned()), 156 | span: iter.span, 157 | }), 158 | span: iter.span, 159 | }); 160 | } 161 | let new_cond = match cond { 162 | Some(cond) => cond.to_owned(), 163 | None => Box::new(Expression { 164 | node: ExpressionEnum::Empty, 165 | span: Span::default(), 166 | }), 167 | }; 168 | new_block.push(StatementOrDeclaration { 169 | node: StatementOrDeclarationEnum::Statement(Statement { 170 | node: StatementEnum::While( 171 | new_cond.clone(), 172 | Box::new(Statement { 173 | node: StatementEnum::Compound(new_body), 174 | span: body.span, 175 | }), 176 | ), 177 | span: new_cond.span, 178 | }), 179 | span: new_cond.span, 180 | }); 181 | self.gen_compound_statement(&new_block)?; 182 | Ok(()) 183 | } 184 | 185 | fn gen_break_statement(&mut self, span: Span) -> Result<(), CE> { 186 | if self.break_labels.is_empty() { 187 | return Err(CE::keyword_not_in_a_loop("break".to_string(), span)); 188 | } 189 | let break_block = self.break_labels.back().unwrap(); 190 | self.builder.build_unconditional_branch(*break_block); 191 | Ok(()) 192 | } 193 | 194 | fn gen_continue_statement(&mut self, span: Span) -> Result<(), CE> { 195 | if self.continue_labels.is_empty() { 196 | return Err(CE::keyword_not_in_a_loop("continue".to_string(), span)); 197 | } 198 | let continue_block = self.continue_labels.back().unwrap(); 199 | self.builder.build_unconditional_branch(*continue_block); 200 | Ok(()) 201 | } 202 | 203 | fn gen_if_statement( 204 | &mut self, 205 | cond: &Expression, 206 | then_stmt: &Statement, 207 | else_stmt: &Option>, 208 | ) -> Result<(), CE> { 209 | let func_val = self.current_function.as_ref().unwrap().0; 210 | 211 | let if_block = self.context.append_basic_block(func_val, "if_block"); 212 | let else_block = self.context.append_basic_block(func_val, "else_block"); 213 | let after_block = self.context.append_basic_block(func_val, "after_block"); 214 | 215 | let cond_int_value = self.gen_expression(cond)?.1.into_int_value(); 216 | self.builder 217 | .build_conditional_branch(cond_int_value, if_block, else_block); 218 | 219 | self.builder.position_at_end(if_block); 220 | self.gen_statement(then_stmt)?; 221 | if self.no_terminator() { 222 | self.builder.build_unconditional_branch(after_block); 223 | }; 224 | 225 | self.builder.position_at_end(else_block); 226 | if let Some(ref else_stmt) = *else_stmt { 227 | self.gen_statement(else_stmt)?; 228 | } 229 | if self.no_terminator() { 230 | self.builder.build_unconditional_branch(after_block); 231 | } 232 | 233 | self.builder.position_at_end(after_block); 234 | 235 | Ok(()) 236 | } 237 | 238 | fn gen_return_statement(&mut self, expr: &Option>) -> Result<(), CE> { 239 | if expr.is_none() { 240 | self.builder.build_return(None); 241 | return Ok(()); 242 | } 243 | 244 | let func_return_type = self 245 | .current_function 246 | .as_ref() 247 | .unwrap() 248 | .to_owned() 249 | .1 250 | .base_type; 251 | let (e_t, e_v) = self.gen_expression(&expr.to_owned().unwrap())?; 252 | 253 | e_t.test_cast( 254 | &func_return_type, 255 | expr.as_ref().unwrap().span, 256 | &self.typedef_map, 257 | )?; 258 | 259 | let return_val = 260 | self.cast_value(&e_t, &e_v, &func_return_type, expr.as_ref().unwrap().span)?; 261 | self.builder.build_return(Some(&return_val)); 262 | 263 | Ok(()) 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/generator/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::{BaseType, Span}; 2 | use crate::generator::Generator; 3 | use crate::utils::CompileErr as CE; 4 | 5 | impl<'ctx> Generator<'ctx> { 6 | pub(crate) fn no_terminator(&self) -> bool { 7 | let block = self.builder.get_insert_block(); 8 | let terminator = block.unwrap().get_terminator(); 9 | terminator.is_none() 10 | } 11 | 12 | pub(crate) fn gen_err_output(&self, file_id: usize, e: &CE) { 13 | use codespan_reporting::term; 14 | use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; 15 | 16 | let diagnostic = e.to_diagnostic(file_id); 17 | 18 | let writer = StandardStream::stderr(ColorChoice::Always); 19 | let config = term::Config::default(); 20 | 21 | term::emit(&mut writer.lock(), &config, &self.files, &diagnostic).expect("unreachable"); 22 | } 23 | 24 | pub(crate) fn extend_struct_type(&self, t: BaseType, span: Span) -> Result { 25 | match t { 26 | BaseType::Struct(ref name, ref _members) => { 27 | let members = self.global_struct_map.get(name.as_ref().unwrap()); 28 | if members.is_none() { 29 | return Err(CE::struct_not_found(name.clone().unwrap(), span)); 30 | } 31 | let members = members.unwrap().clone(); 32 | Ok(BaseType::Struct(name.clone(), Some(members))) 33 | } 34 | _ => Ok(t), 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate pest; 2 | #[macro_use] 3 | extern crate pest_derive; 4 | extern crate core; 5 | 6 | pub mod ast; 7 | pub mod generator; 8 | pub mod parse; 9 | pub mod preprocess; 10 | pub mod visual; 11 | 12 | pub mod utils; 13 | 14 | pub use ast::*; 15 | pub use parse::*; 16 | pub use preprocess::*; 17 | pub use visual::*; 18 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate pest; 2 | #[macro_use] 3 | extern crate pest_derive; 4 | extern crate core; 5 | 6 | use cc99::compile_result; 7 | use clap::{ArgGroup, Parser}; 8 | use inkwell::{context::Context, OptimizationLevel}; 9 | use std::fs; 10 | use std::io::{stdin, Read}; 11 | use std::path::Path; 12 | use std::process::{Command, Output}; 13 | 14 | mod ast; 15 | mod generator; 16 | mod parse; 17 | mod preprocess; 18 | mod utils; 19 | 20 | use generator::*; 21 | use parse::*; 22 | use preprocess::*; 23 | 24 | #[derive(Parser, Debug)] 25 | #[clap(author, version, about, long_about = None)] 26 | #[clap(group( 27 | ArgGroup::new("stage") 28 | .args(&["expand", "parse", "bitcode", "compile", "assemble","visual"]) 29 | ))] 30 | struct Args { 31 | /// Source code 32 | #[clap()] 33 | file: String, 34 | 35 | /// Place the output into 36 | #[clap(short, long, display_order = 0)] 37 | output: Option, 38 | 39 | /// Preprocess only; do not parse, compile, assemble or link 40 | #[clap(short = 'E', long, display_order = 3)] 41 | expand: bool, 42 | 43 | /// Preprocess and parse; do not compile, assemble or link 44 | #[clap(short, long, display_order = 4)] 45 | parse: bool, 46 | 47 | /// Generate LLVM Bitcode only 48 | #[clap(short, long, display_order = 5)] 49 | bitcode: bool, 50 | 51 | /// Compile only; do not assemble or link 52 | #[clap(short = 'S', long, display_order = 6)] 53 | compile: bool, 54 | 55 | /// Compile and assemble, but do not link 56 | #[clap(short = 'c', long, display_order = 7)] 57 | assemble: bool, 58 | 59 | /// AST Visualization 60 | #[clap(short = 'V', long, display_order = 8)] 61 | visual: bool, 62 | 63 | /// Optimization level, from 0 to 3 64 | #[clap(short = 'O', long, default_value = "0", display_order = 2)] 65 | opt_level: u32, 66 | 67 | /// Add the directory ,,(from left to right) to the list of directories to be searched for header files during preprocessing 68 | #[clap(short, long, display_order = 1)] 69 | include: Option, 70 | } 71 | 72 | fn main() { 73 | let args = Args::parse(); 74 | let include_dirs: Vec<&str> = match args.include { 75 | Some(ref includes) => includes.split(',').collect(), 76 | None => Default::default(), 77 | }; 78 | if args.visual { 79 | let mut buffer = String::new(); 80 | let size = stdin().read_to_string(&mut buffer); 81 | match size { 82 | Ok(_) => { 83 | let res = compile_result(buffer.as_str()); 84 | print!("{}", res); 85 | std::process::exit(0); 86 | } 87 | Err(e) => { 88 | eprintln!("Unable to read stdin: {}", e); 89 | std::process::exit(1); 90 | } 91 | } 92 | } 93 | let basename = Path::new(&args.file).file_stem().unwrap().to_str().unwrap(); 94 | let output_file = match args.output { 95 | Some(output) => output, 96 | None => { 97 | if args.expand { 98 | format!("{}.expand.c", basename) 99 | } else if args.parse { 100 | format!("{}.json", basename) 101 | } else if args.bitcode { 102 | format!("{}.bc", basename) 103 | } else if args.compile { 104 | format!("{}.s", basename) 105 | } else if args.assemble { 106 | format!("{}.o", basename) 107 | } else { 108 | basename.to_string() 109 | } 110 | } 111 | }; 112 | let opt_level = match args.opt_level { 113 | 0 => OptimizationLevel::None, 114 | 1 => OptimizationLevel::Less, 115 | 2 => OptimizationLevel::Default, 116 | 3 => OptimizationLevel::Aggressive, 117 | _ => { 118 | eprintln!("Invalid optimization level"); 119 | std::process::exit(1); 120 | } 121 | }; 122 | 123 | // preprocess 124 | let code = preprocess_file(&args.file, &include_dirs).unwrap_or_else(|e| { 125 | eprintln!("Preprocess failed:\n{}", e); 126 | std::process::exit(1); 127 | }); 128 | 129 | if args.expand { 130 | fs::write(&output_file, &code).unwrap_or_else(|_| { 131 | eprintln!("Unable to write file {}", output_file); 132 | std::process::exit(1); 133 | }); 134 | } else { 135 | // parse 136 | let ast = Parse::new().parse(&code).unwrap_or_else(|e| { 137 | eprintln!("Parse failed:\n{}", e); 138 | std::process::exit(1); 139 | }); 140 | 141 | if args.parse { 142 | fs::write(&output_file, &serde_json::to_string(&ast).unwrap()).unwrap_or_else(|_| { 143 | eprintln!("Unable to write file {}", output_file); 144 | std::process::exit(1); 145 | }); 146 | } else { 147 | // code_gen 148 | let context = Context::create(); 149 | let mut code_gen = Generator::new(&context, &args.file, &code); 150 | code_gen.gen(&ast); 151 | 152 | if args.bitcode { 153 | // generate LLVM bitcode 154 | if !code_gen.out_bc(Some(output_file)) { 155 | eprintln!("Unable to generate bitcode"); 156 | std::process::exit(1); 157 | } 158 | } else if args.compile { 159 | // generate assembly code 160 | if let Err(e) = code_gen.out_asm_or_obj(false, Some(output_file), opt_level) { 161 | eprintln!("{}", e); 162 | std::process::exit(1); 163 | } 164 | } else { 165 | // generate object code 166 | if let Err(e) = code_gen.out_asm_or_obj( 167 | true, 168 | Some(match args.assemble { 169 | true => output_file.clone(), 170 | false => basename.to_string() + ".o", 171 | }), 172 | opt_level, 173 | ) { 174 | eprintln!("{}", e); 175 | std::process::exit(1); 176 | } 177 | if !args.assemble { 178 | // generate binary 179 | let object_file = basename.to_string() + ".o"; 180 | link(&object_file, &output_file, Compiler::Clang) 181 | .map_err(|_| link(&object_file, &output_file, Compiler::GNU)) 182 | .map_or_else( 183 | |e| { 184 | if let Err(e) = e { 185 | eprintln!("{}", e); 186 | std::process::exit(1); 187 | } 188 | }, 189 | |output| { 190 | if !output.status.success() { 191 | eprintln!("{}", String::from_utf8_lossy(&output.stderr)); 192 | std::process::exit(1); 193 | } 194 | }, 195 | ); 196 | // remove tmp files 197 | fs::remove_file(&object_file).unwrap_or_else(|_| { 198 | eprintln!("Unable to remove file {}", object_file); 199 | std::process::exit(1); 200 | }); 201 | } 202 | } 203 | } 204 | } 205 | } 206 | 207 | #[derive(Debug)] 208 | enum Compiler { 209 | GNU, 210 | Clang, 211 | } 212 | 213 | fn link(source_file: &str, target_file: &str, compiler: Compiler) -> Result { 214 | let args = vec!["-o", target_file, source_file]; 215 | let output = match compiler { 216 | Compiler::GNU => Command::new("gcc").args(args).output(), 217 | Compiler::Clang => Command::new("clang").arg("-no-pie").args(args).output(), 218 | }; 219 | match output { 220 | Ok(output) => Ok(output), 221 | Err(e) => Err(format!( 222 | "Failed to link using {:?}: {}", 223 | compiler, 224 | e.to_string() 225 | )), 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/parse/parse.pest: -------------------------------------------------------------------------------- 1 | cc99 = { SOI ~ (declaration | function_definition)* ~ EOI } 2 | 3 | 4 | //>>>>>>>>>>>>>>>>>>>>>>> 5 | // DECLARATION 6 | //<<<<<<<<<<<<<<<<<<<<<<< 7 | declaration = {declaration_specifiers ~ declarator_and_initializer_list? ~ ";"} 8 | declarator_and_initializer_list = {declarator_and_initializer ~ ("," ~ declarator_and_initializer)*} 9 | declarator_and_initializer = {declarator ~ ("=" ~ assignment_expression)?} 10 | pointer = {(star_ ~ (type_qualifier)*)+} 11 | declarator = {pointer? ~ raw_declarator} 12 | raw_declarator = {(identifier ~ ("[" ~ assignment_expression ~ "]")+) | (identifier ~ "(" ~ function_parameter_list ~ ")") | identifier} 13 | function_parameter_list = {function_parameter? ~ ("," ~ function_parameter)* ~ ("," ~ variadic_argument_)?} 14 | function_parameter = {declaration_specifiers ~ function_parameter_declarator} 15 | function_parameter_declarator = {pointer? ~ function_parameter_raw_declarator} 16 | function_parameter_raw_declarator = {(identifier? ~ ("[" ~ assignment_expression ~ "]")+) | (identifier? ~ "(" ~ function_parameter_list ~ ")") | identifier?} 17 | 18 | function_definition = {declaration_specifiers ~ pointer? ~ identifier ~ "(" ~ function_parameter_list ~ ")" ~ compound_statement} 19 | 20 | declaration_specifiers = {(storage_class_specifier | function_specifier | type_qualifier)* ~ type_specifier ~ (storage_class_specifier | function_specifier | type_qualifier)*} 21 | storage_class_specifier = {typedef_ | extern_ | static_ | thread_local_ | auto_ | register_} 22 | type_qualifier = {const_ | volatile_ | restrict_ | atomic_} 23 | function_specifier = {inline_ | noreturn_} 24 | type_specifier = {void_ | ((unsigned_ | signed_)? ~ (char_ | short_ | int_ | (long_ ~ long_) | long_)) | signed_ | unsigned_ | bool_ | float_ | double_ | struct_specifier | identifier} 25 | struct_specifier = {((struct_ | union_) ~ identifier? ~ "{" ~ (struct_declaration)+ ~ "}") | ((struct_ | union_) ~ identifier)} 26 | struct_declaration = {(declaration)+} 27 | 28 | 29 | //>>>>>>>>>>>>>>>>>>>>>>> 30 | // STATEMENT 31 | //<<<<<<<<<<<<<<<<<<<<<<< 32 | statement = {labeled_statement | case_statement | expression_statement | compound_statement | selection_statement | iteration_statement | jump_statement} 33 | labeled_statement = {identifier ~ ":" ~ statement} 34 | case_statement = {((case_ ~ assignment_expression) | default_) ~ ":" ~ statement} 35 | compound_statement = {"{" ~ (statement | declaration)* ~ "}"} 36 | expression_statement = {expression? ~ ";"} 37 | selection_statement = {if_statement | switch_statement} 38 | iteration_statement = {for_statement | while_statement | do_while_statement} 39 | jump_statement = {break_statement | continue_statement | return_statement | goto_statement} 40 | 41 | if_statement = {if_ ~ "(" ~ expression ~ ")" ~ statement ~ (else_ ~ statement)?} 42 | switch_statement = {switch_ ~ "(" ~ expression ~ ")" ~ statement} 43 | 44 | for_statement = {for_ ~ "(" ~ for_init_clause? ~ ";" ~ for_cond_expression? ~ ";" ~ for_iteration_expression? ~ ")" ~ statement} 45 | while_statement = {while_ ~ "(" ~ expression ~ ")" ~ statement} 46 | do_while_statement = {do_ ~ statement ~ while_ ~ "(" ~ expression ~ ")" ~ ";"} 47 | 48 | break_statement = {break_ ~ ";"} 49 | continue_statement = {continue_ ~ ";"} 50 | return_statement = {return_ ~ expression? ~ ";"} 51 | goto_statement = {goto_ ~ identifier ~ ";"} 52 | 53 | for_init_clause = {expression | (declaration_specifiers ~ declarator_and_initializer_list)} 54 | for_cond_expression = {expression} 55 | for_iteration_expression = {expression} 56 | 57 | 58 | //>>>>>>>>>>>>>>>>>>>>>>> 59 | // EXPRESSION 60 | //<<<<<<<<<<<<<<<<<<<<<<< 61 | expression = {assignment_expression ~ (comma ~ assignment_expression)*} 62 | comma = {","} 63 | 64 | // https://en.cppreference.com/w/c/language/operator_precedence#cite_ref-4 65 | assignment_expression = {(unary_expression ~ assignment_operator ~ assignment_expression) | conditional_expression} 66 | 67 | // https://en.cppreference.com/w/c/language/operator_precedence#cite_ref-3 68 | conditional_expression = {logical_or_expression ~ ("?" ~ expression ~ ":" ~ conditional_expression)?} 69 | logical_or_expression = {logical_and_expression ~ (logical_or_op ~ logical_and_expression)*} 70 | logical_and_expression = {bitwise_or_expression ~ (logical_and_op ~ bitwise_or_expression)*} 71 | bitwise_or_expression = {bitwise_xor_expression ~ (bitwise_or_op ~ bitwise_xor_expression)*} 72 | bitwise_xor_expression = {bitwise_and_expression ~ (bitwise_xor_op ~ bitwise_and_expression)*} 73 | bitwise_and_expression = {equal_expression ~ (bitwise_and_op ~ equal_expression)*} 74 | equal_expression = {relational_expression ~ ((equal_op | not_equal_op) ~ relational_expression)*} 75 | relational_expression = {shift_expression ~ ((less_than_or_equal_op | greater_than_or_equal_op | less_than_op | greater_than_op) ~ shift_expression)*} 76 | shift_expression = {add_expression ~ ((left_shift_op | right_shift_op) ~ add_expression)*} 77 | add_expression = {mul_expression ~ ((add_op | sub_op) ~ mul_expression)*} 78 | mul_expression = {unary_expression ~ ((mul_op | div_op | mod_op) ~ unary_expression)*} 79 | 80 | unary_expression = {(sizeof_ ~ "(" ~ type_name ~ ")") | (prefix_unary_operator ~ unary_expression) | postfix_unary_expression} 81 | postfix_unary_expression = {primary_expression ~ (postfix_inc_op| postfix_dec_op | function_call | ("[" ~ expression ~ "]") | ((member_of_object_op | member_of_pointer_op) ~ identifier) | (as_ ~ (type_name | ("(" ~ type_name ~ ")"))))*} 82 | primary_expression = {identifier | constant | string_literal | ("(" ~ expression ~ ")")} 83 | 84 | assignment_operator = {assign_naive_op | assign_add_op | assign_sub_op | assign_mul_op | assign_div_op | assign_mod_op | assign_bitwise_and_op | assign_bitwise_or_op | assign_bitwise_xor_op | assign_left_shift_op | assign_right_shift_op} 85 | prefix_unary_operator = {prefix_inc_op | prefix_dec_op | unary_plus_op | unary_minus_op | logical_not_op | bitwise_not_op | dereference_op | reference_op | sizeof_} 86 | type_name = {declaration_specifiers ~ pointer? ~ ((("[" ~ assignment_expression ~ "]")+) | ("(" ~ function_parameter_list ~ ")"))?} 87 | function_call = {"(" ~ argument_list? ~ ")"} 88 | argument_list = {assignment_expression ~ ("," ~ assignment_expression)*} 89 | 90 | 91 | //>>>>>>>>>>>>>>>>>>>>>>> 92 | // LITERAL 93 | //<<<<<<<<<<<<<<<<<<<<<<< 94 | constant = {integer_constant | floating_constant | character_constant} 95 | 96 | integer_constant = ${(hex_constant | binary_constant | octal_constant | decimal_constant) ~ integer_suffix? ~ !("." | ^"e" | ^"p")} 97 | integer_suffix = {ull_ | ll_ | ul_ | l_ | u_} 98 | decimal_constant = @{ASCII_NONZERO_DIGIT ~ (ASCII_DIGIT)*} 99 | octal_constant = @{"0" ~ (ASCII_OCT_DIGIT)*} 100 | hex_constant = @{"0" ~ ("x" | "X") ~ (ASCII_HEX_DIGIT)*} 101 | binary_constant = @{"0" ~ ("b" | "B") ~ (ASCII_BIN_DIGIT)*} 102 | 103 | floating_constant = {decimal_floating_constant | hex_floating_constant} 104 | decimal_floating_constant = ${decimal_floating_constant_no_suffix ~ floating_suffix?} 105 | decimal_floating_constant_no_suffix = @{decimal_significand ~ decimal_exponent?} 106 | decimal_significand = @{((ASCII_NONZERO_DIGIT ~ (ASCII_DIGIT)*) ~ "."? ~ ((ASCII_DIGIT)+)?) | ((ASCII_NONZERO_DIGIT? ~ (ASCII_DIGIT)*) ~ "."? ~ ((ASCII_DIGIT)+))} 107 | decimal_exponent = @{(^"e" ~ ("+"|"-")? ~ ASCII_DIGIT+)} 108 | hex_floating_constant = ${hex_floating_constant_no_suffix ~ floating_suffix?} 109 | hex_floating_constant_no_suffix = @{hex_significand ~ hex_exponent?} 110 | hex_significand = @{"0" ~ ("x" | "X") ~ (((ASCII_HEX_DIGIT) ~ "."? ~ ((ASCII_HEX_DIGIT)+)?) | ((ASCII_HEX_DIGIT)? ~ "."? ~ ((ASCII_HEX_DIGIT)+)))} 111 | hex_exponent = @{(^"p" ~ ("+"|"-")? ~ ASCII_DIGIT+)} 112 | floating_suffix = {f_ | l_} 113 | 114 | character_constant = ${PUSH("'") ~ (char_no_escape | escape_sequence) ~ POP} 115 | string_literal = ${PUSH("\"") ~ (char_no_escape | escape_sequence)* ~ POP} 116 | char_no_escape = @{!(PEEK | "\\" | NEWLINE) ~ ANY} 117 | 118 | escape_sequence = @{"\\'" | "\\\"" | "\\?" | "\\\\" | "\\a" | "\\b" | "\\f" | "\\n" | "\\r" | "\\t" | "\\v" | ("\\" ~ ASCII_OCT_DIGIT{1, 3}) | ("\\x" ~ ASCII_HEX_DIGIT{1, 2}) | ("\\u" ~ ASCII_HEX_DIGIT{4}) | ("\\U" ~ ASCII_HEX_DIGIT{8})} 119 | 120 | 121 | //>>>>>>>>>>>>>>>>>>>>>>> 122 | // TOKEN 123 | //<<<<<<<<<<<<<<<<<<<<<<< 124 | identifier = @{!keyword ~ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")*} 125 | 126 | keyword = @{(as_ | auto_ | break_ | case_ | char_ | const_ | continue_ | default_ | do_ | double_ | else_ | enum_ | extern_ | float_ | for_ | goto_ | if_ | inline_ | int_ | long_ | register_ | restrict_ | return_ | short_ | signed_ | sizeof_ | static_ | struct_ | switch_ | typedef_ | union_ | unsigned_ | void_ | volatile_ | while_ | alignas_ | alignof_ | atomic_ | bool_ | complex_ | generic_ | imaginary_ | noreturn_ | static_assert_ | thread_local_) ~ !(ASCII_ALPHA | "_")} 127 | 128 | as_ = {"as"} 129 | auto_ = {"auto"} 130 | break_ = {"break"} 131 | case_ = {"case"} 132 | char_ = {"char"} 133 | const_ = {"const"} 134 | continue_ = {"continue"} 135 | default_ = {"default"} 136 | do_ = {"do"} 137 | double_ = {"double"} 138 | else_ = {"else"} 139 | enum_ = {"enum"} 140 | extern_ = {"extern"} 141 | float_ = {"float"} 142 | for_ = {"for"} 143 | goto_ = {"goto"} 144 | if_ = {"if"} 145 | inline_ = {"inline"} 146 | int_ = {"int"} 147 | long_ = {"long"} 148 | register_ = {"register"} 149 | restrict_ = {"restrict"} 150 | return_ = {"return"} 151 | short_ = {"short"} 152 | signed_ = {"signed"} 153 | sizeof_ = {"sizeof"} 154 | static_ = {"static"} 155 | struct_ = {"struct"} 156 | switch_ = {"switch"} 157 | typedef_ = {"typedef"} 158 | union_ = {"union"} 159 | unsigned_ = {"unsigned"} 160 | void_ = {"void"} 161 | volatile_ = {"volatile"} 162 | while_ = {"while"} 163 | alignas_ = {"_Alignas"} 164 | alignof_ = {"_Alignof"} 165 | atomic_ = {"_Atomic"} 166 | bool_ = {"_Bool"} 167 | complex_ = {"_Complex"} 168 | generic_ = {"_Generic"} 169 | imaginary_ = {"_Imaginary"} 170 | noreturn_ = {"_Noreturn"} 171 | static_assert_ = {"_Static_assert"} 172 | thread_local_ = {"_Thread_local"} 173 | 174 | assign_naive_op = {"="} 175 | assign_add_op = {"+="} 176 | assign_sub_op = {"-="} 177 | assign_mul_op = {"*="} 178 | assign_div_op = {"/="} 179 | assign_mod_op = {"%="} 180 | assign_bitwise_and_op = {"&="} 181 | assign_bitwise_or_op = {"|="} 182 | assign_bitwise_xor_op = {"^="} 183 | assign_left_shift_op = {"<<="} 184 | assign_right_shift_op = {">>="} 185 | 186 | prefix_inc_op = {"++"} 187 | prefix_dec_op = {"--"} 188 | postfix_inc_op = {"++"} 189 | postfix_dec_op = {"--"} 190 | unary_plus_op = {"+" ~ !("+" | "=")} 191 | unary_minus_op = {"-" ~ !("-" | "=")} 192 | bitwise_not_op = {"~"} 193 | logical_not_op = {"!" ~ !("=")} 194 | reference_op = {"&" ~ !("&" | "=")} 195 | dereference_op = {"*"} 196 | 197 | add_op = {"+" ~ !("+" | "=")} 198 | sub_op = {"-" ~ !("-" | "=")} 199 | mul_op = {"*" ~ !("=")} 200 | div_op = {"/" ~ !("=")} 201 | mod_op = {"%" ~ !("=")} 202 | bitwise_and_op = {"&" ~ !("&" | "=")} 203 | bitwise_or_op = {"|" ~ !("|" | "=")} 204 | bitwise_xor_op = {"^" ~ !("=")} 205 | left_shift_op = {"<<" ~ !("=")} 206 | right_shift_op = {">>" ~ !("=")} 207 | logical_and_op = {"&&"} 208 | logical_or_op = {"||"} 209 | equal_op = {"=="} 210 | not_equal_op = {"!="} 211 | less_than_op = {"<"} 212 | less_than_or_equal_op = {"<="} 213 | greater_than_op = {">"} 214 | greater_than_or_equal_op = {">="} 215 | 216 | member_of_object_op = {"."} 217 | member_of_pointer_op = {"->"} 218 | 219 | star_ = {"*"} 220 | variadic_argument_ = {"..."} 221 | u_ = {^"u"} 222 | l_ = {^"l"} 223 | ul_ = {^"ul" | ^"lu"} 224 | ll_ = {^"ll"} 225 | ull_ = {^"ull" | ^"llu"} 226 | f_ = {^"f"} 227 | 228 | WHITESPACE = _{ " " | NEWLINE | "\t"} 229 | -------------------------------------------------------------------------------- /src/preprocess/mod.rs: -------------------------------------------------------------------------------- 1 | use pest::Parser; 2 | use std::collections::HashMap; 3 | use std::error::Error; 4 | use std::fs; 5 | use typed_arena::Arena; 6 | 7 | mod phase2; 8 | mod phase3; 9 | mod phase4; 10 | mod phase6; 11 | 12 | use phase2::*; 13 | use phase3::*; 14 | use phase4::*; 15 | use phase6::*; 16 | 17 | pub fn preprocess_file(path: &str, include_dirs: &[&str]) -> Result> { 18 | let source_content = 19 | fs::read_to_string(path).unwrap_or_else(|_| panic!("Unable to read source file {}", path)); 20 | preprocess(&source_content, include_dirs) 21 | } 22 | 23 | pub fn preprocess(code: &str, include_dirs: &[&str]) -> Result> { 24 | let code = phase2(code); 25 | let code = phase3(&code)?; 26 | 27 | let mut defined: HashMap = Default::default(); 28 | let code_arena = Arena::new(); 29 | let code = phase4(&code, &mut defined, include_dirs, &code_arena)?; 30 | 31 | let code = phase6(&code)?; 32 | Ok(code) 33 | } 34 | 35 | #[cfg(test)] 36 | mod tests { 37 | use super::*; 38 | 39 | #[test] 40 | #[should_panic] 41 | fn process_comments_fail() { 42 | let code = r#"/* "#; 43 | let include_dirs = vec![]; 44 | println!("result: {}", preprocess(code, &include_dirs).unwrap()); 45 | } 46 | 47 | #[test] 48 | fn process_comments() { 49 | let code = r#" 50 | int main() { 51 | '"'; "//"; 52 | // This is a comment 53 | /* This is a comment */ 54 | /** This is a comment 55 | // 56 | */ 57 | return 0; 58 | } 59 | "#; 60 | let expected = r#" 61 | int main() { 62 | '"'; "//"; 63 | 64 | 65 | 66 | return 0; 67 | } 68 | "#; 69 | let include_dirs = vec![]; 70 | assert_eq!(expected, preprocess(code, &include_dirs).unwrap()); 71 | } 72 | 73 | #[test] 74 | fn process_continued_lines() { 75 | let code = r#" 76 | int main() { \ 77 | } 78 | "#; 79 | let expected = r#" 80 | int main() { } 81 | "#; 82 | let include_dirs = vec![]; 83 | assert_eq!(expected, preprocess(code, &include_dirs).unwrap()); 84 | } 85 | 86 | #[test] 87 | fn process_continued_lines_and_comments() { 88 | let code = r#" 89 | int main() { 90 | // This is a comment \ 91 | This is a comment, too 92 | return 0; 93 | } 94 | "#; 95 | let expected = r#" 96 | int main() { 97 | 98 | return 0; 99 | } 100 | "#; 101 | let include_dirs = vec![]; 102 | assert_eq!(expected, preprocess(code, &include_dirs).unwrap()); 103 | } 104 | 105 | #[test] 106 | fn combine_adjacent_strings() { 107 | let code = r#" 108 | int main() { 109 | char *x = "x" "y""z"; 110 | } 111 | "#; 112 | let expected = r#" 113 | int main() { 114 | char *x = "xyz"; 115 | } 116 | "#; 117 | let include_dirs = vec![]; 118 | assert_eq!(expected, preprocess(code, &include_dirs).unwrap()); 119 | } 120 | 121 | #[test] 122 | fn process_object_define() { 123 | let code = r#" 124 | #define b 0 125 | #define x b 126 | int main() { 127 | return x; 128 | } 129 | "#; 130 | let expected = r#" 131 | int main() { 132 | return 0; 133 | } 134 | "#; 135 | let include_dirs = vec![]; 136 | assert_eq!(expected, preprocess(code, &include_dirs).unwrap()); 137 | } 138 | 139 | #[test] 140 | fn process_conditional_macro() { 141 | let code = r#" 142 | #define x 143 | #ifndef x 144 | #define x 1 145 | #else 146 | #if defined(y) 147 | #elif defined(x) 148 | #define x 0 149 | #endif 150 | #endif 151 | int main() { return x; } 152 | "#; 153 | let expected = r#" 154 | int main() { return 0; } 155 | "#; 156 | let include_dirs = vec![]; 157 | assert_eq!(expected, preprocess(code, &include_dirs).unwrap()); 158 | } 159 | 160 | #[test] 161 | fn process_function_define() { 162 | let code = r#" 163 | #define x(a) a 164 | #define m_1(a, b) a##b 165 | #define m_2(a) #a 166 | #define m_3(a, ...) #__VA_ARGS__ 167 | #define m_4() 0 168 | int main() { 169 | m_1(i, j); 170 | m_2(sadf); 171 | m_3(1, 2, -(3*4, 7), " ,"); 172 | return x(0)m_4(); 173 | } 174 | "#; 175 | let expected = r#" 176 | int main() { 177 | ij; 178 | "sadf"; 179 | 2,-(3*4,7)," ,"; 180 | return 00; 181 | } 182 | "#; 183 | let include_dirs = vec![]; 184 | assert_eq!(expected, preprocess(code, &include_dirs).unwrap()); 185 | } 186 | 187 | #[test] 188 | fn process_recursive_define() { 189 | let code = r#" 190 | #define a b 191 | #define b a 192 | int main() { 193 | int a = 0; 194 | int c = a; 195 | return c; 196 | } 197 | "#; 198 | let expected = r#" 199 | int main() { 200 | int a = 0; 201 | int c = a; 202 | return c; 203 | } 204 | "#; 205 | let include_dirs = vec![]; 206 | assert_eq!(expected, preprocess(code, &include_dirs).unwrap()); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/preprocess/phase2.rs: -------------------------------------------------------------------------------- 1 | pub fn phase2(code: &str) -> String { 2 | let mut code = code.replace("\\\n", ""); 3 | if !code.ends_with('\n') { 4 | code.push('\n'); 5 | } 6 | code 7 | } 8 | -------------------------------------------------------------------------------- /src/preprocess/phase3.pest: -------------------------------------------------------------------------------- 1 | cc99 = { SOI ~ (char_literal | string_literal | c_comment | cpp_comment | code)* ~ EOI } 2 | 3 | escape_sequence = {"\\'" | "\\\"" | "\\?" | "\\\\" | "\\a" | "\\b" | "\\f" | "\\n" | "\\r" | "\\t" | "\\v" 4 | | ("\\" ~ ASCII_OCT_DIGIT{1, 3}) | ("\\x" ~ ASCII_HEX_DIGIT{1, 2}) 5 | | ("\\u" ~ ASCII_HEX_DIGIT{4}) | ("\\U" ~ ASCII_HEX_DIGIT{8})} 6 | char_literal = {"'" ~ ((!("'" | "\\" | NEWLINE) ~ ANY) | escape_sequence) ~ "'"} 7 | string_literal = {"\"" ~ ((!("\"" | "\\" | NEWLINE) ~ ANY) | escape_sequence)* ~ "\""} 8 | 9 | c_comment = {"/*" ~ (!"*/" ~ ANY)* ~ "*/"} 10 | cpp_comment = {"//" ~ (!NEWLINE ~ ANY)* ~ (NEWLINE | EOI)} 11 | 12 | code = {!"/*" ~ ANY} 13 | -------------------------------------------------------------------------------- /src/preprocess/phase3.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Parser)] 4 | #[grammar = "./preprocess/phase3.pest"] 5 | struct Phase3Parser; 6 | 7 | pub fn phase3(code: &str) -> Result> { 8 | let pairs = match Phase3Parser::parse(Rule::cc99, code)?.next() { 9 | Some(p) => p.into_inner(), 10 | None => unreachable!(), 11 | }; 12 | let mut result = String::new(); 13 | for pair in pairs { 14 | match pair.as_rule() { 15 | Rule::cpp_comment => result.push('\n'), 16 | Rule::c_comment => result.push(' '), 17 | _ => result.push_str(pair.as_str()), 18 | } 19 | } 20 | Ok(result) 21 | } 22 | -------------------------------------------------------------------------------- /src/preprocess/phase4.pest: -------------------------------------------------------------------------------- 1 | cc99 = { SOI ~ group* ~ EOI } 2 | 3 | group = {control_line | conditional | token_string_line} 4 | 5 | conditional = {if_line ~ group* ~ (elif_line ~ group*)* ~ (else_line ~ group*)? ~ endif_line} 6 | if_line = {(("#" ~ if__ ~ constant_expression) | ("#" ~ ifdef__ ~ identifier) | ("#" ~ ifndef__ ~ identifier)) ~ NEWLINE} 7 | elif_line = {("#" ~ elif__ ~ constant_expression) ~ NEWLINE} 8 | else_line = {"#" ~ else__ ~ NEWLINE} 9 | endif_line = {"#" ~ endif__ ~ NEWLINE} 10 | 11 | control_line = {(function_like_macro | object_like_macro | current_include | standard_include | line_info | undef_macro | error_macro | pragma_macro) ~ NEWLINE} 12 | function_like_macro = {"#" ~ define__ ~ identifier ~ "(" ~ (identifier ~ ("," ~ identifier)*)? ~ ("," ~ variadic_)? ~ ")" ~ token_string?} 13 | object_like_macro = {"#" ~ define__ ~ identifier ~ token_string?} 14 | current_include = {"#" ~ include__ ~ "\"" ~ path_spec ~ "\""} 15 | standard_include = {"#" ~ include__ ~ "<" ~ path_spec ~ ">"} 16 | line_info = {"#" ~ line__ ~ digit_sequence ~ ("\"" ~ filename ~ "\"")?} 17 | undef_macro = {"#" ~ undef__ ~ identifier} 18 | error_macro = {"#" ~ error__ ~ token_string} 19 | pragma_macro = {"#" ~ pragma__ ~ token_string} 20 | 21 | token_string_line = {empty_line | token_string? ~ NEWLINE} 22 | empty_line = {"#" ~ NEWLINE} 23 | 24 | token_string = {token+} 25 | token = {string_literal | constant | macro_expression | keyword | identifier | punctuator} 26 | filename = {(ASCII_ALPHANUMERIC | "-" | "_" | ".")*} 27 | path_spec = {(ASCII_ALPHANUMERIC | "-" | "_" | "." | "/" | ":")*} 28 | constant_expression = {("defined" ~ "(" ~ identifier ~ ")") | ("defined" ~ identifier)} 29 | digit_sequence = {ASCII_DIGIT+} 30 | variadic_ = {"..."} 31 | macro_expression = {token_pasting | stringizing} 32 | token_pasting = {(keyword | identifier) ~ "##" ~ (keyword | identifier)} 33 | stringizing = {("#" ~ !macro_keyword) ~ (keyword | identifier)} 34 | 35 | punctuator = {"{" | "}" | "[" | "]" | "(" | ")" | ";" | ":" | "..." | "?" | "." | "->" | "~" | "=" | "+=" | "*=" | "/=" | "%=" | "^=" | "&=" | "|=" | "==" | "!=" | "<=" | ">=" | "<<=" | ">>=" | "++" | "--"| "!" | "+" | "-" | "*" | "/" | "%" | "^" | "&" | "|" | "<" | ">" | "&&" | "||" | "<<" | ">>" | ","} 36 | macro_keyword = {define__ | include__ | line__ | undef__ | error__ | pragma__ | if__ | ifdef__ | ifndef__ | elif__ | else__ | endif__ | NEWLINE} 37 | define__ = ${"define" ~ &(WHITESPACE | NEWLINE)} 38 | include__ = ${"include" ~ &(WHITESPACE | NEWLINE | "<" | "\"")} 39 | line__ = ${"line" ~ &(WHITESPACE | NEWLINE)} 40 | undef__ = ${"undef" ~ &(WHITESPACE | NEWLINE)} 41 | error__ = ${"error" ~ &(WHITESPACE | NEWLINE)} 42 | pragma__ = ${"pragma" ~ &(WHITESPACE | NEWLINE)} 43 | if__ = ${"if" ~ &(WHITESPACE | NEWLINE)} 44 | ifdef__ = ${"ifdef" ~ &(WHITESPACE | NEWLINE)} 45 | ifndef__ = ${"ifndef" ~ &(WHITESPACE | NEWLINE)} 46 | elif__ = ${"elif" ~ &(WHITESPACE | NEWLINE)} 47 | else__ = ${"else" ~ &(WHITESPACE | NEWLINE)} 48 | endif__ = ${"endif" ~ &(WHITESPACE | NEWLINE)} 49 | 50 | //>>>>>>>>>>>>>>>>>>>>>>> 51 | // LITERAL 52 | //<<<<<<<<<<<<<<<<<<<<<<< 53 | constant = {integer_constant | floating_constant | character_constant} 54 | 55 | integer_constant = ${(hex_constant | binary_constant | octal_constant | decimal_constant) ~ integer_suffix? ~ !("." | ^"e" | ^"p")} 56 | integer_suffix = {ull_ | ll_ | ul_ | l_ | u_} 57 | decimal_constant = @{ASCII_NONZERO_DIGIT ~ (ASCII_DIGIT)*} 58 | octal_constant = @{"0" ~ (ASCII_OCT_DIGIT)*} 59 | hex_constant = @{"0" ~ ("x" | "X") ~ (ASCII_HEX_DIGIT)*} 60 | binary_constant = @{"0" ~ ("b" | "B") ~ (ASCII_BIN_DIGIT)*} 61 | 62 | floating_constant = {decimal_floating_constant | hex_floating_constant} 63 | decimal_floating_constant = ${decimal_floating_constant_no_suffix ~ floating_suffix?} 64 | decimal_floating_constant_no_suffix = @{decimal_significand ~ decimal_exponent?} 65 | decimal_significand = @{((ASCII_NONZERO_DIGIT ~ (ASCII_DIGIT)*) ~ "."? ~ ((ASCII_DIGIT)+)?) | ((ASCII_NONZERO_DIGIT? ~ (ASCII_DIGIT)*) ~ "."? ~ ((ASCII_DIGIT)+))} 66 | decimal_exponent = @{(^"e" ~ ("+"|"-")? ~ ASCII_DIGIT+)} 67 | hex_floating_constant = ${hex_floating_constant_no_suffix ~ floating_suffix?} 68 | hex_floating_constant_no_suffix = @{hex_significand ~ hex_exponent?} 69 | hex_significand = @{"0" ~ ("x" | "X") ~ (((ASCII_HEX_DIGIT) ~ "."? ~ ((ASCII_HEX_DIGIT)+)?) | ((ASCII_HEX_DIGIT)? ~ "."? ~ ((ASCII_HEX_DIGIT)+)))} 70 | hex_exponent = @{(^"p" ~ ("+"|"-")? ~ ASCII_DIGIT+)} 71 | floating_suffix = {f_ | l_} 72 | 73 | character_constant = ${PUSH("'") ~ (char_no_escape | escape_sequence) ~ POP} 74 | string_literal = ${PUSH("\"") ~ (char_no_escape | escape_sequence)* ~ POP} 75 | char_no_escape = @{!(PEEK | "\\" | NEWLINE) ~ ANY} 76 | 77 | escape_sequence = @{"\\'" | "\\\"" | "\\?" | "\\\\" | "\\a" | "\\b" | "\\f" | "\\n" | "\\r" | "\\t" | "\\v" | ("\\" ~ ASCII_OCT_DIGIT{1, 3}) | ("\\x" ~ ASCII_HEX_DIGIT{1, 2}) | ("\\u" ~ ASCII_HEX_DIGIT{4}) | ("\\U" ~ ASCII_HEX_DIGIT{8})} 78 | 79 | 80 | //>>>>>>>>>>>>>>>>>>>>>>> 81 | // TOKEN 82 | //<<<<<<<<<<<<<<<<<<<<<<< 83 | identifier = @{!keyword ~ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")*} 84 | 85 | keyword = @{(auto_ | break_ | case_ | char_ | const_ | continue_ | default_ | do_ | double_ | else_ | enum_ | extern_ | float_ | for_ | goto_ | if_ | inline_ | int_ | long_ | register_ | restrict_ | return_ | short_ | signed_ | sizeof_ | static_ | struct_ | switch_ | typedef_ | union_ | unsigned_ | void_ | volatile_ | while_ | alignas_ | alignof_ | atomic_ | bool_ | complex_ | generic_ | imaginary_ | noreturn_ | static_assert_ | thread_local_) ~ !(ASCII_ALPHA | "_")} 86 | 87 | auto_ = {"auto"} 88 | break_ = {"break"} 89 | case_ = {"case"} 90 | char_ = {"char"} 91 | const_ = {"const"} 92 | continue_ = {"continue"} 93 | default_ = {"default"} 94 | do_ = {"do"} 95 | double_ = {"double"} 96 | else_ = {"else"} 97 | enum_ = {"enum"} 98 | extern_ = {"extern"} 99 | float_ = {"float"} 100 | for_ = {"for"} 101 | goto_ = {"goto"} 102 | if_ = {"if"} 103 | inline_ = {"inline"} 104 | int_ = {"int"} 105 | long_ = {"long"} 106 | register_ = {"register"} 107 | restrict_ = {"restrict"} 108 | return_ = {"return"} 109 | short_ = {"short"} 110 | signed_ = {"signed"} 111 | sizeof_ = {"sizeof"} 112 | static_ = {"static"} 113 | struct_ = {"struct"} 114 | switch_ = {"switch"} 115 | typedef_ = {"typedef"} 116 | union_ = {"union"} 117 | unsigned_ = {"unsigned"} 118 | void_ = {"void"} 119 | volatile_ = {"volatile"} 120 | while_ = {"while"} 121 | alignas_ = {"_Alignas"} 122 | alignof_ = {"_Alignof"} 123 | atomic_ = {"_Atomic"} 124 | bool_ = {"_Bool"} 125 | complex_ = {"_Complex"} 126 | generic_ = {"_Generic"} 127 | imaginary_ = {"_Imaginary"} 128 | noreturn_ = {"_Noreturn"} 129 | static_assert_ = {"_Static_assert"} 130 | thread_local_ = {"_Thread_local"} 131 | 132 | u_ = {^"u"} 133 | l_ = {^"l"} 134 | ul_ = {"ul" | "UL"} 135 | ll_ = {"ll" | "LL"} 136 | ull_ = {"ull" | "ULL"} 137 | f_ = {^"f"} 138 | 139 | WHITESPACE = { " " | "\t"} 140 | -------------------------------------------------------------------------------- /src/preprocess/phase6.pest: -------------------------------------------------------------------------------- 1 | cc99 = { SOI ~ (char_literal | sequence_string_literal | string_literal | code)* ~ EOI } 2 | 3 | escape_sequence = {"\\'" | "\\\"" | "\\?" | "\\\\" | "\\a" | "\\b" | "\\f" | "\\n" | "\\r" | "\\t" | "\\v" 4 | | ("\\" ~ ASCII_OCT_DIGIT{1, 3}) | ("\\x" ~ ASCII_HEX_DIGIT{1, 2}) 5 | | ("\\u" ~ ASCII_HEX_DIGIT{4}) | ("\\U" ~ ASCII_HEX_DIGIT{8})} 6 | char_literal = {"'" ~ ((!("'" | "\\" | NEWLINE) ~ ANY) | escape_sequence) ~ "'"} 7 | string_literal = {"\"" ~ ((!("\"" | "\\" | NEWLINE) ~ ANY) | escape_sequence)* ~ "\""} 8 | sequence_string_literal = {string_literal ~ ((" " | "\t" | NEWLINE)* ~ string_literal)+} 9 | 10 | code = {!"/*" ~ ANY} 11 | -------------------------------------------------------------------------------- /src/preprocess/phase6.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Parser)] 4 | #[grammar = "./preprocess/phase6.pest"] 5 | struct Phase6Parser; 6 | 7 | pub fn phase6(code: &str) -> Result> { 8 | let pairs = match Phase6Parser::parse(Rule::cc99, code)?.next() { 9 | Some(p) => p.into_inner(), 10 | None => unreachable!(), 11 | }; 12 | let mut result = String::new(); 13 | for pair in pairs { 14 | match pair.as_rule() { 15 | Rule::sequence_string_literal => { 16 | result.push('"'); 17 | for literal in pair.into_inner() { 18 | if literal.as_rule() == Rule::string_literal { 19 | let literal = literal.as_str(); 20 | result.push_str(&literal[1..literal.len() - 1]); 21 | } 22 | } 23 | result.push('"'); 24 | } 25 | _ => result.push_str(pair.as_str()), 26 | } 27 | } 28 | Ok(result) 29 | } 30 | -------------------------------------------------------------------------------- /src/utils/error.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::Span; 2 | use codespan_reporting::diagnostic::{Diagnostic, Label}; 3 | use thiserror::Error; 4 | 5 | #[derive(Error, Debug)] 6 | pub struct CompileErr { 7 | code: String, 8 | message: String, 9 | label: String, 10 | span: Span, 11 | notes: Option, 12 | } 13 | 14 | impl std::fmt::Display for CompileErr { 15 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 16 | write!(f, "{}", self.message) 17 | } 18 | } 19 | 20 | impl CompileErr { 21 | pub fn plain_error(message: String, span: Span) -> Self { 22 | Self { 23 | code: "E000".to_string(), 24 | message, 25 | label: "error here".to_string(), 26 | span, 27 | notes: None, 28 | } 29 | } 30 | 31 | pub fn duplicated_global_variable(name: String, span: Span) -> CompileErr { 32 | CompileErr { 33 | code: "E001".to_string(), 34 | message: format!("duplicated global variable `{}`", name), 35 | label: "global variable duplicated here".to_string(), 36 | span, 37 | notes: None, 38 | } 39 | } 40 | 41 | pub fn duplicated_symbol(name: String, span: Span) -> CompileErr { 42 | CompileErr { 43 | code: "E002".to_string(), 44 | message: format!("duplicated symbol `{}`", name), 45 | label: "symbol duplicated here".to_string(), 46 | span, 47 | notes: None, 48 | } 49 | } 50 | 51 | pub fn unknown_expression(span: Span) -> CompileErr { 52 | CompileErr { 53 | code: "E003".to_string(), 54 | message: "unknown expression".to_string(), 55 | label: "unknown expression here".to_string(), 56 | span, 57 | notes: None, 58 | } 59 | } 60 | 61 | pub fn invalid_default_cast(from: String, to: String, span: Span) -> CompileErr { 62 | CompileErr { 63 | code: "E004".to_string(), 64 | message: "invalid default cast".to_string(), 65 | label: format!("type invalid here, which cannot be cast into `{}`", to), 66 | span, 67 | notes: Some(format!("invalid default cast from `{}` to `{}`", from, to)), 68 | } 69 | } 70 | 71 | pub fn invalid_cast(from: String, to: String, span: Span) -> CompileErr { 72 | CompileErr { 73 | code: "E005".to_string(), 74 | message: "invalid cast".to_string(), 75 | label: "invalid cast here".to_string(), 76 | span, 77 | notes: Some(format!("invalid cast from `{}` to `{}`", from, to)), 78 | } 79 | } 80 | 81 | pub fn invalid_unary(span: Span) -> CompileErr { 82 | CompileErr { 83 | code: "E006".to_string(), 84 | message: "invalid unary operator".to_string(), 85 | label: "invalid unary operator here".to_string(), 86 | span, 87 | notes: None, 88 | } 89 | } 90 | 91 | pub fn invalid_binary(span: Span) -> CompileErr { 92 | CompileErr { 93 | code: "E007".to_string(), 94 | message: "invalid binary operator".to_string(), 95 | label: "invalid binary operator here".to_string(), 96 | span, 97 | notes: None, 98 | } 99 | } 100 | 101 | pub fn duplicated_function(name: String, span: Span) -> CompileErr { 102 | CompileErr { 103 | code: "E008".to_string(), 104 | message: format!("duplicated function `{}`", name), 105 | label: "function duplicated here".to_string(), 106 | span, 107 | notes: None, 108 | } 109 | } 110 | 111 | pub fn redefinition_symbol(name: String, span: Span) -> CompileErr { 112 | CompileErr { 113 | code: "E009".to_string(), 114 | message: format!("redefinition of symbol `{}`", name), 115 | label: "symbol redefined here".to_string(), 116 | span, 117 | notes: None, 118 | } 119 | } 120 | 121 | pub fn missing_variable(name: String, span: Span) -> CompileErr { 122 | CompileErr { 123 | code: "E010".to_string(), 124 | message: format!("missing variable `{}`", name), 125 | label: "variable missing here".to_string(), 126 | span, 127 | notes: None, 128 | } 129 | } 130 | 131 | pub fn duplicated_variable(name: String, span: Span) -> CompileErr { 132 | CompileErr { 133 | code: "E011".to_string(), 134 | message: format!("duplicated variable `{}`", name), 135 | label: "variable duplicated here".to_string(), 136 | span, 137 | notes: None, 138 | } 139 | } 140 | 141 | pub fn keyword_not_in_a_loop(keyword: String, span: Span) -> CompileErr { 142 | CompileErr { 143 | code: "E012".to_string(), 144 | message: format!("keyword `{}` is not in a loop", keyword), 145 | label: "keyword here, which is not in a loop".to_string(), 146 | span, 147 | notes: None, 148 | } 149 | } 150 | 151 | pub fn array_dimension_mismatch(expect: usize, found: usize, span: Span) -> CompileErr { 152 | CompileErr { 153 | code: "E013".to_string(), 154 | message: "array dimension mismatch".to_string(), 155 | label: "this array subscript is invalid".to_string(), 156 | span, 157 | notes: Some(format!( 158 | "array has `{}` dimension, but found `{}` subscript", 159 | expect, found 160 | )), 161 | } 162 | } 163 | 164 | pub fn pointer_dimension_mismatch(expect: usize, found: usize, span: Span) -> CompileErr { 165 | CompileErr { 166 | code: "E014".to_string(), 167 | message: "pointer dimension mismatch".to_string(), 168 | label: "pointer dimension mismatch here".to_string(), 169 | span, 170 | notes: Some(format!( 171 | "pointer dimension mismatch, expect {}, found {}", 172 | expect, found 173 | )), 174 | } 175 | } 176 | 177 | pub fn invalid_left_value(name: String, span: Span) -> CompileErr { 178 | CompileErr { 179 | code: "E015".to_string(), 180 | message: format!( 181 | "invalid left value for a not addressable variable `{}`", 182 | name 183 | ), 184 | label: "invalid left value here".to_string(), 185 | span, 186 | notes: None, 187 | } 188 | } 189 | 190 | pub fn invalid_dereference(name: String, span: Span) -> CompileErr { 191 | CompileErr { 192 | code: "E016".to_string(), 193 | message: format!("invalid dereference for a no pointer variable `{}`", name), 194 | label: "invalid dereference here".to_string(), 195 | span, 196 | notes: None, 197 | } 198 | } 199 | 200 | pub fn parameter_count_mismatch( 201 | name: String, 202 | expect: usize, 203 | found: usize, 204 | span: Span, 205 | ) -> CompileErr { 206 | CompileErr { 207 | code: "E017".to_string(), 208 | message: "parameter count mismatch".to_string(), 209 | label: "function call here".to_string(), 210 | span, 211 | notes: Some(format!( 212 | "parameter count of function `{}` is incorrect, expect {}, found {}", 213 | name, expect, found 214 | )), 215 | } 216 | } 217 | 218 | pub fn duplicated_struct_definition(name: String, span: Span) -> CompileErr { 219 | CompileErr { 220 | code: "E018".to_string(), 221 | message: format!("duplicated struct named `{}`", name), 222 | label: "duplicated definition here".to_string(), 223 | span, 224 | notes: None, 225 | } 226 | } 227 | 228 | pub fn struct_not_found(name: String, span: Span) -> CompileErr { 229 | CompileErr { 230 | code: "E019".to_string(), 231 | message: format!("struct `{}` not found", name), 232 | label: format!( 233 | "variable declared here, but struct named `{}` not found in scope", 234 | name 235 | ), 236 | span, 237 | notes: None, 238 | } 239 | } 240 | 241 | pub fn struct_member_not_found( 242 | struct_name: String, 243 | member_name: String, 244 | span: Span, 245 | ) -> CompileErr { 246 | CompileErr { 247 | code: "E020".to_string(), 248 | message: format!("struct member `{}` not found", member_name), 249 | label: format!( 250 | "struct member `{}` accessed here, but cannot be found in struct `{}`", 251 | member_name, struct_name, 252 | ), 253 | span, 254 | notes: None, 255 | } 256 | } 257 | 258 | pub fn missing_typedef(name: String, span: Span) -> CompileErr { 259 | CompileErr { 260 | code: "E021".to_string(), 261 | message: format!("missing typedef `{}`", name), 262 | label: format!( 263 | "typedef name `{}` accessed here, but cannot be found in scope", 264 | name 265 | ), 266 | span, 267 | notes: None, 268 | } 269 | } 270 | 271 | pub fn get_member_from_not_struct(member: String, span: Span) -> CompileErr { 272 | CompileErr { 273 | code: "E022".to_string(), 274 | message: format!("can't get a member `{}` from not a struct", member), 275 | label: format!("can't get a member `{}` from not a struct", member), 276 | span, 277 | notes: None, 278 | } 279 | } 280 | 281 | pub fn invalid_size_of_type(type_name: String, span: Span) -> CompileErr { 282 | CompileErr { 283 | code: "E021".to_string(), 284 | message: format!("invalid application of 'sizeof' to a {} type", type_name), 285 | label: "invalid 'sizeof' type".to_string(), 286 | span, 287 | notes: None, 288 | } 289 | } 290 | 291 | pub fn to_diagnostic(&self, file_id: FileId) -> Diagnostic { 292 | Diagnostic::error() 293 | .with_message(self.message.clone()) 294 | .with_code(self.code.clone()) 295 | .with_labels(vec![Label::primary( 296 | file_id, 297 | (self.span.start)..(self.span.end), 298 | ) 299 | .with_message(self.label.clone())]) 300 | .with_notes(vec![self.notes.clone().unwrap_or_else(|| "".to_string())]) 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod error; 2 | 3 | pub use error::CompileErr; 4 | -------------------------------------------------------------------------------- /src/visual/mod.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | 3 | use super::ast::AST; 4 | use super::parse::Parse; 5 | use super::preprocess::preprocess; 6 | 7 | #[derive(Debug, Serialize)] 8 | struct ParseTreeNode { 9 | id: String, 10 | label: String, 11 | children: Vec, 12 | } 13 | #[derive(Debug, Serialize)] 14 | struct VisualResult { 15 | error: bool, 16 | message: String, 17 | ast: Box, 18 | } 19 | 20 | pub fn compile_result(code: &str) -> String { 21 | let mut result = VisualResult { 22 | error: false, 23 | message: String::from(""), 24 | ast: Box::new(AST::GlobalDeclaration(vec![])), 25 | }; 26 | let include_dirs = vec![]; 27 | match preprocess(code, &include_dirs) { 28 | Ok(code) => match Parse::new().parse(&code) { 29 | Ok(ast) => { 30 | result.ast = ast; 31 | } 32 | Err(error) => { 33 | result.error = true; 34 | result.message = error.to_string(); 35 | } 36 | }, 37 | Err(error) => { 38 | result.error = true; 39 | result.message = error.to_string(); 40 | } 41 | }; 42 | serde_json::to_string(&result).unwrap() 43 | } 44 | 45 | #[cfg(test)] 46 | mod tests { 47 | use super::*; 48 | 49 | #[test] 50 | fn test_compile_result() { 51 | let result = compile_result("let x = 1;"); 52 | let result = serde_json::to_string(&result).unwrap(); 53 | println!("{}", result); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/auto-advisor/auto-advisor-advanced.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | struct course { 5 | char *name; //名字,不超过5个字符 6 | int credit; //学分 7 | char ***pre_course; 8 | int pre_status_num; //有多少个类 9 | int *pre_course_num; //每个类的数量 10 | int grade; //成绩 11 | }; 12 | 13 | struct course courses[110]; 14 | int courses_num; 15 | 16 | int try_credit, got_credit, remain_credit; 17 | 18 | double gpa; 19 | //查找某字符出现次数 20 | int str_num(char *str, char c, long start, long end) { 21 | int ans=0; 22 | 23 | for (long i=start; i < end; i++) 24 | { 25 | if (str[i] == c) 26 | ans++; 27 | } 28 | return ans; 29 | } 30 | 31 | long find_char_in_range(char *str, char c, long start, long end) 32 | { 33 | long i=start; 34 | for (; i < end; i++) 35 | { 36 | if (str[i] == c) 37 | return i; 38 | } 39 | return -1l; 40 | } 41 | 42 | 43 | int get_score(char flag) 44 | { 45 | int score; 46 | if (flag == 'A') 47 | score = 4; 48 | else if (flag == 'B') 49 | score = 3; 50 | else if (flag == 'C') 51 | score = 2; 52 | else if (flag == 'D') 53 | score = 1; 54 | else if (flag == 'F') 55 | score = 0; 56 | else 57 | score = -1; 58 | return score; 59 | } 60 | 61 | int read_data() 62 | { 63 | char buf[1000]; 64 | while (scanf("%s", buf) != -1) 65 | { 66 | long index1 = find_char_in_range(buf, '|', 0, strlen(buf)); 67 | long index2 = find_char_in_range(buf, '|', index1 + 1, strlen(buf)); 68 | long index3 = find_char_in_range(buf, '|', index2 + 1, strlen(buf)); 69 | 70 | courses[courses_num].name=malloc((index1+1)*sizeof(char)); 71 | // printf("%d %d %d\n", index1, index2, index3); 72 | strncpy(courses[courses_num].name, buf, index1); 73 | courses[courses_num].name[index1]='\0'; 74 | 75 | courses[courses_num].credit = buf[index1 + 1] - '0'; 76 | 77 | courses[courses_num].grade = get_score(buf[index3 + 1]); 78 | 79 | if (courses[courses_num].grade > 0) 80 | { 81 | gpa += courses[courses_num].credit * courses[courses_num].grade; 82 | } 83 | 84 | if (courses[courses_num].grade >= 0) 85 | { 86 | try_credit += courses[courses_num].credit; 87 | } 88 | if (courses[courses_num].grade > 0) 89 | { 90 | got_credit += courses[courses_num].credit; 91 | } 92 | if (courses[courses_num].grade <= 0) 93 | { 94 | remain_credit += courses[courses_num].credit; 95 | } 96 | long semicolon = index2; 97 | long last_semicolon = index2; 98 | int pre_status_num = 0; 99 | 100 | if (index3 != index2 + 1) 101 | { 102 | int semicolon_num= str_num(buf,';',index2+1,index3); 103 | courses[courses_num].pre_course= malloc(sizeof(char**)*(semicolon_num+1)); 104 | courses[courses_num].pre_course_num= malloc(sizeof(int)*(semicolon_num+1)); 105 | // a trick, QAQ 106 | buf[index3] = ';'; 107 | while ((semicolon = find_char_in_range(buf, ';', semicolon + 1, index3 + 1)) != -1) 108 | { 109 | int comma_num= str_num(buf,',',last_semicolon+1,semicolon); 110 | courses[courses_num].pre_course[pre_status_num]= malloc(sizeof(char*)*(comma_num+1)); 111 | courses[courses_num].pre_course_num[pre_status_num]=comma_num+1; 112 | int pre_course_num = 0; 113 | long i; 114 | for (i = last_semicolon + 1; i < semicolon; i++) 115 | { 116 | if (buf[i] == ',') 117 | { 118 | courses[courses_num].pre_course[pre_status_num][pre_course_num]= malloc(sizeof(char)*(i-last_semicolon)); 119 | strncpy(courses[courses_num].pre_course[pre_status_num][pre_course_num], buf + last_semicolon + 1, 120 | i - last_semicolon - 1); 121 | courses[courses_num].pre_course[pre_status_num][pre_course_num][i - last_semicolon-1]='\0'; 122 | pre_course_num++; 123 | last_semicolon = i; 124 | } 125 | } 126 | courses[courses_num].pre_course[pre_status_num][pre_course_num]= malloc(sizeof(char)*(i-last_semicolon)); 127 | strncpy(courses[courses_num].pre_course[pre_status_num][pre_course_num], buf + last_semicolon + 1, 128 | i - last_semicolon - 1); 129 | courses[courses_num].pre_course[pre_status_num][pre_course_num][i-last_semicolon-1]='\0'; 130 | last_semicolon = semicolon; 131 | pre_status_num++; 132 | } 133 | buf[index3] = '|'; 134 | } 135 | courses[courses_num].pre_status_num = pre_status_num; 136 | courses_num++; 137 | } 138 | } 139 | 140 | int main() 141 | { 142 | read_data(); 143 | if (try_credit > 0) 144 | { 145 | gpa /= try_credit; 146 | } 147 | printf("GPA: %.1lf\n", gpa); 148 | printf("Hours Attempted: %d\n", try_credit); 149 | printf("Hours Completed: %d\n", got_credit); 150 | printf("Credits Remaining: %d\n\n", remain_credit); 151 | printf("Possible Courses to Take Next\n"); 152 | 153 | if (remain_credit == 0) 154 | { 155 | printf(" None - Congratulations!\n"); 156 | return 0; 157 | } 158 | int recommend_num = 0; 159 | for (int i = 0; i < courses_num; i++) 160 | { 161 | if (courses[i].grade <= 0) 162 | { 163 | if (courses[i].pre_status_num == 0) 164 | { 165 | printf(" %s\n", courses[i].name); 166 | recommend_num++; 167 | } 168 | else 169 | { 170 | for (int j = 0; j < courses[i].pre_status_num; j++) 171 | { 172 | int pre_num = 0; 173 | int flag = 1; 174 | 175 | for(int pre_num=0;pre_num 0) 119 | { 120 | gpa += courses_credit[courses_num] * courses_grade[courses_num]; 121 | } 122 | 123 | if (courses_grade[courses_num] >= 0) 124 | { 125 | try_credit += courses_credit[courses_num]; 126 | } 127 | if (courses_grade[courses_num] > 0) 128 | { 129 | got_credit += courses_credit[courses_num]; 130 | } 131 | if (courses_grade[courses_num] <= 0) 132 | { 133 | remain_credit += courses_credit[courses_num]; 134 | } 135 | int semicolon = index2; 136 | int last_semicolon = index2; 137 | int pre_status_num = 0; 138 | 139 | if (index3 != index2 + 1) 140 | { 141 | // a trick, QAQ 142 | buf[index3] = ';'; 143 | while ((semicolon = find_char_in_range(&buf[0], ';', semicolon + 1, index3 + 1)) != -1) 144 | { 145 | int pre_course_num = 0; 146 | int i; 147 | for (i = last_semicolon + 1; i < semicolon; i++) 148 | { 149 | if (buf[i] == ',') 150 | { 151 | strcpy_my(&courses_pre_course[courses_num][pre_status_num][pre_course_num][0], &buf[last_semicolon + 1], 152 | i - last_semicolon - 1); 153 | pre_course_num++; 154 | last_semicolon = i; 155 | } 156 | } 157 | strcpy_my(&courses_pre_course[courses_num][pre_status_num][pre_course_num][0], &buf[last_semicolon + 1], 158 | i - last_semicolon - 1); 159 | 160 | last_semicolon = semicolon; 161 | pre_status_num++; 162 | } 163 | buf[index3] = '|'; 164 | } 165 | courses_pre_course_num[courses_num] = pre_status_num; 166 | courses_num++; 167 | } 168 | } 169 | 170 | int main() 171 | { 172 | read_data(); 173 | if (try_credit > 0) 174 | { 175 | gpa /= try_credit; 176 | } 177 | printf("GPA: %.1lf\n", gpa); 178 | printf("Hours Attempted: %d\n", try_credit); 179 | printf("Hours Completed: %d\n", got_credit); 180 | printf("Credits Remaining: %d\n\n", remain_credit); 181 | printf("Possible Courses to Take Next\n"); 182 | 183 | if (remain_credit == 0) 184 | { 185 | printf(" None - Congratulations!\n"); 186 | return 0; 187 | } 188 | int recommend_num = 0; 189 | for (int i = 0; i < courses_num; i++) 190 | { 191 | if (courses_grade[i] <= 0) 192 | { 193 | if (courses_pre_course_num[i] == 0) 194 | { 195 | printf(" %s\n", &courses_name[i][0]); 196 | recommend_num++; 197 | } 198 | else 199 | { 200 | for (int j = 0; j < courses_pre_course_num[i]; j++) 201 | { 202 | int pre_num = 0; 203 | int flag = 1; 204 | while (courses_pre_course[i][j][pre_num][0] != '\0') 205 | { 206 | int k; 207 | for (k = 0; k < courses_num; k++) 208 | { 209 | if (strcmp_my(&courses_pre_course[i][j][pre_num][0], &courses_name[k][0]) == 0) 210 | { 211 | if (courses_grade[k] <= 0) 212 | { 213 | flag = 0; 214 | break; 215 | } 216 | else 217 | { 218 | break; 219 | } 220 | } 221 | } 222 | if (k == courses_num) 223 | { 224 | flag = 0; 225 | break; 226 | } 227 | pre_num++; 228 | } 229 | if (flag == 1) 230 | { 231 | printf(" %s\n", &courses_name[i][0]); 232 | recommend_num++; 233 | break; 234 | } 235 | } 236 | } 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /tests/auto-advisor/auto-advisor-without-struct.c: -------------------------------------------------------------------------------- 1 | // 2 | // Created by ChenXuzheng on 2022/3/28. 3 | // 4 | 5 | // 标准库中使用了__restrict关键词,限制Pointer aliasing,但是我们目前先不实现这个c99关键词? 6 | extern int scanf(const char *__format, ...); 7 | extern int printf(const char *__format, ...); 8 | 9 | // struct course 10 | // { 11 | // char name[5]; //名字,不超过5个字符 12 | // int credit; //学分 13 | // char pre_course[8][8][5]; 14 | // int pre_course_num; 15 | 16 | // int grade; //成绩 17 | // }; 18 | 19 | // struct course courses[110]; 20 | 21 | char courses_name[110][5]; 22 | int courses_credit[110]; 23 | char courses_pre_course[110][8][8][5]; 24 | int courses_pre_course_num[110]; 25 | int courses_grade[110]; 26 | 27 | int courses_num; 28 | 29 | int try_credit, got_credit, remain_credit; 30 | 31 | double gpa; 32 | 33 | int find_char(char *str, char c) 34 | { 35 | int i; 36 | for (i = 0; str[i] != '\0'; i++) 37 | { 38 | if (str[i] == c) 39 | return i; 40 | } 41 | return -1; 42 | } 43 | 44 | int find_char_in_range(char *str, char c, int start, int end) 45 | { 46 | int i; 47 | for (i = start; i < end; i++) 48 | { 49 | if (str[i] == c) 50 | return i; 51 | } 52 | return -1; 53 | } 54 | 55 | int strcpy_my(char *dest, char *src, int n) 56 | { 57 | int i; 58 | for (i = 0; i < n; i++) 59 | { 60 | dest[i] = src[i]; 61 | } 62 | dest[i] = '\0'; 63 | return i; 64 | } 65 | int strlen_my(char *str) 66 | { 67 | int i; 68 | for (i = 0; str[i] != '\0'; i++) 69 | ; 70 | return i; 71 | } 72 | int strcmp_my(char *str1, char *str2) 73 | { 74 | int i; 75 | for (i = 0; str1[i] != '\0' && str2[i] != '\0'; i++) 76 | { 77 | if (str1[i] != str2[i]) 78 | return str1[i] - str2[i]; 79 | } 80 | return str1[i] - str2[i]; 81 | } 82 | 83 | int get_score(char flag) 84 | { 85 | int score; 86 | if (flag == 'A') 87 | score = 4; 88 | else if (flag == 'B') 89 | score = 3; 90 | else if (flag == 'C') 91 | score = 2; 92 | else if (flag == 'D') 93 | score = 1; 94 | else if (flag == 'F') 95 | score = 0; 96 | else 97 | score = -1; 98 | return score; 99 | } 100 | 101 | int read_data() 102 | { 103 | char buf[1000]; 104 | while (scanf("%s", buf) != -1) 105 | { 106 | int index1 = find_char_in_range(buf, '|', 0, strlen_my(buf)); 107 | int index2 = find_char_in_range(buf, '|', index1 + 1, strlen_my(buf)); 108 | int index3 = find_char_in_range(buf, '|', index2 + 1, strlen_my(buf)); 109 | 110 | // printf("%d %d %d\n", index1, index2, index3); 111 | strcpy_my(courses_name[courses_num], buf, index1); 112 | courses_credit[courses_num] = buf[index1 + 1] - '0'; 113 | 114 | courses_grade[courses_num] = get_score(buf[index3 + 1]); 115 | 116 | if (courses_grade[courses_num] > 0) 117 | { 118 | gpa += courses_credit[courses_num] * courses_grade[courses_num]; 119 | } 120 | 121 | if (courses_grade[courses_num] >= 0) 122 | { 123 | try_credit += courses_credit[courses_num]; 124 | } 125 | if (courses_grade[courses_num] > 0) 126 | { 127 | got_credit += courses_credit[courses_num]; 128 | } 129 | if (courses_grade[courses_num] <= 0) 130 | { 131 | remain_credit += courses_credit[courses_num]; 132 | } 133 | int semicolon = index2; 134 | int last_semicolon = index2; 135 | int pre_status_num = 0; 136 | 137 | if (index3 != index2 + 1) 138 | { 139 | // a trick, QAQ 140 | buf[index3] = ';'; 141 | while ((semicolon = find_char_in_range(buf, ';', semicolon + 1, index3 + 1)) != -1) 142 | { 143 | int pre_course_num = 0; 144 | int i; 145 | for (i = last_semicolon + 1; i < semicolon; i++) 146 | { 147 | if (buf[i] == ',') 148 | { 149 | strcpy_my(courses_pre_course[courses_num][pre_status_num][pre_course_num], buf + last_semicolon + 1, 150 | i - last_semicolon - 1); 151 | pre_course_num++; 152 | last_semicolon = i; 153 | } 154 | } 155 | strcpy_my(courses_pre_course[courses_num][pre_status_num][pre_course_num], buf + last_semicolon + 1, 156 | i - last_semicolon - 1); 157 | 158 | last_semicolon = semicolon; 159 | pre_status_num++; 160 | } 161 | buf[index3] = '|'; 162 | } 163 | courses_pre_course_num[courses_num] = pre_status_num; 164 | courses_num++; 165 | } 166 | } 167 | 168 | int main() 169 | { 170 | read_data(); 171 | if (try_credit > 0) 172 | { 173 | gpa /= try_credit; 174 | } 175 | printf("GPA: %.1lf\n", gpa); 176 | printf("Hours Attempted: %d\n", try_credit); 177 | printf("Hours Completed: %d\n", got_credit); 178 | printf("Credits Remaining: %d\n\n", remain_credit); 179 | printf("Possible Courses to Take Next\n"); 180 | 181 | if (remain_credit == 0) 182 | { 183 | printf(" None - Congratulations!\n"); 184 | return 0; 185 | } 186 | int recommend_num = 0; 187 | for (int i = 0; i < courses_num; i++) 188 | { 189 | if (courses_grade[i] <= 0) 190 | { 191 | if (courses_pre_course_num[i] == 0) 192 | { 193 | printf(" %s\n", courses_name[i]); 194 | recommend_num++; 195 | } 196 | else 197 | { 198 | for (int j = 0; j < courses_pre_course_num[i]; j++) 199 | { 200 | int pre_num = 0; 201 | int flag = 1; 202 | while (courses_pre_course[i][j][pre_num][0] != '\0') 203 | { 204 | int k; 205 | for (k = 0; k < courses_num; k++) 206 | { 207 | if (strcmp_my(courses_pre_course[i][j][pre_num], courses_name[k]) == 0) 208 | { 209 | if (courses_grade[k] <= 0) 210 | { 211 | flag = 0; 212 | break; 213 | } 214 | else 215 | { 216 | break; 217 | } 218 | } 219 | } 220 | if (k == courses_num) 221 | { 222 | flag = 0; 223 | break; 224 | } 225 | pre_num++; 226 | } 227 | if (flag == 1) 228 | { 229 | printf(" %s\n", courses_name[i]); 230 | recommend_num++; 231 | break; 232 | } 233 | } 234 | } 235 | } 236 | } 237 | } -------------------------------------------------------------------------------- /tests/auto-advisor/auto-advisor.c: -------------------------------------------------------------------------------- 1 | // 2 | // Created by ChenXuzheng on 2022/3/28. 3 | // 4 | 5 | // 标准库中使用了__restrict关键词,限制Pointer aliasing,但是我们目前先不实现这个c99关键词? 6 | extern int scanf(const char *__format, ...); 7 | extern int printf(const char *__format, ...); 8 | 9 | struct course 10 | { 11 | char name[5]; //名字,不超过5个字符 12 | int credit; //学分 13 | char pre_course[8][8][5]; 14 | int pre_course_num; 15 | 16 | int grade; //成绩 17 | }; 18 | 19 | struct course courses[110]; 20 | int courses_num; 21 | 22 | int try_credit, got_credit, remain_credit; 23 | 24 | double gpa; 25 | 26 | int find_char(char *str, char c) 27 | { 28 | int i; 29 | for (i = 0; str[i] != '\0'; i++) 30 | { 31 | if (str[i] == c) 32 | return i; 33 | } 34 | return -1; 35 | } 36 | 37 | int find_char_in_range(char *str, char c, int start, int end) 38 | { 39 | int i; 40 | for (i = start; i < end; i++) 41 | { 42 | if (str[i] == c) 43 | return i; 44 | } 45 | return -1; 46 | } 47 | 48 | int strcpy_my(char *dest, char *src, int n) 49 | { 50 | int i; 51 | for (i = 0; i < n; i++) 52 | { 53 | dest[i] = src[i]; 54 | } 55 | dest[i] = '\0'; 56 | return i; 57 | } 58 | int strlen_my(char *str) 59 | { 60 | int i; 61 | for (i = 0; str[i] != '\0'; i++) 62 | ; 63 | return i; 64 | } 65 | int strcmp_my(char *str1, char *str2) 66 | { 67 | int i; 68 | for (i = 0; str1[i] != '\0' && str2[i] != '\0'; i++) 69 | { 70 | if (str1[i] != str2[i]) 71 | return str1[i] - str2[i]; 72 | } 73 | return str1[i] - str2[i]; 74 | } 75 | 76 | int get_score(char flag) 77 | { 78 | int score; 79 | if (flag == 'A') 80 | score = 4; 81 | else if (flag == 'B') 82 | score = 3; 83 | else if (flag == 'C') 84 | score = 2; 85 | else if (flag == 'D') 86 | score = 1; 87 | else if (flag == 'F') 88 | score = 0; 89 | else 90 | score = -1; 91 | return score; 92 | } 93 | 94 | int read_data() 95 | { 96 | char buf[1000]; 97 | while (scanf("%s", buf) != -1) 98 | { 99 | int index1 = find_char_in_range(buf, '|', 0, strlen_my(buf)); 100 | int index2 = find_char_in_range(buf, '|', index1 + 1, strlen_my(buf)); 101 | int index3 = find_char_in_range(buf, '|', index2 + 1, strlen_my(buf)); 102 | 103 | // printf("%d %d %d\n", index1, index2, index3); 104 | strcpy_my(courses[courses_num].name, buf, index1); 105 | courses[courses_num].credit = buf[index1 + 1] - '0'; 106 | 107 | courses[courses_num].grade = get_score(buf[index3 + 1]); 108 | 109 | if (courses[courses_num].grade > 0) 110 | { 111 | gpa += courses[courses_num].credit * courses[courses_num].grade; 112 | } 113 | 114 | if (courses[courses_num].grade >= 0) 115 | { 116 | try_credit += courses[courses_num].credit; 117 | } 118 | if (courses[courses_num].grade > 0) 119 | { 120 | got_credit += courses[courses_num].credit; 121 | } 122 | if (courses[courses_num].grade <= 0) 123 | { 124 | remain_credit += courses[courses_num].credit; 125 | } 126 | int semicolon = index2; 127 | int last_semicolon = index2; 128 | int pre_status_num = 0; 129 | 130 | if (index3 != index2 + 1) 131 | { 132 | // a trick, QAQ 133 | buf[index3] = ';'; 134 | while ((semicolon = find_char_in_range(buf, ';', semicolon + 1, index3 + 1)) != -1) 135 | { 136 | int pre_course_num = 0; 137 | int i; 138 | for (i = last_semicolon + 1; i < semicolon; i++) 139 | { 140 | if (buf[i] == ',') 141 | { 142 | strcpy_my(courses[courses_num].pre_course[pre_status_num][pre_course_num], buf + last_semicolon + 1, 143 | i - last_semicolon - 1); 144 | pre_course_num++; 145 | last_semicolon = i; 146 | } 147 | } 148 | strcpy_my(courses[courses_num].pre_course[pre_status_num][pre_course_num], buf + last_semicolon + 1, 149 | i - last_semicolon - 1); 150 | 151 | last_semicolon = semicolon; 152 | pre_status_num++; 153 | } 154 | buf[index3] = '|'; 155 | } 156 | courses[courses_num].pre_course_num = pre_status_num; 157 | courses_num++; 158 | } 159 | } 160 | 161 | int main() 162 | { 163 | read_data(); 164 | if (try_credit > 0) 165 | { 166 | gpa /= try_credit; 167 | } 168 | printf("GPA: %.1lf\n", gpa); 169 | printf("Hours Attempted: %d\n", try_credit); 170 | printf("Hours Completed: %d\n", got_credit); 171 | printf("Credits Remaining: %d\n\n", remain_credit); 172 | printf("Possible Courses to Take Next\n"); 173 | 174 | if (remain_credit == 0) 175 | { 176 | printf(" None - Congratulations!\n"); 177 | return 0; 178 | } 179 | int recommend_num = 0; 180 | for (int i = 0; i < courses_num; i++) 181 | { 182 | if (courses[i].grade <= 0) 183 | { 184 | if (courses[i].pre_course_num == 0) 185 | { 186 | printf(" %s\n", courses[i].name); 187 | recommend_num++; 188 | } 189 | else 190 | { 191 | for (int j = 0; j < courses[i].pre_course_num; j++) 192 | { 193 | int pre_num = 0; 194 | int flag = 1; 195 | while (courses[i].pre_course[j][pre_num][0] != '\0') 196 | { 197 | int k; 198 | for (k = 0; k < courses_num; k++) 199 | { 200 | if (strcmp_my(courses[i].pre_course[j][pre_num], courses[k].name) == 0) 201 | { 202 | if (courses[k].grade <= 0) 203 | { 204 | flag = 0; 205 | break; 206 | } 207 | else 208 | { 209 | break; 210 | } 211 | } 212 | } 213 | if (k == courses_num) 214 | { 215 | flag = 0; 216 | break; 217 | } 218 | pre_num++; 219 | } 220 | if (flag == 1) 221 | { 222 | printf(" %s\n", courses[i].name); 223 | recommend_num++; 224 | break; 225 | } 226 | } 227 | } 228 | } 229 | } 230 | } -------------------------------------------------------------------------------- /tests/basic/basic.c: -------------------------------------------------------------------------------- 1 | // 2 | // Created by ChenXuzheng on 2022/3/31. 3 | // 4 | 5 | int main(){ 6 | return 0; 7 | } -------------------------------------------------------------------------------- /tests/binary/binary.c: -------------------------------------------------------------------------------- 1 | // 2 | // Created by ChenXuzheng on 2022/3/31. 3 | // 4 | 5 | int add(){ 6 | int a=2+2; 7 | return a; 8 | } 9 | 10 | int minus(){ 11 | int a=2-2; 12 | return a; 13 | } 14 | 15 | int mul(){ 16 | int a=1+2*3-4; 17 | return a*2; 18 | } 19 | 20 | int div(){ 21 | int b=2; 22 | int a=b/2; 23 | return a; 24 | } 25 | 26 | -------------------------------------------------------------------------------- /tests/condition/condition.c: -------------------------------------------------------------------------------- 1 | // 2 | // Created by ChenXuzheng on 2022/3/31. 3 | // 4 | 5 | int main(){ 6 | int a=1; 7 | int b=2; 8 | 9 | if(a>b){ 10 | return 1; 11 | } else if(a%c\n",a,c); 10 | else 11 | { 12 | hannuo(n-1,a,c,b); 13 | printf("\t%c->%c\n",a,c); 14 | hannuo(n-1,b,a,c); 15 | } 16 | } 17 | 18 | int main() { 19 | int n; 20 | printf("请输入要移动的块数:"); 21 | scanf("%d", &n); 22 | move(n, 'a', 'b', 'c'); 23 | return 0; 24 | } -------------------------------------------------------------------------------- /tests/global/decl.c: -------------------------------------------------------------------------------- 1 | // 2 | // Created by ralxyz on 5/9/22. 3 | // 4 | 5 | int a = 1; 6 | char* b = "2"; 7 | 8 | int foo(int c, short d); 9 | -------------------------------------------------------------------------------- /tests/global/decl2.c: -------------------------------------------------------------------------------- 1 | // 2 | // Created by ralxyz on 5/15/22. 3 | // 4 | 5 | #define MAX_SIZE 10010 6 | int data[10010]; 7 | // quick_sort by recursive 快速排序,使用递归 8 | 9 | void quick_sort(int left, int right); 10 | int main(); 11 | 12 | void quick_sort(int left, int right) { 13 | int pivot; 14 | int i; 15 | int j; 16 | if (left >= right) { 17 | return; 18 | } 19 | pivot = data[left]; 20 | i = left; 21 | j = right; 22 | while (i < j) { 23 | while (i < j && data[j] >= pivot) { 24 | j -= 1; 25 | } 26 | data[i] = data[j]; 27 | while (i < j && data[i] <= pivot) { 28 | i += 1; 29 | } 30 | data[j] = data[i]; 31 | } 32 | data[i] = pivot; 33 | quick_sort(left, i - 1); 34 | quick_sort(i + 1, right); 35 | return; 36 | } 37 | 38 | int main(){ 39 | int n; 40 | n = 1; 41 | quick_sort(0, n - 1); 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /tests/loop/loop.c: -------------------------------------------------------------------------------- 1 | // 2 | // Created by ChenXuzheng on 2022/3/31. 3 | // 4 | 5 | int whileLoop(){ 6 | int a = 0; 7 | int b = 30; 8 | int c = 10; 9 | 10 | do { 11 | c = b - c; 12 | } while (c >= 0); 13 | 14 | while (a < b) { 15 | a += 1; 16 | b -= 1; 17 | 18 | if (b == 3) 19 | break; 20 | } 21 | 22 | return 0; 23 | } 24 | 25 | int forLoop(){ 26 | int b=255; 27 | for(int i=0;i>> Start compiling {} <<<", source_path); 29 | 30 | let res = preprocess_file(source_path, &include_dirs).unwrap(); 31 | let ast = Parse::new().parse(&res).unwrap(); 32 | println!("{}", serde_json::to_string(&ast).unwrap()); 33 | println!(">>> Finish Parsing <<<"); 34 | } 35 | } 36 | 37 | #[test] 38 | fn test_gen() { 39 | let code = preprocess_file("./tests/global/decl2.c", vec![].as_slice()).unwrap(); 40 | let ast = Parse::new() 41 | .parse(&code) 42 | .unwrap_or_else(|e| panic!("Parse failed:\n{}", e)); 43 | 44 | let context = Context::create(); 45 | let mut code_gen = Generator::new(&context, "./tests/global/decl2.c", &code); 46 | code_gen.gen(&ast); 47 | code_gen.out_asm_or_obj(false, None, inkwell::OptimizationLevel::None); 48 | code_gen.out_bc(None); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/matrix-multiplication/matrix-multiplication.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | 6 | int **a,**b,**c; 7 | int a_row,a_col,b_row,b_col,c_row,c_col; 8 | 9 | int **init_matrix_ptr(int row,int col){ 10 | int **tmp=malloc(sizeof(int*)*row); 11 | for(int i=0;i= right) { 16 | return; 17 | } 18 | int pivot = data[left]; 19 | int i = left; 20 | int j = right; 21 | while (i < j) { 22 | while (i < j && data[j] >= pivot) { 23 | j = j - 1; 24 | } 25 | data[i] = data[j]; 26 | while (i < j && data[i] <= pivot) { 27 | i = i + 1; 28 | } 29 | data[j] = data[i]; 30 | } 31 | data[i] = pivot; 32 | quick_sort(left, i - 1); 33 | quick_sort(i + 1, right); 34 | } 35 | 36 | int main() { 37 | int n; 38 | scanf("%d", &n); 39 | // TODO malloc要用吗? 40 | for (int i = 0, x; i < n; i = i + 1) { 41 | scanf("%d", &x); 42 | data[i] = x; 43 | } 44 | quick_sort(0, n - 1); 45 | for (int i = 0; i < n; i = i + 1) { 46 | printf("%d\n", data[i]); 47 | } 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /tests/quicksort/quicksort.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | // quick_sort by recursive 快速排序,使用递归 6 | void quick_sort(int *array, int left, int right) { 7 | if (left >= right) { 8 | return; 9 | } 10 | int pivot = array[left]; 11 | int i = left; 12 | int j = right; 13 | while (i < j) { 14 | while (i < j && array[j] >= pivot) { 15 | j--; 16 | } 17 | array[i] = array[j]; 18 | while (i < j && array[i] <= pivot) { 19 | i++; 20 | } 21 | array[j] = array[i]; 22 | } 23 | array[i] = pivot; 24 | quick_sort(array, left, i - 1); 25 | quick_sort(array, i + 1, right); 26 | } 27 | 28 | int main(){ 29 | int n; 30 | scanf("%d", &n); 31 | int *data; 32 | data=malloc(sizeof(int)*n); 33 | for(int i = 0; i < n; i++){ 34 | scanf("%d", data+i); 35 | } 36 | quick_sort(data, 0, n - 1); 37 | for(int i = 0; i < n; i++){ 38 | printf("%d\n", data[i]); 39 | } 40 | return 0; 41 | } 42 | -------------------------------------------------------------------------------- /tests/struct/struct.c: -------------------------------------------------------------------------------- 1 | struct a { 2 | int b; 3 | float c; 4 | }; 5 | 6 | struct d { 7 | double e; 8 | } f; 9 | 10 | struct { 11 | int g; 12 | float h; 13 | } i; 14 | 15 | typedef struct j { 16 | int k; 17 | } l; 18 | 19 | typedef struct { 20 | int n; 21 | } o, p; 22 | -------------------------------------------------------------------------------- /tests/type/cast.c: -------------------------------------------------------------------------------- 1 | // 2 | // Created by ChenXuzheng on 2022/3/31. 3 | // 4 | 5 | typedef int i; 6 | 7 | int cast(){ 8 | double a=10.0; 9 | double b=10.1f; 10 | 11 | 12 | int c=a as int; 13 | 14 | float e=1.2; 15 | float f=1.3f; 16 | 17 | c=e as i; 18 | 19 | void *g = (&c) as (void *); 20 | } 21 | -------------------------------------------------------------------------------- /tests/type/type.c: -------------------------------------------------------------------------------- 1 | // 2 | // Created by ChenXuzheng on 2022/3/31. 3 | // 4 | 5 | int main(){ 6 | short c = 0x3; 7 | int d = 0b1011; 8 | long f = 0xcafeba; 9 | float g = 0.5; 10 | double h = 0.25; 11 | char i = 'a'; 12 | char* s = "String Test"; 13 | 14 | short*pc = &c; 15 | int*pd = &d; 16 | long*pf = &f; 17 | float*pg = &g; 18 | double*ph = &h; 19 | char*pi = &i; 20 | 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /tests/unary/unary.c: -------------------------------------------------------------------------------- 1 | // 2 | // Created by ChenXuzheng on 2022/3/31. 3 | // 4 | 5 | 6 | int not_unary(){ 7 | int a = 1; 8 | int b = !a; 9 | return b; 10 | } 11 | 12 | int complement_unary(){ 13 | int a = 1; 14 | int b = ~a; 15 | return b; 16 | } 17 | -------------------------------------------------------------------------------- /web/web-backend/.gitignore: -------------------------------------------------------------------------------- 1 | /runtime -------------------------------------------------------------------------------- /web/web-backend/biz/handler/gen/gen.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "cc99-backend/biz/model/model_gen" 5 | "cc99-backend/biz/service/service_gen" 6 | "cc99-backend/define" 7 | "cc99-backend/utils/response" 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | func Gen(c *gin.Context) { 12 | var req model_gen.GenReq 13 | err := c.ShouldBind(&req) 14 | if err != nil { 15 | c.Set(define.CC99Response, response.JSONSt(define.StParamErr)) 16 | return 17 | } 18 | c.Set(define.CC99Response, service_gen.GenCode(c, req)) 19 | } 20 | -------------------------------------------------------------------------------- /web/web-backend/biz/handler/ping/ping.go: -------------------------------------------------------------------------------- 1 | package ping 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | func Pong(c *gin.Context) { 6 | c.JSON(200, gin.H{"st": 0, "msg": "pong!"}) 7 | } 8 | -------------------------------------------------------------------------------- /web/web-backend/biz/handler/run/run.go: -------------------------------------------------------------------------------- 1 | package run 2 | 3 | import ( 4 | "cc99-backend/biz/model/model_run" 5 | "cc99-backend/biz/service/service_run" 6 | "cc99-backend/define" 7 | "cc99-backend/utils/response" 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | func Run(c *gin.Context) { 12 | var req model_run.RunReq 13 | err := c.ShouldBind(&req) 14 | if err != nil { 15 | c.Set(define.CC99Response, response.JSONSt(define.StParamErr)) 16 | return 17 | } 18 | c.Set(define.CC99Response, service_run.Run(c, req)) 19 | } 20 | -------------------------------------------------------------------------------- /web/web-backend/biz/handler/visual/visual.go: -------------------------------------------------------------------------------- 1 | package visual 2 | 3 | import ( 4 | "cc99-backend/biz/model/model_visual" 5 | "cc99-backend/biz/service/service_visual" 6 | "cc99-backend/define" 7 | "cc99-backend/utils/response" 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | func Visual(c *gin.Context) { 12 | var req model_visual.VisualReq 13 | err := c.ShouldBind(&req) 14 | if err != nil { 15 | c.Set(define.CC99Response, response.JSONSt(define.StParamErr)) 16 | return 17 | } 18 | c.Set(define.CC99Response, service_visual.VisualCode(c, req)) 19 | } 20 | -------------------------------------------------------------------------------- /web/web-backend/biz/model/model_gen/gen.go: -------------------------------------------------------------------------------- 1 | package model_gen 2 | 3 | type GenReq struct { 4 | CompileOptions string `json:"compileOptions" form:"compileOptions"` 5 | Code string `json:"code" form:"code" binding:"required"` 6 | } 7 | 8 | type GenResp struct { 9 | ExitCode int `json:"exitCode"` 10 | File string `json:"file"` 11 | Stdout string `json:"stdout"` 12 | Stderr string `json:"stderr"` 13 | } 14 | -------------------------------------------------------------------------------- /web/web-backend/biz/model/model_run/run.go: -------------------------------------------------------------------------------- 1 | package model_run 2 | 3 | type RunReq struct { 4 | File string `json:"file" binding:"required"` 5 | ExecArgs string `json:"execArgs" form:"execArgs"` 6 | Stdin string `json:"stdin"` 7 | } 8 | 9 | type RunResp struct { 10 | ExitCode int `json:"exitCode"` 11 | Stdout string `json:"stdout"` 12 | Stderr string `json:"stderr"` 13 | } 14 | -------------------------------------------------------------------------------- /web/web-backend/biz/model/model_visual/visual.go: -------------------------------------------------------------------------------- 1 | package model_visual 2 | 3 | type VisualReq struct { 4 | Code string `json:"code" form:"code"` 5 | } 6 | 7 | type VisualResp struct { 8 | Res string `json:"res" form:"res"` 9 | } 10 | -------------------------------------------------------------------------------- /web/web-backend/biz/router.go: -------------------------------------------------------------------------------- 1 | package biz 2 | 3 | import ( 4 | "cc99-backend/biz/handler/gen" 5 | "cc99-backend/biz/handler/ping" 6 | "cc99-backend/biz/handler/run" 7 | "cc99-backend/biz/handler/visual" 8 | "cc99-backend/mw" 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | func InitRouter() *gin.Engine { 13 | r := gin.New() 14 | r.Use(gin.Logger()) 15 | r.Use(mw.RecoverMiddleware) 16 | r.Use(mw.CorsMiddleware) // CORS中间件 必须在路由前配置 17 | api := r.Group("/api", 18 | mw.ResponseMiddleware, // response middleware 19 | ) 20 | api.GET("/ping", ping.Pong) // ping 21 | api.POST("/gen", gen.Gen) 22 | api.POST("/run", run.Run) 23 | api.POST("/visual", visual.Visual) 24 | 25 | return r 26 | 27 | } 28 | -------------------------------------------------------------------------------- /web/web-backend/biz/service/service_gen/gen.go: -------------------------------------------------------------------------------- 1 | package service_gen 2 | 3 | import ( 4 | "bytes" 5 | "cc99-backend/biz/model/model_gen" 6 | "cc99-backend/define" 7 | "cc99-backend/utils/Rand" 8 | "cc99-backend/utils/response" 9 | "fmt" 10 | "github.com/gin-gonic/gin" 11 | "os" 12 | "os/exec" 13 | ) 14 | 15 | func GenCode(c *gin.Context, data model_gen.GenReq) response.Response { 16 | codeFile, err := os.CreateTemp("", "cc99.*.c") 17 | if err != nil { 18 | return response.JSONStWithMsg(define.StIOErr, err.Error()) 19 | } 20 | defer codeFile.Close() 21 | 22 | _, err = codeFile.WriteString(data.Code) 23 | if err != nil { 24 | return response.JSONStWithMsg(define.StIOErr, err.Error()) 25 | } 26 | fmt.Println() 27 | _ = codeFile.Sync() 28 | outputFile := Rand.RandomString(10) 29 | var cmd *exec.Cmd 30 | if len(data.CompileOptions) == 0 { 31 | cmd = exec.Command(define.CC99Bin, "-o", fmt.Sprintf("runtime/%s", outputFile), codeFile.Name()) 32 | } else { 33 | cmd = exec.Command(define.CC99Bin, data.CompileOptions, "-o", fmt.Sprintf("runtime/%s", outputFile), codeFile.Name()) 34 | } 35 | 36 | var stdout, stderr bytes.Buffer 37 | cmd.Stdout = &stdout 38 | cmd.Stderr = &stderr 39 | err = cmd.Start() 40 | if err != nil { 41 | return response.JSONStWithMsg(define.StIOErr, err.Error()) 42 | } 43 | err = cmd.Wait() 44 | 45 | if err != nil { 46 | retCode := err.(*exec.ExitError).ExitCode() 47 | return response.JSONData(model_gen.GenResp{ExitCode: retCode, File: outputFile, Stdout: stdout.String(), Stderr: stderr.String()}) 48 | } 49 | return response.JSONData(model_gen.GenResp{ExitCode: 0, File: outputFile, Stdout: stdout.String(), Stderr: stderr.String()}) 50 | } 51 | -------------------------------------------------------------------------------- /web/web-backend/biz/service/service_run/run.go: -------------------------------------------------------------------------------- 1 | package service_run 2 | 3 | import ( 4 | "bytes" 5 | "cc99-backend/biz/model/model_gen" 6 | "cc99-backend/biz/model/model_run" 7 | "cc99-backend/define" 8 | "cc99-backend/utils/response" 9 | "fmt" 10 | "github.com/gin-gonic/gin" 11 | "os" 12 | "os/exec" 13 | "strings" 14 | ) 15 | 16 | func Run(c *gin.Context, data model_run.RunReq) response.Response { 17 | _, err := os.Stat(fmt.Sprintf("runtime/%s", data.File)) 18 | if err != nil { 19 | return response.JSONStWithMsg(define.StIOErr, "don't have a file named "+data.File) 20 | } 21 | 22 | cmd := exec.Command("timeout", "10", fmt.Sprintf("runtime/%s", data.File), data.ExecArgs) 23 | 24 | var stdout, stderr bytes.Buffer 25 | cmd.Stdin = strings.NewReader(data.Stdin) 26 | cmd.Stdout = &stdout 27 | cmd.Stderr = &stderr 28 | err = cmd.Start() 29 | if err != nil { 30 | return response.JSONStWithMsg(define.StIOErr, err.Error()) 31 | } 32 | err = cmd.Wait() 33 | if err != nil { 34 | retCode := err.(*exec.ExitError).ExitCode() 35 | return response.JSONData(model_gen.GenResp{ExitCode: retCode, Stdout: stdout.String(), Stderr: stderr.String()}) 36 | } 37 | return response.JSONData(model_gen.GenResp{ExitCode: 0, Stdout: stdout.String(), Stderr: stderr.String()}) 38 | } 39 | -------------------------------------------------------------------------------- /web/web-backend/biz/service/service_visual/visual.go: -------------------------------------------------------------------------------- 1 | package service_visual 2 | 3 | import ( 4 | "bytes" 5 | "cc99-backend/biz/model/model_visual" 6 | "cc99-backend/define" 7 | "cc99-backend/utils/response" 8 | "github.com/gin-gonic/gin" 9 | "os/exec" 10 | "strings" 11 | ) 12 | 13 | func VisualCode(c *gin.Context, data model_visual.VisualReq) response.Response { 14 | cmd := exec.Command(define.CC99Bin, "-V", "-") 15 | var stdout bytes.Buffer 16 | cmd.Stdin = strings.NewReader(data.Code) 17 | cmd.Stdout = &stdout 18 | err := cmd.Start() 19 | if err != nil { 20 | return response.JSONStWithMsg(define.StIOErr, err.Error()) 21 | } 22 | _ = cmd.Wait() 23 | 24 | return response.JSONData(model_visual.VisualResp{Res: stdout.String()}) 25 | } 26 | -------------------------------------------------------------------------------- /web/web-backend/define/consts.go: -------------------------------------------------------------------------------- 1 | package define 2 | 3 | const CC99Response = "CC99RESPONSE" 4 | 5 | const CC99Bin = "cc99" 6 | -------------------------------------------------------------------------------- /web/web-backend/define/st.go: -------------------------------------------------------------------------------- 1 | package define 2 | 3 | import "fmt" 4 | 5 | type St int64 6 | 7 | const ( 8 | StOk St = 0 // 正常 9 | 10 | StParamErr St = 10001 // 参数错误 11 | StNoLoginUser St = 10002 // 未登录 12 | StPermissionErr St = 10003 // 无权限 13 | StLoginErr St = 10004 // 账户或密码错误 14 | StDuplicateJoinErr St = 10005 // 账户已注册 15 | StNoUser St = 10006 // 账户不存在 16 | StTokenExpired St = 10007 // token过期 17 | StInvalidUserParam St = 10008 // 无效的用户注册参数 18 | StRPCErr St = 20001 // RPC失败 19 | StServerErr St = 20002 // 服务器错误 20 | 21 | StIOErr St = 20000 //文件相关错误 22 | StDBErr St = 30000 // 数据库错误 23 | StUrlErr St = 40000 //URL错误 24 | StNoImage St = 40001 //没有图片内容 25 | StNetworkErr St = 50000 //网络错误 26 | StReadBodyErr St = 50001 //读取body错误 27 | ) 28 | 29 | func (s St) String() string { 30 | switch s { 31 | case StOk: 32 | return "" 33 | case StInvalidUserParam: 34 | return "用户注册参数无效" 35 | case StParamErr: 36 | return "参数错误" 37 | case StNoLoginUser: 38 | return "未登录" 39 | case StPermissionErr: 40 | return "无权限" 41 | case StLoginErr: 42 | return "账户或密码错误" 43 | case StDuplicateJoinErr: 44 | return "账户已注册" 45 | case StNoUser: 46 | return "账户不存在" 47 | case StTokenExpired: 48 | return "token过期" 49 | case StRPCErr: 50 | return "服务器远程调用失败" 51 | case StServerErr: 52 | return "服务器错误" 53 | case StDBErr: 54 | return "数据库错误" 55 | } 56 | panic(fmt.Errorf("unknown St:%d", s)) 57 | } 58 | -------------------------------------------------------------------------------- /web/web-backend/go.mod: -------------------------------------------------------------------------------- 1 | module cc99-backend 2 | 3 | go 1.18 4 | 5 | require github.com/gin-gonic/gin v1.7.7 6 | 7 | require ( 8 | github.com/gin-contrib/sse v0.1.0 // indirect 9 | github.com/go-playground/locales v0.13.0 // indirect 10 | github.com/go-playground/universal-translator v0.17.0 // indirect 11 | github.com/go-playground/validator/v10 v10.4.1 // indirect 12 | github.com/golang/protobuf v1.3.3 // indirect 13 | github.com/json-iterator/go v1.1.9 // indirect 14 | github.com/leodido/go-urn v1.2.0 // indirect 15 | github.com/mattn/go-isatty v0.0.12 // indirect 16 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect 17 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect 18 | github.com/ugorji/go/codec v1.1.7 // indirect 19 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect 20 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect 21 | gopkg.in/yaml.v2 v2.2.8 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /web/web-backend/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 5 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 6 | github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= 7 | github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= 8 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= 9 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 10 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= 11 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 12 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= 13 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 14 | github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= 15 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= 16 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= 17 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 18 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 19 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= 20 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 21 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= 22 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 23 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 24 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 25 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= 26 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 27 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= 28 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 29 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 30 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 31 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 32 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 33 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 34 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 35 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 36 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= 37 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 38 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 39 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= 40 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 41 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 42 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 43 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 44 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= 45 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 46 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= 47 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 48 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 49 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 50 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 51 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 52 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 53 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 54 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 55 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 56 | -------------------------------------------------------------------------------- /web/web-backend/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "cc99-backend/biz" 5 | "cc99-backend/define" 6 | "log" 7 | "net/http" 8 | "os/exec" 9 | "time" 10 | ) 11 | 12 | func init() { 13 | cmd := exec.Command(define.CC99Bin, "--version") 14 | if err := cmd.Start(); err != nil { 15 | log.Println("未找到cc99") 16 | log.Fatal(err) 17 | } 18 | err := cmd.Wait() 19 | if err != nil { 20 | log.Println("获取version失败,请手动重试") 21 | log.Fatal(err) 22 | } 23 | log.Println("已正确加载cc99") 24 | } 25 | 26 | func main() { 27 | router := biz.InitRouter() 28 | log.Println("[server] running on 5001") 29 | 30 | s := &http.Server{ 31 | Addr: ":5001", 32 | Handler: router, 33 | ReadTimeout: 60 * time.Second, 34 | WriteTimeout: 60 * time.Second, 35 | MaxHeaderBytes: 1 << 24, 36 | } 37 | s.ListenAndServe() 38 | } 39 | -------------------------------------------------------------------------------- /web/web-backend/mw/cors.go: -------------------------------------------------------------------------------- 1 | package mw 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | ) 7 | 8 | func CorsMiddleware(c *gin.Context) { 9 | c.Header("Access-Control-Allow-Origin", "*") 10 | c.Header("Access-Control-Allow-Headers", "Content-Type, AccessToken, X-CSRF-Token, Authorization, Token") 11 | c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, PATCH, DELETE") 12 | c.Header("Access-Control-Allow-Credentials", "true") 13 | c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type") 14 | 15 | if c.Request.Method == "OPTIONS" { 16 | c.AbortWithStatus(http.StatusNoContent) 17 | } 18 | c.Next() 19 | } 20 | -------------------------------------------------------------------------------- /web/web-backend/mw/recover.go: -------------------------------------------------------------------------------- 1 | package mw 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/gin-gonic/gin" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "runtime" 11 | ) 12 | 13 | // RecoverMiddleware 给panic兜底 14 | func RecoverMiddleware(c *gin.Context) { 15 | defer func() { 16 | if err := recover(); err != nil { 17 | stack := stack(3) 18 | log.Printf("[RecoverMiddleware] panic recovered:\n%v\nstack: \n%s", err, stack) 19 | c.Status(http.StatusInternalServerError) // 出错就500 20 | c.Abort() 21 | } 22 | }() 23 | c.Next() 24 | } 25 | 26 | var ( 27 | dunno = []byte("???") 28 | centerDot = []byte("·") 29 | dot = []byte(".") 30 | slash = []byte("/") 31 | ) 32 | 33 | // 打印堆栈 34 | // stack returns a nicely formatted stack frame, skipping skip frames. 35 | func stack(skip int) []byte { 36 | buf := new(bytes.Buffer) // the returned data 37 | // As we loop, we open files and read them. These variables record the currently 38 | // loaded file. 39 | var lines [][]byte 40 | var lastFile string 41 | for i := skip; ; i++ { // Skip the expected number of frames 42 | pc, file, line, ok := runtime.Caller(i) 43 | if !ok { 44 | break 45 | } 46 | // Print this much at least. If we can't find the source, it won't show. 47 | _, _ = fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc) 48 | if file != lastFile { 49 | data, err := ioutil.ReadFile(file) 50 | if err != nil { 51 | continue 52 | } 53 | lines = bytes.Split(data, []byte{'\n'}) 54 | lastFile = file 55 | } 56 | _, _ = fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line)) 57 | } 58 | return buf.Bytes() 59 | } 60 | 61 | // source returns a space-trimmed slice of the n'th line. 62 | func source(lines [][]byte, n int) []byte { 63 | n-- // in stack trace, lines are 1-indexed but our array is 0-indexed 64 | if n < 0 || n >= len(lines) { 65 | return dunno 66 | } 67 | return bytes.TrimSpace(lines[n]) 68 | } 69 | 70 | // function returns, if possible, the name of the function containing the PC. 71 | func function(pc uintptr) []byte { 72 | fn := runtime.FuncForPC(pc) 73 | if fn == nil { 74 | return dunno 75 | } 76 | name := []byte(fn.Name()) 77 | // The name includes the path name to the package, which is unnecessary 78 | // since the file name is already included. Plus, it has center dots. 79 | // That is, we see 80 | // runtime/debug.*T·ptrmethod 81 | // and want 82 | // *T.ptrmethod 83 | // Also the package path might contains dot (e.g. code.google.com/...), 84 | // so first eliminate the path prefix 85 | if lastSlash := bytes.LastIndex(name, slash); lastSlash >= 0 { 86 | name = name[lastSlash+1:] 87 | } 88 | if period := bytes.Index(name, dot); period >= 0 { 89 | name = name[period+1:] 90 | } 91 | name = bytes.ReplaceAll(name, centerDot, dot) 92 | return name 93 | } 94 | -------------------------------------------------------------------------------- /web/web-backend/mw/response.go: -------------------------------------------------------------------------------- 1 | package mw 2 | 3 | import ( 4 | "cc99-backend/define" 5 | "cc99-backend/utils/response" 6 | "log" 7 | 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | func ResponseMiddleware(c *gin.Context) { 12 | c.Next() 13 | value, exists := c.Get(define.CC99Response) 14 | if !exists { 15 | log.Println("[ResponseMiddleware] response not set!") 16 | return 17 | } 18 | resp, ok := value.(response.Response) 19 | if !ok { 20 | log.Println("[ResponseMiddleware] response type invalid!") 21 | return 22 | } 23 | resp.Write(c) 24 | } 25 | -------------------------------------------------------------------------------- /web/web-backend/utils/Rand/rand.go: -------------------------------------------------------------------------------- 1 | package Rand 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | const charset = "abcdefghijklmnopqrstuvwxyz" + 9 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 10 | 11 | var seededRand = rand.New( 12 | rand.NewSource(time.Now().UnixNano())) 13 | 14 | func stringWithCharset(length int, charset string) string { 15 | b := make([]byte, length) 16 | for i := range b { 17 | b[i] = charset[seededRand.Intn(len(charset))] 18 | } 19 | return string(b) 20 | } 21 | 22 | func RandomString(length int) string { 23 | return stringWithCharset(length, charset) 24 | } 25 | -------------------------------------------------------------------------------- /web/web-backend/utils/response/failed.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | // Failed 用于异常状态 用于主动设置HTTP状态码 比如下载失败需要返回HTTP ExitCode Code = 500时使用 4 | func Failed(code int) Response { 5 | return Response{ 6 | Type: TypeFailed, 7 | FailedCode: code, 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /web/web-backend/utils/response/file.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | // File 用于文件下载 4 | func File(fileName string, fileData []byte) Response { 5 | return Response{ 6 | Type: TypeFile, 7 | File: fileData, 8 | FileName: fileName, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /web/web-backend/utils/response/image.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | func Image(fileName string) Response { 4 | return Response{ 5 | Type: TypeImage, 6 | FileName: fileName, 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /web/web-backend/utils/response/json.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | import "cc99-backend/define" 4 | 5 | // JSONData 用于正常情况 参数为body 如果没有body可以填nil 6 | func JSONData(data interface{}) Response { 7 | return Response{ 8 | Type: TypeJSON, 9 | Json: JSONResponse{ 10 | St: define.StOk, 11 | Msg: "", 12 | Data: data, 13 | }, 14 | } 15 | } 16 | 17 | // JSONSt 用于异常情况 参数为st状态码 18 | func JSONSt(st define.St) Response { 19 | return Response{ 20 | Type: TypeJSON, 21 | Json: JSONResponse{ 22 | St: st, 23 | Msg: st.String(), 24 | Data: nil, 25 | }, 26 | } 27 | } 28 | 29 | // JSONStWithMsg 用于异常情况 参数为st状态码和msg字符串 30 | func JSONStWithMsg(st define.St, msg string) Response { 31 | return Response{ 32 | Type: TypeJSON, 33 | Json: JSONResponse{ 34 | St: st, 35 | Msg: msg, 36 | Data: nil, 37 | }, 38 | } 39 | } 40 | 41 | // JSONResponse 定义JSONResponse的结构 42 | type JSONResponse struct { 43 | St define.St `json:"st"` 44 | Msg string `json:"msg"` 45 | Data interface{} `json:"data"` 46 | } 47 | 48 | func (r *JSONResponse) SetSt(st define.St) { 49 | r.St = st 50 | } 51 | -------------------------------------------------------------------------------- /web/web-backend/utils/response/redirect.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | // Redirect 用于重定向 4 | func Redirect(code int, url string) Response { 5 | return Response{ 6 | Type: TypeRedirect, 7 | RedirectURL: url, 8 | RedirectCode: code, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /web/web-backend/utils/response/response.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "net/url" 9 | "runtime" 10 | "strings" 11 | 12 | "github.com/gin-gonic/gin" 13 | jsoniter "github.com/json-iterator/go" 14 | ) 15 | 16 | // Response 定义Response的结构 17 | type Response struct { 18 | Type Type // 返回类型 19 | Json JSONResponse // JSON数据 20 | File []byte // 文件数据 21 | FileName string // 文件名 22 | FailedCode int // 出错时的http状态码 23 | RedirectURL string // 重定向url 24 | RedirectCode int // 重定向code 25 | } 26 | 27 | // Write 将Response结构体写入HTTP Response 28 | func (r *Response) Write(c *gin.Context) { 29 | switch r.Type { 30 | case TypeJSON: 31 | marshal, _ := jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(r.Json) 32 | c.JSON(http.StatusOK, json.RawMessage(marshal)) 33 | case TypeFile: 34 | escape := url.QueryEscape(r.FileName) 35 | c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", escape)) 36 | c.Data(http.StatusOK, "application/octet-stream", r.File) 37 | case TypeRedirect: 38 | c.Redirect(r.RedirectCode, r.RedirectURL) 39 | case TypeFailed: 40 | c.Status(r.FailedCode) 41 | case TypeImage: 42 | c.File(r.FileName) 43 | } 44 | } 45 | 46 | // ToJSON 用于调试, 将response的json data 转换成JSON字符串,方便打印 47 | func (r *Response) ToJSON() string { 48 | marshal, _ := jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(r.Json) 49 | return string(marshal) 50 | } 51 | 52 | // ToLocalFile 用于调试,将文件保存在本地 53 | func (r *Response) ToLocalFile() string { 54 | _, file, _, _ := runtime.Caller(1) 55 | var values = strings.Split(file, "aitour") 56 | var path = values[0] + "aitour" + "/" 57 | var targetPath = path + r.FileName 58 | 59 | switch r.Type { 60 | case TypeFile: 61 | if err := ioutil.WriteFile(targetPath, r.File, 0644); err != nil { 62 | panic("write local file failed") 63 | } 64 | 65 | default: 66 | err := fmt.Errorf("to local file only support TypeFile, but is:%v", r.Type) 67 | panic(err) 68 | } 69 | 70 | return "写入本地文件成功:" + targetPath 71 | } 72 | 73 | // String 将response的概要信息转为字符串 用于打日志 74 | func (r *Response) String() string { 75 | switch r.Type { 76 | case TypeJSON: // JSON 77 | return fmt.Sprintf("%+v", struct { 78 | Type Type // 类型 79 | Json string // JSON数据 80 | }{r.Type, r.ToJSON()}) 81 | 82 | case TypeFile: 83 | return fmt.Sprintf("%+v", struct { 84 | Type Type // 类型 85 | File string // 文件数据(只显示大小) 86 | FileName string // 文件名 87 | }{r.Type, fmt.Sprintf("<%d byte>", len(r.File)), r.FileName}) 88 | 89 | default: 90 | return "" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /web/web-backend/utils/response/type.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | // Type 也就是ResponseType 标识返回类型 4 | type Type int 5 | 6 | const TypeJSON Type = 1 7 | const TypeFile Type = 2 8 | const TypeFailed Type = 3 9 | const TypeRedirect Type = 4 10 | const TypeImage Type = 5 11 | 12 | func (t Type) String() string { 13 | switch t { 14 | case TypeJSON: 15 | return "JSON" 16 | case TypeFile: 17 | return "File" 18 | case TypeFailed: 19 | return "Failed" 20 | case TypeRedirect: 21 | return "Redirect" 22 | case TypeImage: 23 | return "Images" 24 | default: 25 | return "" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /web/web-frontend/.env: -------------------------------------------------------------------------------- 1 | GENERATE_SOURCEMAP=false -------------------------------------------------------------------------------- /web/web-frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /web/web-frontend/README.md: -------------------------------------------------------------------------------- 1 | 2 | 请提前`npm install`,同时配备一个后端,否则只有代码编辑功能 3 | -------------------------------------------------------------------------------- /web/web-frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cc99_frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@antv/g6": "^4.3.0", 7 | "@testing-library/jest-dom": "^5.16.3", 8 | "@testing-library/react": "^12.1.4", 9 | "@testing-library/user-event": "^13.5.0", 10 | "ace-builds": "^1.4.14", 11 | "ansi-to-react": "^6.1.6", 12 | "antd": "^4.19.3", 13 | "axios": "^0.27.2", 14 | "js-yaml": "^4.1.0", 15 | "react": "^17.0.0", 16 | "react-ace": "^9.5.0", 17 | "react-app-rewired": "^2.2.1", 18 | "react-dom": "^17.0.0", 19 | "react-resize-panel": "^0.3.5", 20 | "react-scripts": "5.0.0", 21 | "web-vitals": "^2.1.4" 22 | }, 23 | "scripts": { 24 | "start": "react-scripts start", 25 | "build": "react-scripts build", 26 | "test": "react-scripts test", 27 | "eject": "react-scripts eject" 28 | }, 29 | "eslintConfig": { 30 | "extends": [ 31 | "react-app", 32 | "react-app/jest" 33 | ] 34 | }, 35 | "browserslist": { 36 | "production": [ 37 | ">0.2%", 38 | "not dead", 39 | "not op_mini all" 40 | ], 41 | "development": [ 42 | "last 1 chrome version", 43 | "last 1 firefox version", 44 | "last 1 safari version" 45 | ] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /web/web-frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RalXYZ/cc99/6cf573a3b0705006292e94311eb359d68f66b2e9/web/web-frontend/public/favicon.ico -------------------------------------------------------------------------------- /web/web-frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | CC99 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /web/web-frontend/public/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/web-frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /web/web-frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /web/web-frontend/src/App.css: -------------------------------------------------------------------------------- 1 | @import '~antd/dist/antd.css'; 2 | 3 | .App { 4 | text-align: center; 5 | } 6 | 7 | .App-logo { 8 | height: 40vmin; 9 | pointer-events: none; 10 | } 11 | @media (prefers-reduced-motion: no-preference) { 12 | .App-logo { 13 | animation: App-logo-spin infinite 20s linear; 14 | } 15 | } 16 | .App-header { 17 | background-image: linear-gradient(to top, #4481eb 0%, #04befe 100%); 18 | background: linear-gradient(to top, #48c6ef 0%, #6f86d6 100%); 19 | background-clip: text; 20 | font-size: 30px; 21 | font-weight: bolder; 22 | color: transparent; 23 | } 24 | .App-link { 25 | color: #61dafb; 26 | } 27 | 28 | @keyframes App-logo-spin { 29 | from { 30 | transform: rotate(0deg); 31 | } 32 | to { 33 | transform: rotate(360deg); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /web/web-frontend/src/App.js: -------------------------------------------------------------------------------- 1 | import "./App.css"; 2 | import { 3 | Card, 4 | Select, 5 | Button, 6 | message, 7 | Layout, 8 | notification, 9 | Input, 10 | Collapse, 11 | } from "antd"; 12 | 13 | import ResizePanel from "react-resize-panel"; 14 | import { useState } from "react"; 15 | import Editor from "./components/AceEditor"; 16 | import { ExampleCode } from "./data/example"; 17 | import Ast2Vis from "./utils/AST2Vis"; 18 | import AntVG6 from "./components/AntVG6"; 19 | import axios from "axios"; 20 | import Ansi from "ansi-to-react"; 21 | 22 | const { Panel } = Collapse; 23 | const { TextArea } = Input; 24 | const { Header, Footer, Content } = Layout; 25 | const { Option } = Select; 26 | function App() { 27 | const [code, setCode] = useState(ExampleCode[0].code); 28 | const [visAst, setVisAst] = useState({ id: "0", label: "CC99" }); 29 | const [stdin, setStdin] = useState(""); 30 | const [compileOptions, setCompileOptions] = useState(""); 31 | const [execArgs, setExecArgs] = useState(""); 32 | 33 | const [stdout, setStdout] = useState(""); 34 | const [stderr, setStderr] = useState(""); 35 | const [exitCode, setExitCode] = useState(0); 36 | const [compileStatus, setCompileStatus] = useState(""); 37 | 38 | const codeSelector = ( 39 | 50 | ); 51 | 52 | const onClickRunCode = async () => { 53 | try { 54 | setStdout(""); 55 | setStderr(""); 56 | setExitCode(""); 57 | setCompileStatus(""); 58 | let result = await axios("/api/gen", { 59 | method: "post", 60 | headers: { 61 | "Content-Type": "application/json", 62 | }, 63 | data: { 64 | code, 65 | compileOptions, 66 | }, 67 | }); 68 | 69 | if (result.data.st !== 0) { 70 | setCompileStatus("Server-related errors, did not compile successfully"); 71 | notification.error({ 72 | duration: 5, 73 | description: "Server related errors", 74 | message: result.data.msg, 75 | }); 76 | return; 77 | } 78 | //查看编译是否成功,根据exitCode进行判断 79 | if (result.data.data.exitCode !== 0) { 80 | setCompileStatus("not compiled successfully"); 81 | setExitCode(result.data.data.exitCode); 82 | setStdout(result.data.data.stdout); 83 | setStderr(result.data.data.stderr); 84 | return; 85 | } 86 | let file = result.data.data.file; 87 | if (!file) { 88 | setCompileStatus("Compilation succeeds, but no files are generated"); 89 | setExitCode(result.data.data.exitCode); 90 | setStdout(result.data.data.stdout); 91 | setStderr(result.data.data.stderr); 92 | return; 93 | } 94 | let res = await axios("/api/run", { 95 | method: "post", 96 | headers: { 97 | "Content-Type": "application/json", 98 | }, 99 | data: { 100 | file, 101 | execArgs, 102 | stdin, 103 | }, 104 | }); 105 | if (res.data.st !== 0) { 106 | setCompileStatus( 107 | "Server-related errors, compilation is successful, execution errors occur" 108 | ); 109 | notification.error({ 110 | duration: 5, 111 | description: 112 | "Server-related errors, compilation is successful, execution errors occur", 113 | message: result.data.msg, 114 | }); 115 | return; 116 | } 117 | if (res.data.data.exitCode !== 0) { 118 | setCompileStatus("Compilation succeeded, execution error occurred"); 119 | } else { 120 | setCompileStatus("Compilation succeeded, execution succeeded"); 121 | } 122 | setStdout(res.data.data.stdout); 123 | setStderr(res.data.data.stderr); 124 | setExitCode(res.data.data.exitCode); 125 | await compile(); 126 | } catch (e) { 127 | notification.error({ 128 | duration: 5, 129 | description: "Unknown Error!", 130 | message: e, 131 | }); 132 | } 133 | }; 134 | 135 | const compile = async () => { 136 | try { 137 | let res = await axios("/api/visual", { 138 | method: "post", 139 | headers: { 140 | "Content-Type": "application/json", 141 | }, 142 | data: { 143 | code, 144 | }, 145 | }); 146 | if (res.data.st !== 0) { 147 | notification.error({ 148 | duration: 5, 149 | description: "Server related errors", 150 | message: res.data.msg, 151 | }); 152 | return; 153 | } 154 | let data = JSON.parse(res.data.data.res); 155 | console.log(data); 156 | if (!data["error"]) { 157 | setVisAst(Ast2Vis(data["ast"])); 158 | 159 | message.success("Compile Success!"); 160 | } else { 161 | notification.error({ 162 | message: "Compile Error", 163 | description: data["message"], 164 | duration: 5, 165 | }); 166 | } 167 | } catch (e) { 168 | notification.error({ 169 | duration: 5, 170 | description: "Server related errors", 171 | message: e, 172 | }); 173 | } 174 | // console.log(JSON.stringify(data["ast"], null, "\t")); 175 | }; 176 | return ( 177 | <> 178 | 179 |
CC99
180 | 181 |
188 | 189 | 201 | 202 | 203 | 204 | 208 | Visual! 209 | 210 | } 211 | headStyle={{ fontWeight: "bold", fontSize: 22 }} 212 | bodyStyle={{ flexGrow: 1, padding: 0, overflow: "hidden" }} 213 | style={{ 214 | flexGrow: 1, 215 | display: "flex", 216 | flexDirection: "column", 217 | }} 218 | > 219 | 220 | 221 | 222 | 223 | 227 | Run! 228 | 229 | } 230 | headStyle={{ fontWeight: "bold", fontSize: 22 }} 231 | bodyStyle={{ 232 | flexGrow: 1, 233 | overflowY: "auto", 234 | paddingLeft: 2, 235 | paddingRight: 2, 236 | }} 237 | style={{ 238 | width: "100%", 239 | flexGrow: 1, 240 | display: "flex", 241 | flexDirection: "column", 242 | }} 243 | > 244 | setCompileOptions(e.target.value)} 247 | /> 248 | setExecArgs(e.target.value)} 251 | /> 252 |