├── .gitignore ├── CMakeLists.txt ├── CMakeSettings.json ├── README.md ├── build-wasm.sh ├── data ├── parser_benchmark.qak ├── parser_eol.qak ├── parser_expression.qak ├── parser_function.qak ├── parser_module.qak ├── parser_module_var.qak ├── parser_v_0_1.qak ├── playground.txt ├── tokens.qak └── tokens_error.qak ├── src ├── 3rdparty │ └── sokol_time.h ├── apps │ ├── qak.cpp │ ├── test.h │ ├── test_c_api.c │ ├── test_map.cpp │ ├── test_memory.cpp │ ├── test_parser.cpp │ └── test_tokenizer.cpp ├── array.h ├── error.cpp ├── error.h ├── io.cpp ├── io.h ├── map.h ├── memory.h ├── parser.cpp ├── parser.h ├── qak.cpp ├── qak.h ├── source.h ├── tokenizer.cpp ├── tokenizer.h └── types.h └── wasm ├── index.html ├── qak_post.js └── qak_pre.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | cmake-build-debug 3 | cmake-build-release 4 | .DS_Store 5 | .vs 6 | out 7 | qak.wasm 8 | qak.js 9 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(qak) 3 | 4 | if (MSVC) 5 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W3 -D_CRT_SECURE_NO_WARNINGS") 6 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W3 -D_CRT_SECURE_NO_WARNINGS") 7 | 8 | if(CMAKE_BUILD_TYPE MATCHES Release) 9 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") 10 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") 11 | endif() 12 | 13 | if (CMAKE_BUILD_TYPE MATCHES RelWithDebInfo) 14 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /PROFILE") 15 | endif() 16 | 17 | message("MSVC CXX Flags: ${CMAKE_CXX_FLAGS}") 18 | message("MSVC CXX Flags Release: ${CMAKE_CXX_FLAGS_RELEASE}") 19 | else () 20 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -pedantic -std=c99") 21 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -std=c++11") 22 | 23 | if(CMAKE_BUILD_TYPE MATCHES Release) 24 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -fno-exceptions -fno-rtti") 25 | endif() 26 | message("Clang CXX Flags: ${CMAKE_CXX_FLAGS}") 27 | message("Clang CXX Flags Release: ${CMAKE_CXX_FLAGS_RELEASE}") 28 | endif () 29 | 30 | set(CMAKE_CXX_STANDARD 11) 31 | 32 | include_directories(src) 33 | file(GLOB INCLUDES "src/*.h") 34 | file(GLOB SOURCES "src/*.cpp") 35 | add_library(qak-lib ${INCLUDE} ${SOURCES}) 36 | set_target_properties(qak-lib PROPERTIES OUTPUT_NAME "qak") 37 | 38 | add_executable(qak ${INCLUDES} "src/apps/qak.cpp") 39 | target_link_libraries(qak LINK_PUBLIC qak-lib) 40 | 41 | include_directories(src/apps) 42 | add_executable(test_memory ${INCLUDES} "src/apps/test_memory.cpp") 43 | target_link_libraries(test_memory LINK_PUBLIC qak-lib) 44 | 45 | include_directories(src/apps) 46 | add_executable(test_map ${INCLUDES} "src/apps/test_map.cpp") 47 | target_link_libraries(test_map LINK_PUBLIC qak-lib) 48 | 49 | include_directories(src/apps) 50 | add_executable(test_tokenizer ${INCLUDES} "src/apps/test_tokenizer.cpp") 51 | target_link_libraries(test_tokenizer LINK_PUBLIC qak-lib) 52 | 53 | include_directories(src/apps) 54 | add_executable(test_parser ${INCLUDES} "src/apps/test_parser.cpp") 55 | target_link_libraries(test_parser LINK_PUBLIC qak-lib) 56 | 57 | include_directories(src/apps) 58 | add_executable(test_c_api ${INCLUDES} "src/apps/test_c_api.c") 59 | target_link_libraries(test_c_api LINK_PUBLIC qak-lib) -------------------------------------------------------------------------------- /CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com//fwlink//?linkid=834763 for more information about this file. 3 | "configurations": [ 4 | { 5 | "name": "x64-Debug", 6 | "generator": "Ninja", 7 | "configurationType": "Debug", 8 | "inheritEnvironments": [ "msvc_x64_x64" ], 9 | "buildRoot": "${projectDir}\\out\\build\\${name}", 10 | "installRoot": "${projectDir}\\out\\install\\${name}", 11 | "cmakeCommandArgs": "", 12 | "buildCommandArgs": "", 13 | "ctestCommandArgs": "" 14 | }, 15 | { 16 | "name": "x64-Release", 17 | "generator": "Ninja", 18 | "configurationType": "Release", 19 | "inheritEnvironments": [ "msvc_x64_x64" ], 20 | "buildRoot": "${projectDir}\\out\\build\\${name}", 21 | "installRoot": "${projectDir}\\out\\install\\${name}", 22 | "cmakeCommandArgs": "", 23 | "buildCommandArgs": "", 24 | "ctestCommandArgs": "" 25 | }, 26 | { 27 | "name": "x64-Release-DbgInfo", 28 | "generator": "Ninja", 29 | "configurationType": "RelWithDebInfo", 30 | "inheritEnvironments": [ "msvc_x64_x64" ], 31 | "buildRoot": "${projectDir}\\out\\build\\${name}", 32 | "installRoot": "${projectDir}\\out\\install\\${name}", 33 | "cmakeCommandArgs": "", 34 | "buildCommandArgs": "", 35 | "ctestCommandArgs": "" 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Qak 2 | 3 | Qak is a typed scripting language written in C++, embedable in native and web apps (through WASM). -------------------------------------------------------------------------------- /build-wasm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SOURCES=`find src -name *.cpp -maxdepth 1` 3 | emcc -O0 -DWASM \ 4 | -s WASM=1 \ 5 | -s LLD_REPORT_UNDEFINED \ 6 | -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap", "allocateUTF8", "UTF8ArrayToString"]' \ 7 | --extern-pre-js wasm/qak_pre.js \ 8 | --extern-post-js wasm/qak_post.js \ 9 | -Isrc $SOURCES \ 10 | -o wasm/qak.js -------------------------------------------------------------------------------- /data/parser_benchmark.qak: -------------------------------------------------------------------------------- 1 | # variable declaration with initializers and simple type inference 2 | # Variables without initializer will be initialized to the type's 3 | # default value. 4 | 5 | module test 6 | while true 7 | if (shouldWeStop()) 8 | # break and continue (not pictured here) 9 | break 10 | end 11 | end 12 | 13 | var foo = 123 14 | var bar: boolean = true 15 | var zeroInitializer: int32 16 | 17 | # While statement, who needs for(-each)?! 18 | while(bar) 19 | # Variables are block scoped 20 | var uff = 3 21 | 22 | # If statement 23 | if (foo > 200) 24 | # Assignments 25 | bar = false 26 | else 27 | # arbitrary expressions (Which includes things 28 | # like function calls. 29 | print(foo) 30 | 31 | # The value generated by this expression is simply discarded. 32 | foo + 34 * zeroInitializer hhhh 33 | 34 | if (shouldWeStop()) 35 | # break and continue (not pictured here) 36 | break 37 | end 38 | end 39 | 40 | foo = foo + 1 41 | end 42 | 43 | # return statement 44 | return foo * 10 45 | 46 | # variable declaration with initializers and simple type inference 47 | # Variables without initializer will be initialized to the type's 48 | # default value. 49 | 50 | module test 51 | while true 52 | if (shouldWeStop()) 53 | # break and continue (not pictured here) 54 | break 55 | end 56 | end 57 | 58 | var foo = 123 59 | var bar: boolean = true 60 | var zeroInitializer: int32 61 | 62 | # While statement, who needs for(-each)?! 63 | while(bar) 64 | # Variables are block scoped 65 | var uff = 3 66 | 67 | # If statement 68 | if (foo > 200) 69 | # Assignments 70 | bar = false 71 | else 72 | # arbitrary expressions (Which includes things 73 | # like function calls. 74 | print(foo) 75 | 76 | # The value generated by this expression is simply discarded. 77 | foo + 34 * zeroInitializer hhhh 78 | 79 | if (shouldWeStop()) 80 | # break and continue (not pictured here) 81 | break 82 | end 83 | end 84 | 85 | foo = foo + 1 86 | end 87 | 88 | # return statement 89 | return foo * 10 90 | 91 | # variable declaration with initializers and simple type inference 92 | # Variables without initializer will be initialized to the type's 93 | # default value. 94 | 95 | module test 96 | while true 97 | if (shouldWeStop()) 98 | # break and continue (not pictured here) 99 | break 100 | end 101 | end 102 | 103 | var foo = 123 104 | var bar: boolean = true 105 | var zeroInitializer: int32 106 | 107 | # While statement, who needs for(-each)?! 108 | while(bar) 109 | # Variables are block scoped 110 | var uff = 3 111 | 112 | # If statement 113 | if (foo > 200) 114 | # Assignments 115 | bar = false 116 | else 117 | # arbitrary expressions (Which includes things 118 | # like function calls. 119 | print(foo) 120 | 121 | # The value generated by this expression is simply discarded. 122 | foo + 34 * zeroInitializer hhhh 123 | 124 | if (shouldWeStop()) 125 | # break and continue (not pictured here) 126 | break 127 | end 128 | end 129 | 130 | foo = foo + 1 131 | end 132 | 133 | # return statement 134 | return foo * 10 135 | 136 | # variable declaration with initializers and simple type inference 137 | # Variables without initializer will be initialized to the type's 138 | # default value. 139 | 140 | module test 141 | while true 142 | if (shouldWeStop()) 143 | # break and continue (not pictured here) 144 | break 145 | end 146 | end 147 | 148 | var foo = 123 149 | var bar: boolean = true 150 | var zeroInitializer: int32 151 | 152 | # While statement, who needs for(-each)?! 153 | while(bar) 154 | # Variables are block scoped 155 | var uff = 3 156 | 157 | # If statement 158 | if (foo > 200) 159 | # Assignments 160 | bar = false 161 | else 162 | # arbitrary expressions (Which includes things 163 | # like function calls. 164 | print(foo) 165 | 166 | # The value generated by this expression is simply discarded. 167 | foo + 34 * zeroInitializer hhhh 168 | 169 | if (shouldWeStop()) 170 | # break and continue (not pictured here) 171 | break 172 | end 173 | end 174 | 175 | foo = foo + 1 176 | end 177 | 178 | # return statement 179 | return foo * 10 180 | 181 | # variable declaration with initializers and simple type inference 182 | # Variables without initializer will be initialized to the type's 183 | # default value. 184 | 185 | module test 186 | while true 187 | if (shouldWeStop()) 188 | # break and continue (not pictured here) 189 | break 190 | end 191 | end 192 | 193 | var foo = 123 194 | var bar: boolean = true 195 | var zeroInitializer: int32 196 | 197 | # While statement, who needs for(-each)?! 198 | while(bar) 199 | # Variables are block scoped 200 | var uff = 3 201 | 202 | # If statement 203 | if (foo > 200) 204 | # Assignments 205 | bar = false 206 | else 207 | # arbitrary expressions (Which includes things 208 | # like function calls. 209 | print(foo) 210 | 211 | # The value generated by this expression is simply discarded. 212 | foo + 34 * zeroInitializer hhhh 213 | 214 | if (shouldWeStop()) 215 | # break and continue (not pictured here) 216 | break 217 | end 218 | end 219 | 220 | foo = foo + 1 221 | end 222 | 223 | # return statement 224 | return foo * 10 225 | 226 | # variable declaration with initializers and simple type inference 227 | # Variables without initializer will be initialized to the type's 228 | # default value. 229 | 230 | module test 231 | while true 232 | if (shouldWeStop()) 233 | # break and continue (not pictured here) 234 | break 235 | end 236 | end 237 | 238 | var foo = 123 239 | var bar: boolean = true 240 | var zeroInitializer: int32 241 | 242 | # While statement, who needs for(-each)?! 243 | while(bar) 244 | # Variables are block scoped 245 | var uff = 3 246 | 247 | # If statement 248 | if (foo > 200) 249 | # Assignments 250 | bar = false 251 | else 252 | # arbitrary expressions (Which includes things 253 | # like function calls. 254 | print(foo) 255 | 256 | # The value generated by this expression is simply discarded. 257 | foo + 34 * zeroInitializer hhhh 258 | 259 | if (shouldWeStop()) 260 | # break and continue (not pictured here) 261 | break 262 | end 263 | end 264 | 265 | foo = foo + 1 266 | end 267 | 268 | # return statement 269 | return foo * 10 270 | 271 | # variable declaration with initializers and simple type inference 272 | # Variables without initializer will be initialized to the type's 273 | # default value. 274 | 275 | module test 276 | while true 277 | if (shouldWeStop()) 278 | # break and continue (not pictured here) 279 | break 280 | end 281 | end 282 | 283 | var foo = 123 284 | var bar: boolean = true 285 | var zeroInitializer: int32 286 | 287 | # While statement, who needs for(-each)?! 288 | while(bar) 289 | # Variables are block scoped 290 | var uff = 3 291 | 292 | # If statement 293 | if (foo > 200) 294 | # Assignments 295 | bar = false 296 | else 297 | # arbitrary expressions (Which includes things 298 | # like function calls. 299 | print(foo) 300 | 301 | # The value generated by this expression is simply discarded. 302 | foo + 34 * zeroInitializer hhhh 303 | 304 | if (shouldWeStop()) 305 | # break and continue (not pictured here) 306 | break 307 | end 308 | end 309 | 310 | foo = foo + 1 311 | end 312 | 313 | # return statement 314 | return foo * 10 315 | 316 | # variable declaration with initializers and simple type inference 317 | # Variables without initializer will be initialized to the type's 318 | # default value. 319 | 320 | module test 321 | while true 322 | if (shouldWeStop()) 323 | # break and continue (not pictured here) 324 | break 325 | end 326 | end 327 | 328 | var foo = 123 329 | var bar: boolean = true 330 | var zeroInitializer: int32 331 | 332 | # While statement, who needs for(-each)?! 333 | while(bar) 334 | # Variables are block scoped 335 | var uff = 3 336 | 337 | # If statement 338 | if (foo > 200) 339 | # Assignments 340 | bar = false 341 | else 342 | # arbitrary expressions (Which includes things 343 | # like function calls. 344 | print(foo) 345 | 346 | # The value generated by this expression is simply discarded. 347 | foo + 34 * zeroInitializer hhhh 348 | 349 | if (shouldWeStop()) 350 | # break and continue (not pictured here) 351 | break 352 | end 353 | end 354 | 355 | foo = foo + 1 356 | end 357 | 358 | # return statement 359 | return foo * 10 360 | 361 | # variable declaration with initializers and simple type inference 362 | # Variables without initializer will be initialized to the type's 363 | # default value. 364 | 365 | module test 366 | while true 367 | if (shouldWeStop()) 368 | # break and continue (not pictured here) 369 | break 370 | end 371 | end 372 | 373 | var foo = 123 374 | var bar: boolean = true 375 | var zeroInitializer: int32 376 | 377 | # While statement, who needs for(-each)?! 378 | while(bar) 379 | # Variables are block scoped 380 | var uff = 3 381 | 382 | # If statement 383 | if (foo > 200) 384 | # Assignments 385 | bar = false 386 | else 387 | # arbitrary expressions (Which includes things 388 | # like function calls. 389 | print(foo) 390 | 391 | # The value generated by this expression is simply discarded. 392 | foo + 34 * zeroInitializer hhhh 393 | 394 | if (shouldWeStop()) 395 | # break and continue (not pictured here) 396 | break 397 | end 398 | end 399 | 400 | foo = foo + 1 401 | end 402 | 403 | # return statement 404 | return foo * 10 405 | 406 | # variable declaration with initializers and simple type inference 407 | # Variables without initializer will be initialized to the type's 408 | # default value. 409 | 410 | module test 411 | while true 412 | if (shouldWeStop()) 413 | # break and continue (not pictured here) 414 | break 415 | end 416 | end 417 | 418 | var foo = 123 419 | var bar: boolean = true 420 | var zeroInitializer: int32 421 | 422 | # While statement, who needs for(-each)?! 423 | while(bar) 424 | # Variables are block scoped 425 | var uff = 3 426 | 427 | # If statement 428 | if (foo > 200) 429 | # Assignments 430 | bar = false 431 | else 432 | # arbitrary expressions (Which includes things 433 | # like function calls. 434 | print(foo) 435 | 436 | # The value generated by this expression is simply discarded. 437 | foo + 34 * zeroInitializer hhhh 438 | 439 | if (shouldWeStop()) 440 | # break and continue (not pictured here) 441 | break 442 | end 443 | end 444 | 445 | foo = foo + 1 446 | end 447 | 448 | # return statement 449 | return foo * 10 450 | 451 | # variable declaration with initializers and simple type inference 452 | # Variables without initializer will be initialized to the type's 453 | # default value. 454 | 455 | module test 456 | while true 457 | if (shouldWeStop()) 458 | # break and continue (not pictured here) 459 | break 460 | end 461 | end 462 | 463 | var foo = 123 464 | var bar: boolean = true 465 | var zeroInitializer: int32 466 | 467 | # While statement, who needs for(-each)?! 468 | while(bar) 469 | # Variables are block scoped 470 | var uff = 3 471 | 472 | # If statement 473 | if (foo > 200) 474 | # Assignments 475 | bar = false 476 | else 477 | # arbitrary expressions (Which includes things 478 | # like function calls. 479 | print(foo) 480 | 481 | # The value generated by this expression is simply discarded. 482 | foo + 34 * zeroInitializer hhhh 483 | 484 | if (shouldWeStop()) 485 | # break and continue (not pictured here) 486 | break 487 | end 488 | end 489 | 490 | foo = foo + 1 491 | end 492 | 493 | # return statement 494 | return foo * 10 495 | 496 | # variable declaration with initializers and simple type inference 497 | # Variables without initializer will be initialized to the type's 498 | # default value. 499 | 500 | module test 501 | while true 502 | if (shouldWeStop()) 503 | # break and continue (not pictured here) 504 | break 505 | end 506 | end 507 | 508 | var foo = 123 509 | var bar: boolean = true 510 | var zeroInitializer: int32 511 | 512 | # While statement, who needs for(-each)?! 513 | while(bar) 514 | # Variables are block scoped 515 | var uff = 3 516 | 517 | # If statement 518 | if (foo > 200) 519 | # Assignments 520 | bar = false 521 | else 522 | # arbitrary expressions (Which includes things 523 | # like function calls. 524 | print(foo) 525 | 526 | # The value generated by this expression is simply discarded. 527 | foo + 34 * zeroInitializer hhhh 528 | 529 | if (shouldWeStop()) 530 | # break and continue (not pictured here) 531 | break 532 | end 533 | end 534 | 535 | foo = foo + 1 536 | end 537 | 538 | # return statement 539 | return foo * 10 540 | 541 | # variable declaration with initializers and simple type inference 542 | # Variables without initializer will be initialized to the type's 543 | # default value. 544 | 545 | module test 546 | while true 547 | if (shouldWeStop()) 548 | # break and continue (not pictured here) 549 | break 550 | end 551 | end 552 | 553 | var foo = 123 554 | var bar: boolean = true 555 | var zeroInitializer: int32 556 | 557 | # While statement, who needs for(-each)?! 558 | while(bar) 559 | # Variables are block scoped 560 | var uff = 3 561 | 562 | # If statement 563 | if (foo > 200) 564 | # Assignments 565 | bar = false 566 | else 567 | # arbitrary expressions (Which includes things 568 | # like function calls. 569 | print(foo) 570 | 571 | # The value generated by this expression is simply discarded. 572 | foo + 34 * zeroInitializer hhhh 573 | 574 | if (shouldWeStop()) 575 | # break and continue (not pictured here) 576 | break 577 | end 578 | end 579 | 580 | foo = foo + 1 581 | end 582 | 583 | # return statement 584 | return foo * 10 585 | 586 | # variable declaration with initializers and simple type inference 587 | # Variables without initializer will be initialized to the type's 588 | # default value. 589 | 590 | module test 591 | while true 592 | if (shouldWeStop()) 593 | # break and continue (not pictured here) 594 | break 595 | end 596 | end 597 | 598 | var foo = 123 599 | var bar: boolean = true 600 | var zeroInitializer: int32 601 | 602 | # While statement, who needs for(-each)?! 603 | while(bar) 604 | # Variables are block scoped 605 | var uff = 3 606 | 607 | # If statement 608 | if (foo > 200) 609 | # Assignments 610 | bar = false 611 | else 612 | # arbitrary expressions (Which includes things 613 | # like function calls. 614 | print(foo) 615 | 616 | # The value generated by this expression is simply discarded. 617 | foo + 34 * zeroInitializer hhhh 618 | 619 | if (shouldWeStop()) 620 | # break and continue (not pictured here) 621 | break 622 | end 623 | end 624 | 625 | foo = foo + 1 626 | end 627 | 628 | # return statement 629 | return foo * 10 630 | 631 | # variable declaration with initializers and simple type inference 632 | # Variables without initializer will be initialized to the type's 633 | # default value. 634 | 635 | module test 636 | while true 637 | if (shouldWeStop()) 638 | # break and continue (not pictured here) 639 | break 640 | end 641 | end 642 | 643 | var foo = 123 644 | var bar: boolean = true 645 | var zeroInitializer: int32 646 | 647 | # While statement, who needs for(-each)?! 648 | while(bar) 649 | # Variables are block scoped 650 | var uff = 3 651 | 652 | # If statement 653 | if (foo > 200) 654 | # Assignments 655 | bar = false 656 | else 657 | # arbitrary expressions (Which includes things 658 | # like function calls. 659 | print(foo) 660 | 661 | # The value generated by this expression is simply discarded. 662 | foo + 34 * zeroInitializer hhhh 663 | 664 | if (shouldWeStop()) 665 | # break and continue (not pictured here) 666 | break 667 | end 668 | end 669 | 670 | foo = foo + 1 671 | end 672 | 673 | # return statement 674 | return foo * 10 675 | 676 | # variable declaration with initializers and simple type inference 677 | # Variables without initializer will be initialized to the type's 678 | # default value. 679 | 680 | module test 681 | while true 682 | if (shouldWeStop()) 683 | # break and continue (not pictured here) 684 | break 685 | end 686 | end 687 | 688 | var foo = 123 689 | var bar: boolean = true 690 | var zeroInitializer: int32 691 | 692 | # While statement, who needs for(-each)?! 693 | while(bar) 694 | # Variables are block scoped 695 | var uff = 3 696 | 697 | # If statement 698 | if (foo > 200) 699 | # Assignments 700 | bar = false 701 | else 702 | # arbitrary expressions (Which includes things 703 | # like function calls. 704 | print(foo) 705 | 706 | # The value generated by this expression is simply discarded. 707 | foo + 34 * zeroInitializer hhhh 708 | 709 | if (shouldWeStop()) 710 | # break and continue (not pictured here) 711 | break 712 | end 713 | end 714 | 715 | foo = foo + 1 716 | end 717 | 718 | # return statement 719 | return foo * 10 720 | 721 | # variable declaration with initializers and simple type inference 722 | # Variables without initializer will be initialized to the type's 723 | # default value. 724 | 725 | module test 726 | while true 727 | if (shouldWeStop()) 728 | # break and continue (not pictured here) 729 | break 730 | end 731 | end 732 | 733 | var foo = 123 734 | var bar: boolean = true 735 | var zeroInitializer: int32 736 | 737 | # While statement, who needs for(-each)?! 738 | while(bar) 739 | # Variables are block scoped 740 | var uff = 3 741 | 742 | # If statement 743 | if (foo > 200) 744 | # Assignments 745 | bar = false 746 | else 747 | # arbitrary expressions (Which includes things 748 | # like function calls. 749 | print(foo) 750 | 751 | # The value generated by this expression is simply discarded. 752 | foo + 34 * zeroInitializer hhhh 753 | 754 | if (shouldWeStop()) 755 | # break and continue (not pictured here) 756 | break 757 | end 758 | end 759 | 760 | foo = foo + 1 761 | end 762 | 763 | # return statement 764 | return foo * 10 765 | 766 | # variable declaration with initializers and simple type inference 767 | # Variables without initializer will be initialized to the type's 768 | # default value. 769 | 770 | module test 771 | while true 772 | if (shouldWeStop()) 773 | # break and continue (not pictured here) 774 | break 775 | end 776 | end 777 | 778 | var foo = 123 779 | var bar: boolean = true 780 | var zeroInitializer: int32 781 | 782 | # While statement, who needs for(-each)?! 783 | while(bar) 784 | # Variables are block scoped 785 | var uff = 3 786 | 787 | # If statement 788 | if (foo > 200) 789 | # Assignments 790 | bar = false 791 | else 792 | # arbitrary expressions (Which includes things 793 | # like function calls. 794 | print(foo) 795 | 796 | # The value generated by this expression is simply discarded. 797 | foo + 34 * zeroInitializer hhhh 798 | 799 | if (shouldWeStop()) 800 | # break and continue (not pictured here) 801 | break 802 | end 803 | end 804 | 805 | foo = foo + 1 806 | end 807 | 808 | # return statement 809 | return foo * 10 810 | 811 | # variable declaration with initializers and simple type inference 812 | # Variables without initializer will be initialized to the type's 813 | # default value. 814 | 815 | module test 816 | while true 817 | if (shouldWeStop()) 818 | # break and continue (not pictured here) 819 | break 820 | end 821 | end 822 | 823 | var foo = 123 824 | var bar: boolean = true 825 | var zeroInitializer: int32 826 | 827 | # While statement, who needs for(-each)?! 828 | while(bar) 829 | # Variables are block scoped 830 | var uff = 3 831 | 832 | # If statement 833 | if (foo > 200) 834 | # Assignments 835 | bar = false 836 | else 837 | # arbitrary expressions (Which includes things 838 | # like function calls. 839 | print(foo) 840 | 841 | # The value generated by this expression is simply discarded. 842 | foo + 34 * zeroInitializer hhhh 843 | 844 | if (shouldWeStop()) 845 | # break and continue (not pictured here) 846 | break 847 | end 848 | end 849 | 850 | foo = foo + 1 851 | end 852 | 853 | # return statement 854 | return foo * 10 855 | 856 | # variable declaration with initializers and simple type inference 857 | # Variables without initializer will be initialized to the type's 858 | # default value. 859 | 860 | module test 861 | while true 862 | if (shouldWeStop()) 863 | # break and continue (not pictured here) 864 | break 865 | end 866 | end 867 | 868 | var foo = 123 869 | var bar: boolean = true 870 | var zeroInitializer: int32 871 | 872 | # While statement, who needs for(-each)?! 873 | while(bar) 874 | # Variables are block scoped 875 | var uff = 3 876 | 877 | # If statement 878 | if (foo > 200) 879 | # Assignments 880 | bar = false 881 | else 882 | # arbitrary expressions (Which includes things 883 | # like function calls. 884 | print(foo) 885 | 886 | # The value generated by this expression is simply discarded. 887 | foo + 34 * zeroInitializer hhhh 888 | 889 | if (shouldWeStop()) 890 | # break and continue (not pictured here) 891 | break 892 | end 893 | end 894 | 895 | foo = foo + 1 896 | end 897 | 898 | # return statement 899 | return foo * 10 900 | 901 | # variable declaration with initializers and simple type inference 902 | # Variables without initializer will be initialized to the type's 903 | # default value. 904 | 905 | module test 906 | while true 907 | if (shouldWeStop()) 908 | # break and continue (not pictured here) 909 | break 910 | end 911 | end 912 | 913 | var foo = 123 914 | var bar: boolean = true 915 | var zeroInitializer: int32 916 | 917 | # While statement, who needs for(-each)?! 918 | while(bar) 919 | # Variables are block scoped 920 | var uff = 3 921 | 922 | # If statement 923 | if (foo > 200) 924 | # Assignments 925 | bar = false 926 | else 927 | # arbitrary expressions (Which includes things 928 | # like function calls. 929 | print(foo) 930 | 931 | # The value generated by this expression is simply discarded. 932 | foo + 34 * zeroInitializer hhhh 933 | 934 | if (shouldWeStop()) 935 | # break and continue (not pictured here) 936 | break 937 | end 938 | end 939 | 940 | foo = foo + 1 941 | end 942 | 943 | # return statement 944 | return foo * 10 945 | 946 | # variable declaration with initializers and simple type inference 947 | # Variables without initializer will be initialized to the type's 948 | # default value. 949 | 950 | module test 951 | while true 952 | if (shouldWeStop()) 953 | # break and continue (not pictured here) 954 | break 955 | end 956 | end 957 | 958 | var foo = 123 959 | var bar: boolean = true 960 | var zeroInitializer: int32 961 | 962 | # While statement, who needs for(-each)?! 963 | while(bar) 964 | # Variables are block scoped 965 | var uff = 3 966 | 967 | # If statement 968 | if (foo > 200) 969 | # Assignments 970 | bar = false 971 | else 972 | # arbitrary expressions (Which includes things 973 | # like function calls. 974 | print(foo) 975 | 976 | # The value generated by this expression is simply discarded. 977 | foo + 34 * zeroInitializer hhhh 978 | 979 | if (shouldWeStop()) 980 | # break and continue (not pictured here) 981 | break 982 | end 983 | end 984 | 985 | foo = foo + 1 986 | end 987 | 988 | # return statement 989 | return foo * 10 990 | 991 | # variable declaration with initializers and simple type inference 992 | # Variables without initializer will be initialized to the type's 993 | # default value. 994 | 995 | module test 996 | while true 997 | if (shouldWeStop()) 998 | # break and continue (not pictured here) 999 | break 1000 | end 1001 | end 1002 | 1003 | var foo = 123 1004 | var bar: boolean = true 1005 | var zeroInitializer: int32 1006 | 1007 | # While statement, who needs for(-each)?! 1008 | while(bar) 1009 | # Variables are block scoped 1010 | var uff = 3 1011 | 1012 | # If statement 1013 | if (foo > 200) 1014 | # Assignments 1015 | bar = false 1016 | else 1017 | # arbitrary expressions (Which includes things 1018 | # like function calls. 1019 | print(foo) 1020 | 1021 | # The value generated by this expression is simply discarded. 1022 | foo + 34 * zeroInitializer hhhh 1023 | 1024 | if (shouldWeStop()) 1025 | # break and continue (not pictured here) 1026 | break 1027 | end 1028 | end 1029 | 1030 | foo = foo + 1 1031 | end 1032 | 1033 | # return statement 1034 | return foo * 10 1035 | 1036 | # variable declaration with initializers and simple type inference 1037 | # Variables without initializer will be initialized to the type's 1038 | # default value. 1039 | 1040 | module test 1041 | while true 1042 | if (shouldWeStop()) 1043 | # break and continue (not pictured here) 1044 | break 1045 | end 1046 | end 1047 | 1048 | var foo = 123 1049 | var bar: boolean = true 1050 | var zeroInitializer: int32 1051 | 1052 | # While statement, who needs for(-each)?! 1053 | while(bar) 1054 | # Variables are block scoped 1055 | var uff = 3 1056 | 1057 | # If statement 1058 | if (foo > 200) 1059 | # Assignments 1060 | bar = false 1061 | else 1062 | # arbitrary expressions (Which includes things 1063 | # like function calls. 1064 | print(foo) 1065 | 1066 | # The value generated by this expression is simply discarded. 1067 | foo + 34 * zeroInitializer hhhh 1068 | 1069 | if (shouldWeStop()) 1070 | # break and continue (not pictured here) 1071 | break 1072 | end 1073 | end 1074 | 1075 | foo = foo + 1 1076 | end 1077 | 1078 | # return statement 1079 | return foo * 10 1080 | 1081 | # variable declaration with initializers and simple type inference 1082 | # Variables without initializer will be initialized to the type's 1083 | # default value. 1084 | 1085 | module test 1086 | while true 1087 | if (shouldWeStop()) 1088 | # break and continue (not pictured here) 1089 | break 1090 | end 1091 | end 1092 | 1093 | var foo = 123 1094 | var bar: boolean = true 1095 | var zeroInitializer: int32 1096 | 1097 | # While statement, who needs for(-each)?! 1098 | while(bar) 1099 | # Variables are block scoped 1100 | var uff = 3 1101 | 1102 | # If statement 1103 | if (foo > 200) 1104 | # Assignments 1105 | bar = false 1106 | else 1107 | # arbitrary expressions (Which includes things 1108 | # like function calls. 1109 | print(foo) 1110 | 1111 | # The value generated by this expression is simply discarded. 1112 | foo + 34 * zeroInitializer hhhh 1113 | 1114 | if (shouldWeStop()) 1115 | # break and continue (not pictured here) 1116 | break 1117 | end 1118 | end 1119 | 1120 | foo = foo + 1 1121 | end 1122 | 1123 | # return statement 1124 | return foo * 10 1125 | 1126 | # variable declaration with initializers and simple type inference 1127 | # Variables without initializer will be initialized to the type's 1128 | # default value. 1129 | 1130 | module test 1131 | while true 1132 | if (shouldWeStop()) 1133 | # break and continue (not pictured here) 1134 | break 1135 | end 1136 | end 1137 | 1138 | var foo = 123 1139 | var bar: boolean = true 1140 | var zeroInitializer: int32 1141 | 1142 | # While statement, who needs for(-each)?! 1143 | while(bar) 1144 | # Variables are block scoped 1145 | var uff = 3 1146 | 1147 | # If statement 1148 | if (foo > 200) 1149 | # Assignments 1150 | bar = false 1151 | else 1152 | # arbitrary expressions (Which includes things 1153 | # like function calls. 1154 | print(foo) 1155 | 1156 | # The value generated by this expression is simply discarded. 1157 | foo + 34 * zeroInitializer hhhh 1158 | 1159 | if (shouldWeStop()) 1160 | # break and continue (not pictured here) 1161 | break 1162 | end 1163 | end 1164 | 1165 | foo = foo + 1 1166 | end 1167 | 1168 | # return statement 1169 | return foo * 10 -------------------------------------------------------------------------------- /data/parser_eol.qak: -------------------------------------------------------------------------------- 1 | module test 2 | 3 | fun foo(a: int32, b: int32) 4 | return a + b 5 | end 6 | 7 | foo(1, 2 -------------------------------------------------------------------------------- /data/parser_expression.qak: -------------------------------------------------------------------------------- 1 | module test 2 | 3 | 1 + 2 * 4 / 5 -------------------------------------------------------------------------------- /data/parser_function.qak: -------------------------------------------------------------------------------- 1 | module test 2 | 3 | fun empty() 4 | end 5 | 6 | fun emptyReturn(): i32 7 | end 8 | 9 | fun oneArg(a: i32) 10 | end 11 | 12 | fun argsAndReturn(a: i32, b: i32): i32 13 | end 14 | 15 | fun foo(a: i32, b: i32): i32 16 | var result = 1 + 2 * 3 / 4 17 | end -------------------------------------------------------------------------------- /data/parser_module.qak: -------------------------------------------------------------------------------- 1 | module myModule -------------------------------------------------------------------------------- /data/parser_module_var.qak: -------------------------------------------------------------------------------- 1 | module test 2 | 3 | var foo: i32 4 | var bar = true ? (234 + 3) * 5 : 123 -------------------------------------------------------------------------------- /data/parser_v_0_1.qak: -------------------------------------------------------------------------------- 1 | # variable declaration with initializers and simple type inference 2 | # Variables without initializer will be initialized to the type's 3 | # default value. 4 | 5 | module test 6 | while true 7 | if (shouldWeStop()) 8 | # break and continue (not pictured here) 9 | break 10 | end 11 | end 12 | 13 | var foo = 123 14 | var bar: boolean = true 15 | var zeroInitializer: int32 16 | 17 | # While statement, who needs for(-each)?! 18 | while(bar) 19 | # Variables are block scoped 20 | var uff = 3 21 | 22 | # If statement 23 | if (foo > 200) 24 | # Assignments 25 | bar = false 26 | else 27 | # arbitrary expressions (Which includes things 28 | # like function calls. 29 | print(foo) 30 | 31 | # The value generated by this expression is simply discarded. 32 | foo + 34 * zeroInitializer hhhh 33 | 34 | if (shouldWeStop()) 35 | # break and continue (not pictured here) 36 | break 37 | end 38 | end 39 | 40 | foo = foo + 1 41 | end 42 | 43 | # return statement 44 | return foo * 10 -------------------------------------------------------------------------------- /data/playground.txt: -------------------------------------------------------------------------------- 1 | // Possible syntax and semantic playground 2 | 3 | module math 4 | 5 | type Point 6 | x: f32 7 | y: f32 8 | end 9 | 10 | fun add(p1: Point, p2: Point): Point 11 | return Point(p1.x + p2.x, p1.y + p2.y) 12 | end 13 | 14 | var p1 = new Point(0, 10) // ref Point 15 | var p2 = Point(0, 10) // Point 16 | var p3 = add(p1, p2) // Point 17 | var p4 = new Point(add(p1, p2)) // ref Point 18 | var p4 = Point(0, 0) 19 | p3 = p1 // ref assigned to val -> copy 20 | p1 = p2 // val assigned to ref -> copy 21 | p4 = p1 // ref assigned to ref -> 22 | 23 | type String 24 | _bytes: i8[] 25 | 26 | fun String(other: String) 27 | bytes = other.bytes 28 | end 29 | 30 | fun String(bytes: i8[]) 31 | self.bytes = std.copy(bytes) 32 | end 33 | 34 | fun numBytes(): i32 35 | return bytes.length 36 | end 37 | 38 | fun bytes(): i8[] 39 | return bytes.copy() 40 | end 41 | end 42 | 43 | type StringIterator 44 | _str: String 45 | end 46 | 47 | type Callbacks 48 | start: (msg: String) 49 | update: (msg: String) 50 | end 51 | 52 | type List 53 | items: T[] // arrays are always a ref 54 | size: i32 55 | capacity: i32 56 | end 57 | 58 | type Node 59 | next: ref Node 60 | value: T 61 | end 62 | 63 | type AstNode 64 | If | While | Return 65 | end 66 | 67 | var node = new If() 68 | 69 | fun print(node: AstNode) // can take val and ref of AstNode 70 | match(node) 71 | If: 72 | While: 73 | Return: 74 | end 75 | 76 | fun print(node: ref AstNode) // can take ref of AstNode, but not val of AstNode 77 | 78 | end 79 | 80 | // syntactic sugar for 81 | type AstNodeVariant 82 | type: ref Type 83 | isRef: boolean 84 | value: ... a value or ref ... 85 | end 86 | 87 | 88 | -------------------------------------------------------------------------------- /data/tokens.qak: -------------------------------------------------------------------------------- 1 | <= 2 | >= 3 | == 4 | != 5 | < 6 | > 7 | = 8 | . 9 | , 10 | ; 11 | : 12 | + 13 | - 14 | * 15 | / 16 | % 17 | ( 18 | ) 19 | [ 20 | ] 21 | { 22 | } 23 | & 24 | | 25 | ^ 26 | ! 27 | ? 28 | 한자🥴 29 | 123 30 | 123b 31 | 123s 32 | 123l 33 | 123.2 34 | 123.3f 35 | 123.4d 36 | 'c' 37 | '\n' 38 | true 39 | false 40 | null 41 | _Some987Identifier 42 | "Hello world. 한자🥴" 43 | # comment -------------------------------------------------------------------------------- /data/tokens_error.qak: -------------------------------------------------------------------------------- 1 | 한자🥴 2 | 123 123b 123s 123l 123.2 123.3f 123.4d 3 | 'c' "sdfaser ser qwer 4 | '\n' 5 | -------------------------------------------------------------------------------- /src/3rdparty/sokol_time.h: -------------------------------------------------------------------------------- 1 | #ifndef SOKOL_TIME_INCLUDED 2 | /* 3 | sokol_time.h -- simple cross-platform time measurement 4 | 5 | Project URL: https://github.com/floooh/sokol 6 | 7 | Do this: 8 | #define SOKOL_IMPL 9 | before you include this file in *one* C or C++ file to create the 10 | implementation. 11 | 12 | Optionally provide the following defines with your own implementations: 13 | SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) 14 | SOKOL_API_DECL - public function declaration prefix (default: extern) 15 | SOKOL_API_IMPL - public function implementation prefix (default: -) 16 | 17 | If sokol_time.h is compiled as a DLL, define the following before 18 | including the declaration or implementation: 19 | 20 | SOKOL_DLL 21 | 22 | On Windows, SOKOL_DLL will define SOKOL_API_DECL as __declspec(dllexport) 23 | or __declspec(dllimport) as needed. 24 | 25 | void stm_setup(); 26 | Call once before any other functions to initialize sokol_time 27 | (this calls for instance QueryPerformanceFrequency on Windows) 28 | 29 | uint64_t stm_now(); 30 | Get current point in time in unspecified 'ticks'. The value that 31 | is returned has no relation to the 'wall-clock' time and is 32 | not in a specific time unit, it is only useful to compute 33 | time differences. 34 | 35 | uint64_t stm_diff(uint64_t new, uint64_t old); 36 | Computes the time difference between new and old. This will always 37 | return a positive, non-zero value. 38 | 39 | uint64_t stm_since(uint64_t start); 40 | Takes the current time, and returns the elapsed time since start 41 | (this is a shortcut for "stm_diff(stm_now(), start)") 42 | 43 | uint64_t stm_laptime(uint64_t* last_time); 44 | This is useful for measuring frame time and other recurring 45 | events. It takes the current time, returns the time difference 46 | to the value in last_time, and stores the current time in 47 | last_time for the next call. If the value in last_time is 0, 48 | the return value will be zero (this usually happens on the 49 | very first call). 50 | 51 | uint64_t stm_round_to_common_refresh_rate(uint64_t duration) 52 | This oddly named function takes a measured frame time and 53 | returns the closest "nearby" common display refresh rate frame duration 54 | in ticks. If the input duration isn't close to any common display 55 | refresh rate, the input duration will be returned unchanged as a fallback. 56 | The main purpose of this function is to remove jitter/inaccuracies from 57 | measured frame times, and instead use the display refresh rate as 58 | frame duration. 59 | 60 | Use the following functions to convert a duration in ticks into 61 | useful time units: 62 | 63 | double stm_sec(uint64_t ticks); 64 | double stm_ms(uint64_t ticks); 65 | double stm_us(uint64_t ticks); 66 | double stm_ns(uint64_t ticks); 67 | Converts a tick value into seconds, milliseconds, microseconds 68 | or nanoseconds. Note that not all platforms will have nanosecond 69 | or even microsecond precision. 70 | 71 | Uses the following time measurement functions under the hood: 72 | 73 | Windows: QueryPerformanceFrequency() / QueryPerformanceCounter() 74 | MacOS/iOS: mach_absolute_time() 75 | emscripten: performance.now() 76 | Linux+others: clock_gettime(CLOCK_MONOTONIC) 77 | 78 | zlib/libpng license 79 | 80 | Copyright (c) 2018 Andre Weissflog 81 | 82 | This software is provided 'as-is', without any express or implied warranty. 83 | In no event will the authors be held liable for any damages arising from the 84 | use of this software. 85 | 86 | Permission is granted to anyone to use this software for any purpose, 87 | including commercial applications, and to alter it and redistribute it 88 | freely, subject to the following restrictions: 89 | 90 | 1. The origin of this software must not be misrepresented; you must not 91 | claim that you wrote the original software. If you use this software in a 92 | product, an acknowledgment in the product documentation would be 93 | appreciated but is not required. 94 | 95 | 2. Altered source versions must be plainly marked as such, and must not 96 | be misrepresented as being the original software. 97 | 98 | 3. This notice may not be removed or altered from any source 99 | distribution. 100 | */ 101 | #define SOKOL_TIME_INCLUDED (1) 102 | #include 103 | 104 | #ifndef SOKOL_API_DECL 105 | #if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_IMPL) 106 | #define SOKOL_API_DECL __declspec(dllexport) 107 | #elif defined(_WIN32) && defined(SOKOL_DLL) 108 | #define SOKOL_API_DECL __declspec(dllimport) 109 | #else 110 | #define SOKOL_API_DECL extern 111 | #endif 112 | #endif 113 | 114 | #ifdef __cplusplus 115 | extern "C" { 116 | #endif 117 | 118 | SOKOL_API_DECL void stm_setup(void); 119 | SOKOL_API_DECL uint64_t stm_now(void); 120 | SOKOL_API_DECL uint64_t stm_diff(uint64_t new_ticks, uint64_t old_ticks); 121 | SOKOL_API_DECL uint64_t stm_since(uint64_t start_ticks); 122 | SOKOL_API_DECL uint64_t stm_laptime(uint64_t* last_time); 123 | SOKOL_API_DECL uint64_t stm_round_to_common_refresh_rate(uint64_t frame_ticks); 124 | SOKOL_API_DECL double stm_sec(uint64_t ticks); 125 | SOKOL_API_DECL double stm_ms(uint64_t ticks); 126 | SOKOL_API_DECL double stm_us(uint64_t ticks); 127 | SOKOL_API_DECL double stm_ns(uint64_t ticks); 128 | 129 | #ifdef __cplusplus 130 | } /* extern "C" */ 131 | #endif 132 | #endif // SOKOL_TIME_INCLUDED 133 | 134 | /*-- IMPLEMENTATION ----------------------------------------------------------*/ 135 | #ifdef SOKOL_IMPL 136 | #define SOKOL_TIME_IMPL_INCLUDED (1) 137 | #include /* memset */ 138 | 139 | #ifndef SOKOL_API_IMPL 140 | #define SOKOL_API_IMPL 141 | #endif 142 | #ifndef SOKOL_ASSERT 143 | #include 144 | #define SOKOL_ASSERT(c) assert(c) 145 | #endif 146 | #ifndef _SOKOL_PRIVATE 147 | #if defined(__GNUC__) || defined(__clang__) 148 | #define _SOKOL_PRIVATE __attribute__((unused)) static 149 | #else 150 | #define _SOKOL_PRIVATE static 151 | #endif 152 | #endif 153 | 154 | #if defined(_WIN32) 155 | #ifndef WIN32_LEAN_AND_MEAN 156 | #define WIN32_LEAN_AND_MEAN 157 | #endif 158 | #include 159 | typedef struct { 160 | uint32_t initialized; 161 | LARGE_INTEGER freq; 162 | LARGE_INTEGER start; 163 | } _stm_state_t; 164 | #elif defined(__APPLE__) && defined(__MACH__) 165 | #include 166 | typedef struct { 167 | uint32_t initialized; 168 | mach_timebase_info_data_t timebase; 169 | uint64_t start; 170 | } _stm_state_t; 171 | #elif defined(__EMSCRIPTEN__) 172 | #include 173 | typedef struct { 174 | uint32_t initialized; 175 | double start; 176 | } _stm_state_t; 177 | #else /* anything else, this will need more care for non-Linux platforms */ 178 | #ifdef ESP8266 179 | // On the ESP8266, clock_gettime ignores the first argument and CLOCK_MONOTONIC isn't defined 180 | #define CLOCK_MONOTONIC 0 181 | #endif 182 | #include 183 | typedef struct { 184 | uint32_t initialized; 185 | uint64_t start; 186 | } _stm_state_t; 187 | #endif 188 | static _stm_state_t _stm; 189 | 190 | /* prevent 64-bit overflow when computing relative timestamp 191 | see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3 192 | */ 193 | #if defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__)) 194 | _SOKOL_PRIVATE int64_t int64_muldiv(int64_t value, int64_t numer, int64_t denom) { 195 | int64_t q = value / denom; 196 | int64_t r = value % denom; 197 | return q * numer + r * numer / denom; 198 | } 199 | #endif 200 | 201 | #if defined(__EMSCRIPTEN__) 202 | EM_JS(double, stm_js_perfnow, (void), { 203 | return performance.now(); 204 | }); 205 | #endif 206 | 207 | SOKOL_API_IMPL void stm_setup(void) { 208 | memset(&_stm, 0, sizeof(_stm)); 209 | _stm.initialized = 0xABCDABCD; 210 | #if defined(_WIN32) 211 | QueryPerformanceFrequency(&_stm.freq); 212 | QueryPerformanceCounter(&_stm.start); 213 | #elif defined(__APPLE__) && defined(__MACH__) 214 | mach_timebase_info(&_stm.timebase); 215 | _stm.start = mach_absolute_time(); 216 | #elif defined(__EMSCRIPTEN__) 217 | _stm.start = stm_js_perfnow(); 218 | #else 219 | struct timespec ts; 220 | clock_gettime(CLOCK_MONOTONIC, &ts); 221 | _stm.start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec; 222 | #endif 223 | } 224 | 225 | SOKOL_API_IMPL uint64_t stm_now(void) { 226 | SOKOL_ASSERT(_stm.initialized == 0xABCDABCD); 227 | uint64_t now; 228 | #if defined(_WIN32) 229 | LARGE_INTEGER qpc_t; 230 | QueryPerformanceCounter(&qpc_t); 231 | now = int64_muldiv(qpc_t.QuadPart - _stm.start.QuadPart, 1000000000, _stm.freq.QuadPart); 232 | #elif defined(__APPLE__) && defined(__MACH__) 233 | const uint64_t mach_now = mach_absolute_time() - _stm.start; 234 | now = int64_muldiv(mach_now, _stm.timebase.numer, _stm.timebase.denom); 235 | #elif defined(__EMSCRIPTEN__) 236 | double js_now = stm_js_perfnow() - _stm.start; 237 | SOKOL_ASSERT(js_now >= 0.0); 238 | now = (uint64_t) (js_now * 1000000.0); 239 | #else 240 | struct timespec ts; 241 | clock_gettime(CLOCK_MONOTONIC, &ts); 242 | now = ((uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec) - _stm.start; 243 | #endif 244 | return now; 245 | } 246 | 247 | SOKOL_API_IMPL uint64_t stm_diff(uint64_t new_ticks, uint64_t old_ticks) { 248 | if (new_ticks > old_ticks) { 249 | return new_ticks - old_ticks; 250 | } 251 | else { 252 | return 1; 253 | } 254 | } 255 | 256 | SOKOL_API_IMPL uint64_t stm_since(uint64_t start_ticks) { 257 | return stm_diff(stm_now(), start_ticks); 258 | } 259 | 260 | SOKOL_API_IMPL uint64_t stm_laptime(uint64_t* last_time) { 261 | SOKOL_ASSERT(last_time); 262 | uint64_t dt = 0; 263 | uint64_t now = stm_now(); 264 | if (0 != *last_time) { 265 | dt = stm_diff(now, *last_time); 266 | } 267 | *last_time = now; 268 | return dt; 269 | } 270 | 271 | // first number is frame duration in ns, second number is tolerance in ns, 272 | // the resulting min/max values must not overlap! 273 | static const uint64_t _stm_refresh_rates[][2] = { 274 | { 16666667, 1000000 }, // 60 Hz: 16.6667 +- 1ms 275 | { 13888889, 250000 }, // 72 Hz: 13.8889 +- 0.25ms 276 | { 13333333, 250000 }, // 75 Hz: 13.3333 +- 0.25ms 277 | { 11764706, 250000 }, // 85 Hz: 11.7647 +- 0.25 278 | { 11111111, 250000 }, // 90 Hz: 11.1111 +- 0.25ms 279 | { 8333333, 500000 }, // 120 Hz: 8.3333 +- 0.5ms 280 | { 6944445, 500000 }, // 144 Hz: 6.9445 +- 0.5ms 281 | { 4166667, 1000000 }, // 240 Hz: 4.1666 +- 1ms 282 | { 0, 0 }, // keep the last element always at zero 283 | }; 284 | 285 | SOKOL_API_IMPL uint64_t stm_round_to_common_refresh_rate(uint64_t ticks) { 286 | uint64_t ns; 287 | int i = 0; 288 | while (0 != (ns = _stm_refresh_rates[i][0])) { 289 | uint64_t tol = _stm_refresh_rates[i][1]; 290 | if ((ticks > (ns - tol)) && (ticks < (ns + tol))) { 291 | return ns; 292 | } 293 | i++; 294 | } 295 | // fallthough: didn't fit into any buckets 296 | return ticks; 297 | } 298 | 299 | SOKOL_API_IMPL double stm_sec(uint64_t ticks) { 300 | return (double)ticks / 1000000000.0; 301 | } 302 | 303 | SOKOL_API_IMPL double stm_ms(uint64_t ticks) { 304 | return (double)ticks / 1000000.0; 305 | } 306 | 307 | SOKOL_API_IMPL double stm_us(uint64_t ticks) { 308 | return (double)ticks / 1000.0; 309 | } 310 | 311 | SOKOL_API_IMPL double stm_ns(uint64_t ticks) { 312 | return (double)ticks; 313 | } 314 | #endif /* SOKOL_IMPL */ 315 | -------------------------------------------------------------------------------- /src/apps/qak.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "qak.h" 3 | 4 | void printHelp() { 5 | printf("Usage: qak "); 6 | } 7 | 8 | int main(int argc, char **argv) { 9 | if (argc != 2) { 10 | printHelp(); 11 | return 0; 12 | } 13 | 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /src/apps/test.h: -------------------------------------------------------------------------------- 1 | #ifndef QAK_TEST_H 2 | #define QAK_TEST_H 3 | 4 | #include 5 | #include 6 | 7 | #ifdef WIN32 8 | #define WINDOWS_LEAN_AND_MEAN 9 | #include 10 | #endif 11 | 12 | #define QAK_CHECK(expr, ...) { if (!(expr)) { fprintf(stdout, "ERROR: "); fprintf(stdout, __VA_ARGS__); fprintf(stdout, " (%s:%d)\n", __FILE__, __LINE__); fflush(stdout); exit(-1); } } 13 | 14 | #ifdef __cplusplus 15 | namespace qak { 16 | struct Test { 17 | const char *name; 18 | 19 | Test(const char *name) : name(name) { 20 | #ifdef WIN32 21 | SetConsoleOutputCP(65001); 22 | #endif 23 | printf("========= Test: %s\n", name); 24 | } 25 | 26 | ~Test() { 27 | printf("\n"); 28 | } 29 | }; 30 | } 31 | #endif 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /src/apps/test_c_api.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "qak.h" 3 | #include "test.h" 4 | 5 | #define INDENT 3 6 | 7 | static void printIndent(int indent) { 8 | printf("%*s", indent, ""); 9 | } 10 | 11 | static void printSpan(const char *label, qak_span *span) { 12 | printf("%s%.*s\n", label, (int)span->data.length, span->data.data); 13 | } 14 | 15 | static void printAstNodeRecursive(qak_module module, qak_ast_node *node, int indent) { 16 | switch (node->type) { 17 | case QakAstTypeSpecifier: { 18 | printIndent(indent); 19 | printSpan("Type: ", &node->data.typeSpecifier.name); 20 | break; 21 | } 22 | case QakAstParameter: { 23 | printIndent(indent); 24 | printSpan("Parameter: ", &node->data.parameter.name); 25 | printAstNodeRecursive(module, qak_module_get_ast_node(module, node->data.parameter.typeSpecifier), indent + INDENT); 26 | break; 27 | } 28 | case QakAstFunction: { 29 | printIndent(indent); 30 | printSpan("Function: ", &node->data.function.name); 31 | if (node->data.function.parameters.numNodes > 0) { 32 | printIndent(indent + INDENT); 33 | printf("Parameters:\n"); 34 | for (size_t i = 0; i < node->data.function.parameters.numNodes; i++) 35 | printAstNodeRecursive(module, qak_module_get_ast_node(module, node->data.function.parameters.nodes[i]), indent + INDENT * 2); 36 | } 37 | if (node->data.function.returnType >= 0) { 38 | printIndent(indent + INDENT); 39 | printf("Return type:\n"); 40 | printAstNodeRecursive(module, qak_module_get_ast_node(module, node->data.function.returnType), indent + INDENT * 2); 41 | } 42 | 43 | if (node->data.function.statements.numNodes > 0) { 44 | printIndent(indent + INDENT); 45 | printf("Statements:\n"); 46 | for (size_t i = 0; i < node->data.function.statements.numNodes; i++) { 47 | printAstNodeRecursive(module, qak_module_get_ast_node(module, node->data.function.statements.nodes[i]), indent + INDENT * 2); 48 | } 49 | } 50 | break; 51 | } 52 | case QakAstVariable: { 53 | printIndent(indent); 54 | printSpan("Variable: ", &node->data.variable.name); 55 | if (node->data.variable.typeSpecifier >= 0) 56 | printAstNodeRecursive(module, qak_module_get_ast_node(module, node->data.variable.typeSpecifier), indent + INDENT); 57 | if (node->data.variable.initializerExpression >= 0) { 58 | printIndent(indent + INDENT); 59 | printf("Initializer: \n"); 60 | printAstNodeRecursive(module, qak_module_get_ast_node(module, node->data.variable.initializerExpression), indent + INDENT * 2); 61 | } 62 | break; 63 | } 64 | case QakAstWhile: { 65 | printIndent(indent); 66 | printf("While\n"); 67 | 68 | printIndent(indent + INDENT); 69 | printf("Condition: \n"); 70 | printAstNodeRecursive(module, qak_module_get_ast_node(module, node->data.whileNode.condition), indent + INDENT * 2); 71 | 72 | if (node->data.whileNode.statements.numNodes > 0) { 73 | printIndent(indent + INDENT); 74 | printf("Statements: \n"); 75 | for (size_t i = 0; i < node->data.whileNode.statements.numNodes; i++) { 76 | printAstNodeRecursive(module, qak_module_get_ast_node(module, node->data.whileNode.statements.nodes[i]), indent + INDENT * 2); 77 | } 78 | } 79 | break; 80 | } 81 | case QakAstIf: { 82 | printIndent(indent); 83 | printf("If\n"); 84 | 85 | printIndent(indent + INDENT); 86 | printf("Condition: \n"); 87 | printAstNodeRecursive(module, qak_module_get_ast_node(module, node->data.ifNode.condition), indent + INDENT * 2); 88 | 89 | if (node->data.ifNode.trueBlock.numNodes > 0) { 90 | printIndent(indent + INDENT); 91 | printf("True-block statements: \n"); 92 | for (size_t i = 0; i < node->data.ifNode.trueBlock.numNodes; i++) { 93 | printAstNodeRecursive(module, qak_module_get_ast_node(module, node->data.ifNode.trueBlock.nodes[i]), indent + INDENT * 2); 94 | } 95 | } 96 | 97 | if (node->data.ifNode.falseBlock.numNodes > 0) { 98 | printIndent(indent + INDENT); 99 | printf("False-block statements: \n"); 100 | for (size_t i = 0; i < node->data.ifNode.falseBlock.numNodes; i++) { 101 | printAstNodeRecursive(module, qak_module_get_ast_node(module, node->data.ifNode.falseBlock.nodes[i]), indent + INDENT * 2); 102 | } 103 | } 104 | break; 105 | } 106 | case QakAstReturn: { 107 | printIndent(indent); 108 | printf("Return:\n"); 109 | 110 | if (node->data.returnNode.returnValue) { 111 | printIndent(indent + INDENT); 112 | printf("Value:\n"); 113 | printAstNodeRecursive(module, qak_module_get_ast_node(module, node->data.returnNode.returnValue), indent + INDENT * 2); 114 | } 115 | 116 | break; 117 | } 118 | case QakAstTernaryOperation: { 119 | printIndent(indent); 120 | printf("Ternary operator:\n"); 121 | printAstNodeRecursive(module, qak_module_get_ast_node(module, node->data.ternaryOperation.condition), indent + INDENT); 122 | printAstNodeRecursive(module, qak_module_get_ast_node(module, node->data.ternaryOperation.trueValue), indent + INDENT); 123 | printAstNodeRecursive(module, qak_module_get_ast_node(module, node->data.ternaryOperation.falseValue), indent + INDENT); 124 | break; 125 | } 126 | case QakAstBinaryOperation: { 127 | printIndent(indent); 128 | printSpan("Binary operator: ", &node->data.binaryOperation.op); 129 | printAstNodeRecursive(module, qak_module_get_ast_node(module, node->data.binaryOperation.left), indent + INDENT); 130 | printAstNodeRecursive(module, qak_module_get_ast_node(module, node->data.binaryOperation.right), indent + INDENT); 131 | break; 132 | } 133 | case QakAstUnaryOperation: { 134 | printIndent(indent); 135 | printSpan("Unary op: ", &node->data.unaryOperation.op); 136 | printAstNodeRecursive(module, qak_module_get_ast_node(module, node->data.unaryOperation.value), indent + INDENT); 137 | break; 138 | } 139 | case QakAstLiteral: { 140 | printIndent(indent); 141 | printSpan("", &node->data.literal.value); 142 | break; 143 | } 144 | case QakAstVariableAccess: { 145 | printIndent(indent); 146 | printSpan("Variable access: ", &node->data.variableAccess.name); 147 | break; 148 | } 149 | case QakAstFunctionCall: { 150 | printIndent(indent); 151 | printSpan("Function call: ", &node->span); 152 | 153 | if (node->data.functionCall.arguments.numNodes > 0) { 154 | printIndent(indent + INDENT); 155 | printf("Arguments:\n"); 156 | for (size_t i = 0; i < node->data.functionCall.arguments.numNodes; i++) { 157 | printAstNodeRecursive(module, qak_module_get_ast_node(module, node->data.functionCall.arguments.nodes[i]), indent + INDENT * 2); 158 | } 159 | } 160 | break; 161 | } 162 | case QakAstModule: { 163 | printIndent(indent); 164 | printSpan("Module: ", &node->data.module.name); 165 | 166 | if (node->data.module.statements.numNodes > 0) { 167 | printIndent(indent + INDENT); 168 | printf("Module statements:\n"); 169 | for (size_t i = 0; i < node->data.module.statements.numNodes; i++) { 170 | printAstNodeRecursive(module, qak_module_get_ast_node(module, node->data.module.statements.nodes[i]), indent + INDENT * 2); 171 | } 172 | } 173 | 174 | for (size_t i = 0; i < node->data.module.functions.numNodes; i++) { 175 | printAstNodeRecursive(module, qak_module_get_ast_node(module, node->data.module.functions.nodes[i]), indent + INDENT); 176 | } 177 | break; 178 | } 179 | } 180 | } 181 | 182 | int main() { 183 | qak_compiler compiler = qak_compiler_new(); 184 | QAK_CHECK(compiler, "Couldn't create compiler"); 185 | 186 | qak_module module = qak_compiler_compile_file(compiler, "data/parser_function.qak"); 187 | QAK_CHECK(module, "Couldn't parse module"); 188 | 189 | qak_ast_module *astModule = qak_module_get_ast(module); 190 | QAK_CHECK(astModule, "Couldn't get module AST"); 191 | 192 | printSpan("Module: ", &astModule->name); 193 | 194 | printf("Functions: %i\n", astModule->functions.numNodes); 195 | for (size_t i = 0; i < astModule->functions.numNodes; i++) { 196 | printAstNodeRecursive(module, qak_module_get_ast_node(module, astModule->functions.nodes[i]), 1); 197 | } 198 | 199 | printf("Variables: %i\n", astModule->variables.numNodes); 200 | for (size_t i = 0; i < astModule->variables.numNodes; i++) { 201 | printAstNodeRecursive(module, qak_module_get_ast_node(module, astModule->variables.nodes[i]), 1); 202 | } 203 | 204 | printf("Statements: %i\n", astModule->statements.numNodes); 205 | for (size_t i = 0; i < astModule->statements.numNodes; i++) { 206 | printAstNodeRecursive(module, qak_module_get_ast_node(module, astModule->statements.nodes[i]), 1); 207 | } 208 | 209 | qak_module_delete(module); 210 | 211 | qak_compiler_delete(compiler); 212 | } 213 | -------------------------------------------------------------------------------- /src/apps/test_map.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "map.h" 3 | #include "test.h" 4 | 5 | using namespace qak; 6 | 7 | struct IntHashFunction { 8 | uint64_t operator()(const int &key) const { 9 | return (uint64_t) key; 10 | } 11 | }; 12 | 13 | struct IntEqualsFunction { 14 | bool operator()(const int &a, const int &b) const { 15 | return a == b; 16 | } 17 | }; 18 | 19 | typedef Map IntIntMap; 20 | 21 | int main() { 22 | Test test("Map"); 23 | HeapAllocator mem; 24 | { 25 | IntIntMap intMap(mem); 26 | 27 | intMap.put(1, 2); 28 | QAK_CHECK(intMap.size() == 1, "Expected size of 1, got %zu.", intMap.size()); 29 | 30 | MapEntry *entry = intMap.get(1); 31 | QAK_CHECK(entry != nullptr, "Expected map entry for key 1."); 32 | QAK_CHECK(entry->key == 1, "Expected key 1, got %i", entry->key); 33 | QAK_CHECK(entry->value == 2, "Expected value 2, got %i", entry->value); 34 | 35 | IntIntMap::MapEntries entries = intMap.entries(); 36 | QAK_CHECK(entries.hasNext(), "Expected an entry"); 37 | entry = entries.next(); 38 | QAK_CHECK(entry->key == 1, "Expected key 1, got %i", entry->key); 39 | QAK_CHECK(entry->value == 2, "Expected value 2, got %i", entry->value); 40 | QAK_CHECK(!entries.hasNext(), "Expected no more entries"); 41 | 42 | intMap.remove(1); 43 | QAK_CHECK(intMap.size() == 0, "Expected map size 0, got %zu", intMap.size()); 44 | QAK_CHECK(intMap.get(1) == nullptr, "Expected no entry for key 1"); 45 | } 46 | mem.printAllocations(); 47 | { 48 | IntIntMap intMap(mem); 49 | int sumKeys = 0; 50 | int sumValues = 0; 51 | for (int i = 0; i < 10; i++) { 52 | intMap.put(i, i * 10); 53 | sumKeys += i; 54 | sumValues += i * 10; 55 | } 56 | QAK_CHECK(intMap.size() == 10, "Expected map size to be 10, got %zu", intMap.size()); 57 | 58 | IntIntMap::MapEntries entries = intMap.entries(); 59 | while (entries.hasNext()) { 60 | MapEntry *entry = entries.next(); 61 | sumKeys -= entry->key; 62 | sumValues -= entry->value; 63 | } 64 | QAK_CHECK(sumKeys == 0, "Expected sumKeys to be 0, got %i", sumKeys); 65 | QAK_CHECK(sumValues == 0, "Expected sumValues to be 0, got %i", sumValues); 66 | } 67 | } 68 | 69 | -------------------------------------------------------------------------------- /src/apps/test_memory.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "memory.h" 3 | #include "test.h" 4 | 5 | using namespace qak; 6 | 7 | int main() { 8 | Test test("Bump allocator"); 9 | HeapAllocator mem; 10 | BumpAllocator allocator(mem, 16); 11 | 12 | uint8_t *fourBytes = allocator.alloc(4); 13 | QAK_CHECK(fourBytes != nullptr, "Expected allocated memory."); 14 | QAK_CHECK(allocator.head->next == nullptr, "Expected single block."); 15 | QAK_CHECK(allocator.head->nextFree - allocator.head->base == 4, "Expected 4 allocated bytes."); 16 | 17 | uint8_t *eightBytes = allocator.alloc(8); 18 | QAK_CHECK(eightBytes != nullptr, "Expected allocated memory."); 19 | QAK_CHECK(allocator.head->next == nullptr, "Expected single block."); 20 | QAK_CHECK(allocator.head->nextFree - allocator.head->base == 12, "Expected 12 allocated bytes."); 21 | 22 | uint8_t *moreBytes = allocator.alloc(1000); 23 | QAK_CHECK(moreBytes != nullptr, "Expected allocated memory."); 24 | QAK_CHECK(allocator.head->next != nullptr, "Expected single block."); 25 | QAK_CHECK(allocator.head->nextFree - allocator.head->base == 1000, "Expected 1000 allocated bytes."); 26 | 27 | allocator.free(); 28 | QAK_CHECK(allocator.head == nullptr, "Expected no block."); 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/apps/test_parser.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "io.h" 3 | #include "parser.h" 4 | #include "test.h" 5 | 6 | using namespace qak; 7 | using namespace qak::ast; 8 | 9 | void testBench() { 10 | Test test("Parser - Benchmark"); 11 | HeapAllocator mem; 12 | BumpAllocator bumpMem(mem); 13 | 14 | Source *source = io::readFile("data/parser_benchmark.qak", mem); 15 | QAK_CHECK(source != nullptr, "Couldn't read test file data/parser_benchmark.qak"); 16 | 17 | double start = io::timeMillis(); 18 | Parser parser(mem); 19 | Errors errors(mem, bumpMem); 20 | 21 | printf("Total allocations before benchmark: %zu\n", mem.totalAllocations()); 22 | 23 | uint32_t iterations = 100000; 24 | for (uint32_t i = 0; i < iterations; i++) { 25 | BumpAllocator moduleMem(mem); 26 | Module *module = parser.parse(*source, errors, &moduleMem); 27 | 28 | if (errors.hasErrors()) errors.print(); 29 | QAK_CHECK(module, "Expected module, got nullptr."); 30 | } 31 | 32 | double time = (io::timeMillis() - start) / 1000.0; 33 | double throughput = (double) source->size * iterations / time / 1024 / 1024; 34 | printf("File size: %zu bytes\n", source->size); 35 | printf("Took %f\n", time); 36 | printf("Throughput %f MB/s\n", throughput); 37 | 38 | printf("Total allocations after benchmark: %zu\n", mem.totalAllocations()); 39 | printf("Total frees: %zu\n", mem.totalFrees()); 40 | printf("Allocations after benchmark: %zu\n", mem.numAllocations()); 41 | } 42 | 43 | void testModule() { 44 | Test test("Parser - simple module"); 45 | HeapAllocator mem; 46 | BumpAllocator bumpMem(mem); 47 | 48 | Source *source = io::readFile("data/parser_module.qak", mem); 49 | QAK_CHECK(source != nullptr, "Couldn't read test file data/parser_module.qak"); 50 | 51 | Parser parser(mem); 52 | BumpAllocator moduleMem(mem); 53 | Errors errors(mem, moduleMem); 54 | Module *module = parser.parse(*source, errors, &moduleMem); 55 | QAK_CHECK(module, "Expected module, got nullptr."); 56 | } 57 | 58 | void testExpression() { 59 | Test test("Parser - expressions"); 60 | HeapAllocator mem; 61 | 62 | { 63 | Source *source = io::readFile("data/parser_expression.qak", mem); 64 | QAK_CHECK(source != nullptr, "Couldn't read test file data/parser_expression.qak"); 65 | Parser parser(mem); 66 | BumpAllocator moduleMem(mem); 67 | Errors errors(mem, moduleMem); 68 | Module *module = parser.parse(*source, errors, &moduleMem); 69 | if (errors.hasErrors()) errors.print(); 70 | QAK_CHECK(module, "Expected module, got nullptr."); 71 | module->~Module(); 72 | mem.freeObject(source, QAK_SRC_LOC); 73 | } 74 | 75 | if (mem.numAllocations() != 0) mem.printAllocations(); 76 | QAK_CHECK(mem.numAllocations() == 0, "Expected all memory to be deallocated, but %zu allocations remaining.", mem.numAllocations()); 77 | } 78 | 79 | void testModuleVariable() { 80 | Test test("Parser - module variable"); 81 | HeapAllocator mem; 82 | 83 | Source *source = io::readFile("data/parser_module_var.qak", mem); 84 | QAK_CHECK(source != nullptr, "Couldn't read test file data/parser_module_var.qak"); 85 | 86 | Parser parser(mem); 87 | BumpAllocator moduleMem(mem); 88 | Errors errors(mem, moduleMem); 89 | Module *module = parser.parse(*source, errors, &moduleMem); 90 | if (errors.hasErrors()) errors.print(); 91 | QAK_CHECK(module, "Expected module, got nullptr."); 92 | 93 | parser::printAstNode(module, mem); 94 | } 95 | 96 | void testFunction() { 97 | Test test("Parser - function"); 98 | HeapAllocator mem; 99 | 100 | Source *source = io::readFile("data/parser_function.qak", mem); 101 | QAK_CHECK(source != nullptr, "Couldn't read test file data/parser_function.qak"); 102 | 103 | Parser parser(mem); 104 | BumpAllocator moduleMem(mem); 105 | Errors errors(mem, moduleMem); 106 | Module *module = parser.parse(*source, errors, &moduleMem); 107 | if (errors.hasErrors()) errors.print(); 108 | QAK_CHECK(module, "Expected module, got nullptr."); 109 | 110 | parser::printAstNode(module, mem); 111 | } 112 | 113 | void testV01() { 114 | Test test("Parser - v0.1"); 115 | HeapAllocator mem; 116 | 117 | { 118 | Source *source = io::readFile("data/parser_v_0_1.qak", mem); 119 | QAK_CHECK(source != nullptr, "Couldn't read test file data/parser_v_0_1.qak"); 120 | 121 | Parser parser(mem); 122 | BumpAllocator moduleMem(mem); 123 | Errors errors(mem, moduleMem); 124 | Module *module = parser.parse(*source, errors, &moduleMem); 125 | if (errors.hasErrors()) errors.print(); 126 | QAK_CHECK(module, "Expected module, got nullptr."); 127 | 128 | { 129 | HeapAllocator printMem; 130 | tokenizer::printTokens(parser.tokens(), printMem); 131 | parser::printAstNode(module, printMem); 132 | } 133 | 134 | mem.freeObject(source, QAK_SRC_LOC); 135 | } 136 | 137 | if (mem.numAllocations() != 0) mem.printAllocations(); 138 | QAK_CHECK(mem.numAllocations() == 0, "Expected all memory to be deallocated, but %zu allocations remaining.", mem.numAllocations()); 139 | } 140 | 141 | void testEOL() { 142 | Test test("Parser - EOL"); 143 | HeapAllocator mem; 144 | 145 | Source *source = io::readFile("data/parser_eol.qak", mem); 146 | QAK_CHECK(source != nullptr, "Couldn't read test file data/parser_eol.qak"); 147 | 148 | Parser parser(mem); 149 | BumpAllocator moduleMem(mem); 150 | Errors errors(mem, moduleMem); 151 | Module *module = parser.parse(*source, errors, &moduleMem); 152 | if (errors.hasErrors()) errors.print(); 153 | QAK_CHECK(module, "Expected module, got nullptr."); 154 | 155 | parser::printAstNode(module, mem); 156 | } 157 | 158 | int main() { 159 | testEOL(); 160 | 161 | testModule(); 162 | testExpression(); 163 | testModuleVariable(); 164 | testFunction(); 165 | testV01(); 166 | testBench(); 167 | return 0; 168 | } -------------------------------------------------------------------------------- /src/apps/test_tokenizer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "io.h" 3 | #include "tokenizer.h" 4 | #include "test.h" 5 | 6 | using namespace qak; 7 | 8 | void testBench() { 9 | Test test("Tokenizer - Benchmark"); 10 | double start = io::timeMillis(); 11 | HeapAllocator mem; 12 | BumpAllocator bumpMem(mem); 13 | Source *source = io::readFile("data/parser_benchmark.qak", mem); 14 | QAK_CHECK(source != nullptr, "Couldn't read test file data/parser_benchmark.qak"); 15 | 16 | Array tokens(mem); 17 | Errors errors(mem, bumpMem); 18 | uint32_t iterations = 100000; 19 | for (uint32_t i = 0; i < iterations; i++) { 20 | tokens.clear(); 21 | tokenizer::tokenize(*source, tokens, errors); 22 | } 23 | 24 | double time = (io::timeMillis() - start) / 1000.0; 25 | double throughput = (double) source->size * iterations / time / 1024 / 1024; 26 | printf("File size: %zu bytes\n", source->size); 27 | printf("Took %f\n", time); 28 | printf("Throughput %f MB/s\n", throughput); 29 | } 30 | 31 | void testTokenizer() { 32 | Test test("Tokenizer - all token types"); 33 | HeapAllocator mem; 34 | BumpAllocator bumpMem(mem); 35 | Source *source = io::readFile("data/tokens.qak", mem); 36 | QAK_CHECK(source != nullptr, "Couldn't read test file data/tokens.qak"); 37 | 38 | Array tokens(mem); 39 | Errors errors(mem, bumpMem); 40 | 41 | tokenizer::tokenize(*source, tokens, errors); 42 | 43 | QAK_CHECK(tokens.size() == 42, "Expected 42 tokens, got %zu", tokens.size()) 44 | if (errors.hasErrors()) errors.print(); 45 | QAK_CHECK(errors.getErrors().size() == 0, "Expected 0 errors, got %zu", errors.getErrors().size()); 46 | 47 | for (uint32_t i = 0; i < tokens.size(); i++) { 48 | Token &token = tokens[i]; 49 | printf("%s (%d:%d:%d): %s\n", tokenizer::tokenTypeToString(token.type), token.startLine, token.start, token.end, token.toCString(mem)); 50 | } 51 | } 52 | 53 | void testError() { 54 | Test test("Tokenizer - unknown token"); 55 | HeapAllocator mem; 56 | BumpAllocator bumpMem(mem); 57 | Source *source = io::readFile("data/tokens_error.qak", mem); 58 | QAK_CHECK(source != nullptr, "Couldn't read test file data/tokens_error.qak"); 59 | 60 | Array tokens(mem); 61 | Errors errors(mem, bumpMem); 62 | 63 | tokenizer::tokenize(*source, tokens, errors); 64 | QAK_CHECK(errors.getErrors().size() == 1, "Expected 1 error, got %zu", errors.getErrors().size()); 65 | 66 | errors.getErrors()[0].print(); 67 | } 68 | 69 | void generateLiteralToTokenArray() { 70 | HeapAllocator mem; 71 | uint32_t type = Period; 72 | Array types(mem); 73 | types.setSize(256, qak::Unknown); 74 | 75 | // Else check for simple tokens 76 | while (type != qak::Unknown) { 77 | const char *literal = tokenizer::tokenTypeToString((qak::TokenType) type); 78 | if (strlen(literal) == 1) { 79 | types.buffer()[(size_t) literal[0]] = (qak::TokenType) type; 80 | } else { 81 | printf("Non single-character literal: %s\n", literal); 82 | } 83 | type++; 84 | } 85 | 86 | printf("static const uint32_t literalToTokenType[] = {\n"); 87 | for (int i = 0; i < (int) types.size(); i++) { 88 | printf("\t%d", types[i]); 89 | if (i < (int) types.size() - 1) 90 | if (types[i] != qak::Unknown) 91 | printf(", // %s\n", tokenizer::tokenTypeToString(types[i])); 92 | else 93 | printf(",\n"); 94 | else 95 | printf("\n"); 96 | } 97 | printf("};"); 98 | } 99 | 100 | int main() { 101 | testTokenizer(); 102 | testError(); 103 | testBench(); 104 | return 0; 105 | } 106 | -------------------------------------------------------------------------------- /src/array.h: -------------------------------------------------------------------------------- 1 | #ifndef QAK_ARRAY_H 2 | #define QAK_ARRAY_H 3 | 4 | #include "memory.h" 5 | 6 | namespace qak { 7 | 8 | template 9 | class Array { 10 | private: 11 | HeapAllocator &_mem; 12 | size_t _size; 13 | size_t _capacity; 14 | T *_buffer; 15 | 16 | QAK_FORCE_INLINE void deallocate(T *buffer) { 17 | if (buffer) { 18 | _mem.free(buffer, QAK_SRC_LOC); 19 | } 20 | } 21 | 22 | QAK_FORCE_INLINE void construct(T *buffer, const T &val) { 23 | new(buffer) T(val); 24 | } 25 | 26 | QAK_FORCE_INLINE void destroy(T *buffer) { 27 | buffer->~T(); 28 | } 29 | 30 | Array(const Array &other) = delete; 31 | 32 | public: 33 | Array(HeapAllocator &mem, size_t capacity = 0) : _mem(mem), _size(0), _capacity(0), _buffer(nullptr) { 34 | if (capacity > 0) 35 | ensureCapacity(capacity); 36 | } 37 | 38 | ~Array() { 39 | clear(); 40 | deallocate(_buffer); 41 | } 42 | 43 | QAK_FORCE_INLINE void clear() { 44 | for (size_t i = 0; i < _size; ++i) { 45 | destroy(_buffer + (_size - 1 - i)); 46 | } 47 | 48 | _size = 0; 49 | } 50 | 51 | QAK_FORCE_INLINE void freeObjects() { 52 | for (int32_t i = (int32_t) size() - 1; i >= 0; i--) { 53 | _mem.freeObject(_buffer[i], QAK_SRC_LOC); 54 | } 55 | _size = 0; 56 | } 57 | 58 | QAK_FORCE_INLINE size_t capacity() const { 59 | return _capacity; 60 | } 61 | 62 | QAK_FORCE_INLINE size_t size() const { 63 | return _size; 64 | } 65 | 66 | QAK_FORCE_INLINE void setSize(size_t newSize, const T &defaultValue) { 67 | size_t oldSize = _size; 68 | _size = newSize; 69 | if (_capacity < newSize) { 70 | _capacity = (size_t) (_size * 1.75f); 71 | if (_capacity < 8) _capacity = 8; 72 | _buffer = _mem.realloc(_buffer, _capacity, QAK_SRC_LOC); 73 | } 74 | if (oldSize < _size) { 75 | for (size_t i = oldSize; i < _size; i++) { 76 | construct(_buffer + i, defaultValue); 77 | } 78 | } 79 | } 80 | 81 | QAK_FORCE_INLINE void ensureCapacity(size_t newCapacity = 0) { 82 | if (_capacity >= newCapacity) return; 83 | _capacity = newCapacity; 84 | _buffer = _mem.realloc(_buffer, newCapacity, QAK_SRC_LOC); 85 | } 86 | 87 | QAK_FORCE_INLINE void add(const T &inValue) { 88 | if (_size == _capacity) { 89 | // inValue might reference an element in this buffer 90 | // When we reallocate, the reference becomes invalid. 91 | // We thus need to create a defensive copy before 92 | // reallocating. 93 | T valueCopy = inValue; 94 | _capacity = (size_t) (_size * 1.75f); 95 | if (_capacity < 8) _capacity = 8; 96 | _buffer = _mem.realloc(_buffer, _capacity, QAK_SRC_LOC); 97 | construct(_buffer + _size++, valueCopy); 98 | } else { 99 | construct(_buffer + _size++, inValue); 100 | } 101 | } 102 | 103 | QAK_FORCE_INLINE void addAll(Array &inValue) { 104 | ensureCapacity(this->size() + inValue.size()); 105 | for (size_t i = 0; i < inValue.size(); i++) { 106 | add(inValue[i]); 107 | } 108 | } 109 | 110 | QAK_FORCE_INLINE void removeAt(size_t inIndex) { 111 | --_size; 112 | 113 | if (inIndex != _size) { 114 | for (size_t i = inIndex; i < _size; ++i) { 115 | T tmp(_buffer[i]); 116 | _buffer[i] = _buffer[i + 1]; 117 | _buffer[i + 1] = tmp; 118 | } 119 | } 120 | 121 | destroy(_buffer + _size); 122 | } 123 | 124 | QAK_FORCE_INLINE bool contains(const T &inValue) { 125 | for (size_t i = 0; i < _size; ++i) { 126 | if (_buffer[i] == inValue) { 127 | return true; 128 | } 129 | } 130 | 131 | return false; 132 | } 133 | 134 | QAK_FORCE_INLINE int32_t indexOf(const T &inValue) { 135 | for (size_t i = 0; i < _size; ++i) { 136 | if (_buffer[i] == inValue) { 137 | return (int32_t) i; 138 | } 139 | } 140 | 141 | return -1; 142 | } 143 | 144 | QAK_FORCE_INLINE T &operator[](size_t inIndex) { 145 | return _buffer[inIndex]; 146 | } 147 | 148 | QAK_FORCE_INLINE T *buffer() { 149 | return _buffer; 150 | } 151 | }; 152 | 153 | template 154 | class FixedArray { 155 | private: 156 | BumpAllocator &_mem; 157 | size_t _size; 158 | T *_buffer; 159 | 160 | FixedArray(const FixedArray &other) = delete; 161 | 162 | public: 163 | FixedArray(BumpAllocator &mem) : _mem(mem), _size(0), _buffer(nullptr) {} 164 | 165 | FixedArray(BumpAllocator &mem, Array &array) : _mem(mem), _size(array.size()) { 166 | if (array.size() > 0) { 167 | _buffer = _mem.alloc(_size); 168 | for (size_t i = 0; i < _size; i++) { 169 | new(_buffer + i) T(array[i]); 170 | } 171 | } 172 | } 173 | 174 | QAK_FORCE_INLINE void set(Array &array) { 175 | if (array.size() > 0) { 176 | _size = array.size(); 177 | _buffer = _mem.alloc(_size); 178 | for (size_t i = 0; i < _size; i++) { 179 | new(_buffer + i) T(array[i]); 180 | } 181 | } else { 182 | _size = 0; 183 | } 184 | } 185 | 186 | QAK_FORCE_INLINE T &operator[](size_t inIndex) { 187 | return _buffer[inIndex]; 188 | } 189 | 190 | QAK_FORCE_INLINE size_t size() const { 191 | return _size; 192 | } 193 | }; 194 | 195 | template 196 | class ArrayPool { 197 | private: 198 | HeapAllocator &_mem; 199 | Array *> _items; 200 | 201 | ArrayPool(const ArrayPool &other) = delete; 202 | 203 | public: 204 | ArrayPool(HeapAllocator &mem) : _mem(mem), _items(mem) { 205 | } 206 | 207 | ~ArrayPool() { 208 | _items.freeObjects(); 209 | } 210 | 211 | Array *obtain(const char *file, int32_t line) { 212 | Array *result = nullptr; 213 | if (_items.size() == 0) { 214 | result = _mem.allocObject>(file, line, _mem); 215 | } else { 216 | result = _items[_items.size() - 1]; 217 | _items.removeAt(_items.size() - 1); 218 | } 219 | return result; 220 | } 221 | 222 | void free(Array *array) { 223 | array->clear(); 224 | _items.add(array); 225 | } 226 | }; 227 | } 228 | 229 | #endif //QAK_ARRAY_H 230 | -------------------------------------------------------------------------------- /src/error.cpp: -------------------------------------------------------------------------------- 1 | #include "error.h" 2 | #include 3 | #include 4 | 5 | using namespace qak; 6 | 7 | Line &Error::getLine() { 8 | return span.source.lines()[span.startLine]; 9 | } 10 | 11 | void Error::print() { 12 | HeapAllocator mem; 13 | Line &line = getLine(); 14 | uint8_t *lineStr = mem.alloc(line.length() + 1, QAK_SRC_LOC); 15 | if (line.length() > 0) memcpy(lineStr, span.source.data + line.start, line.length()); 16 | lineStr[line.length()] = 0; 17 | 18 | printf("Error (%s:%i): %s\n", span.source.fileName, line.lineNumber, message); 19 | 20 | if (line.length() > 0) { 21 | printf("%s\n", lineStr); 22 | int32_t errorStart = span.start - line.start; 23 | int32_t errorEnd = errorStart + span.length() - 1; 24 | for (int32_t i = 0, n = line.length(); i < n; i++) { 25 | bool useTab = (span.source.data + line.start)[i] == '\t'; 26 | printf("%s", i >= errorStart && i <= errorEnd ? "^" : (useTab ? "\t" : " ")); 27 | } 28 | } 29 | } 30 | 31 | void Errors::add(Error error) { 32 | errors.add(error); 33 | } 34 | 35 | void Errors::add(Span span, const char *msg...) { 36 | va_list args; 37 | va_start(args, msg); 38 | char scratch[1]; 39 | int len = vsnprintf(scratch, 1, msg, args); 40 | char *buffer = bumpMem.alloc(len + 1); 41 | vsnprintf(buffer, len + 1, msg, args); 42 | va_end(args); 43 | add({span, buffer}); 44 | } 45 | 46 | void Errors::addAll(Errors &errors) { 47 | for (size_t i = 0; i < errors.getErrors().size(); i++) { 48 | add(errors.getErrors()[i]); 49 | } 50 | } 51 | 52 | Array &Errors::getErrors() { 53 | return errors; 54 | } 55 | 56 | bool Errors::hasErrors() { 57 | return errors.size() != 0; 58 | } 59 | 60 | void Errors::print() { 61 | for (uint32_t i = 0; i < errors.size(); i++) { 62 | errors[i].print(); 63 | } 64 | printf("\n"); 65 | fflush(stdout); 66 | } -------------------------------------------------------------------------------- /src/error.h: -------------------------------------------------------------------------------- 1 | #ifndef QAK_ERROR_H 2 | #define QAK_ERROR_H 3 | 4 | #include "source.h" 5 | #include "array.h" 6 | 7 | namespace qak { 8 | 9 | #define QAK_ERROR(span, ...) { errors.add(span, __VA_ARGS__); return; } 10 | 11 | struct Error { 12 | Span span; 13 | const char *message; 14 | 15 | Error(Span span, const char *message) : span(span), message(message) {} 16 | 17 | Line &getLine(); 18 | 19 | void print(); 20 | }; 21 | 22 | struct Errors { 23 | BumpAllocator &bumpMem; 24 | Array errors; 25 | 26 | Errors(HeapAllocator &mem, BumpAllocator &bumpMem) : bumpMem(bumpMem), errors(mem) {} 27 | 28 | void add(Error error); 29 | 30 | void add(Span span, const char *msg...); 31 | 32 | void addAll(Errors &errors); 33 | 34 | Array &getErrors(); 35 | 36 | bool hasErrors(); 37 | 38 | void print(); 39 | }; 40 | } 41 | 42 | #endif //QAK_ERROR_H 43 | -------------------------------------------------------------------------------- /src/io.cpp: -------------------------------------------------------------------------------- 1 | #include "io.h" 2 | 3 | #include 4 | 5 | #define SOKOL_IMPL 6 | 7 | #include "3rdparty/sokol_time.h" 8 | 9 | 10 | using namespace qak; 11 | 12 | 13 | Source *io::readFile(const char *fileName, HeapAllocator &mem) { 14 | FILE *file = fopen(fileName, "rb"); 15 | if (file == nullptr) { 16 | return nullptr; 17 | } 18 | 19 | // BOZO error handling 20 | fseek(file, 0L, SEEK_END); 21 | size_t size = ftell(file); 22 | fseek(file, 0L, SEEK_SET); 23 | 24 | uint8_t *data = mem.alloc(size, QAK_SRC_LOC); 25 | fread(data, sizeof(uint8_t), size, file); 26 | fclose(file); 27 | 28 | size_t fileNameLength = strlen(fileName) + 1; 29 | char *fileNameCopy = mem.alloc(fileNameLength, QAK_SRC_LOC); 30 | memcpy(fileNameCopy, fileName, fileNameLength); 31 | return mem.allocObject(QAK_SRC_LOC, mem, fileNameCopy, data, size); 32 | } 33 | 34 | static bool isTimeSetup = false; 35 | 36 | double io::timeMillis() { 37 | if (!isTimeSetup) { 38 | stm_setup(); 39 | isTimeSetup = true; 40 | } 41 | uint64_t time = stm_now(); 42 | return stm_ms(time); 43 | } -------------------------------------------------------------------------------- /src/io.h: -------------------------------------------------------------------------------- 1 | #ifndef QAK_IO_H 2 | #define QAK_IO_H 3 | 4 | #include "source.h" 5 | 6 | namespace qak { 7 | namespace io { 8 | Source *readFile(const char *fileName, HeapAllocator &mem); 9 | 10 | double timeMillis(); 11 | } 12 | } 13 | 14 | #endif //QAK_IO_H 15 | -------------------------------------------------------------------------------- /src/map.h: -------------------------------------------------------------------------------- 1 | #ifndef QAK_MAP_H 2 | #define QAK_MAP_H 3 | 4 | #include "memory.h" 5 | #include "array.h" 6 | 7 | namespace qak { 8 | 9 | template 10 | struct HashFunction { 11 | int64_t operator()(const K &key) const { 12 | return (int64_t) key; 13 | } 14 | }; 15 | 16 | template 17 | struct EqualsFunction { 18 | bool operator()(const K &a, const K &b) const { 19 | return (int64_t) a == (int64_t) b; 20 | } 21 | }; 22 | 23 | template 24 | struct MapEntry { 25 | K key; 26 | V value; 27 | MapEntry *next; 28 | 29 | MapEntry(K key, V value) : key(key), value(value), next(nullptr) {} 30 | }; 31 | 32 | template, typename E = EqualsFunction> 33 | class Map { 34 | private: 35 | HeapAllocator &_mem; 36 | Array *> _entries; 37 | size_t _size; 38 | H _hashFunc; 39 | E _equalsFunc; 40 | 41 | public: 42 | struct MapEntries { 43 | Array *> &_entries; 44 | size_t _index; 45 | MapEntry *_nextEntry; 46 | 47 | MapEntries(Array *> &entries) : _entries(entries), _index(0), _nextEntry(entries[0]) {} 48 | 49 | bool hasNext() { 50 | while (true) { 51 | if (_index >= _entries.size()) return false; 52 | if (_nextEntry == nullptr) { 53 | _index++; 54 | if (_index < _entries.size()) _nextEntry = _entries[_index]; 55 | continue; 56 | } 57 | return true; 58 | } 59 | } 60 | 61 | MapEntry *next() { 62 | MapEntry *entry = _nextEntry; 63 | if (entry != nullptr) _nextEntry = entry->next; 64 | return entry; 65 | } 66 | }; 67 | 68 | explicit Map(HeapAllocator &mem) : _mem(mem), _entries(mem), _size(0) { 69 | _entries.setSize(16, nullptr); 70 | } 71 | 72 | Map(HeapAllocator &mem, size_t tableSize) : _mem(mem), _entries(mem), _size(0) { 73 | _entries.setSize(tableSize, nullptr); 74 | } 75 | 76 | ~Map() { 77 | for (size_t i = 0; i < _entries.size(); i++) { 78 | MapEntry *entry = _entries[i]; 79 | while (entry != nullptr) { 80 | MapEntry *next = entry->next; 81 | _mem.free(entry, QAK_SRC_LOC); 82 | entry = next; 83 | } 84 | } 85 | } 86 | 87 | void put(const K &key, const V &value) { 88 | size_t hash = (size_t) (_hashFunc(key) % _entries.size()); 89 | MapEntry *entry = _entries[hash]; 90 | _size++; 91 | 92 | // No entries for that hash, add a new entry 93 | if (entry == nullptr) { 94 | entry = new(_mem.alloc>(1, QAK_SRC_LOC)) MapEntry(key, value); 95 | _entries[(size_t) hash] = entry; 96 | return; 97 | } 98 | 99 | while (entry != nullptr) { 100 | // Found key, replace key and value in entry. 101 | if (_equalsFunc(key, entry->key)) { 102 | entry->key = key; 103 | entry->value = value; 104 | return; 105 | } 106 | entry = entry->next; 107 | } 108 | 109 | // Didn't find key, add a new entry 110 | entry = new(_mem.alloc>(1, QAK_SRC_LOC)) MapEntry(key, value); 111 | entry->next = _entries[hash]; 112 | _entries[hash] = entry; 113 | } 114 | 115 | MapEntry *get(const K &key) { 116 | size_t hash = (size_t) (_hashFunc(key) % _entries.size()); 117 | MapEntry *entry = _entries[hash]; 118 | while (entry != nullptr) { 119 | if (_equalsFunc(key, entry->key)) break; 120 | entry = entry->next; 121 | } 122 | return entry; 123 | } 124 | 125 | void remove(const K &key) { 126 | size_t hash = (size_t) (_hashFunc(key) % _entries.size()); 127 | MapEntry *prevEntry = nullptr; 128 | MapEntry *entry = _entries[hash]; 129 | while (entry != nullptr) { 130 | if (_equalsFunc(key, entry->key)) { 131 | if (prevEntry == nullptr) { 132 | _entries[hash] = entry->next; 133 | } else { 134 | prevEntry->next = entry->next; 135 | } 136 | entry->~MapEntry(); 137 | _mem.free(entry, QAK_SRC_LOC); 138 | _size--; 139 | break; 140 | } 141 | prevEntry = entry; 142 | entry = entry->next; 143 | } 144 | } 145 | 146 | MapEntries entries() { 147 | return MapEntries(_entries); 148 | } 149 | 150 | size_t size() { 151 | return _size; 152 | } 153 | }; 154 | } 155 | 156 | #endif //QAK_MAP_H 157 | -------------------------------------------------------------------------------- /src/memory.h: -------------------------------------------------------------------------------- 1 | #ifndef QAK_MEMORY_H 2 | #define QAK_MEMORY_H 3 | 4 | #include "types.h" 5 | #include 6 | #include 7 | #include 8 | 9 | // Default block size of the BumpAllocator 10 | #define QAK_BLOCK_SIZE (512 * 16) 11 | 12 | namespace qak { 13 | 14 | struct Allocation { 15 | void *address; 16 | size_t size; 17 | const char *fileName; 18 | int32_t line; 19 | 20 | Allocation() : address(nullptr), size(0), fileName(nullptr), line(0) {} 21 | 22 | Allocation(void *address, size_t size, const char *file, int32_t line) : address(address), size(size), fileName(file), line(line) {} 23 | }; 24 | 25 | class HeapAllocator { 26 | private: 27 | std::map _allocations; 28 | size_t _totalAllocations; 29 | size_t _totalFrees; 30 | 31 | public: 32 | HeapAllocator() : _totalAllocations(0), _totalFrees(0) {}; 33 | 34 | HeapAllocator(const HeapAllocator &other) = delete; 35 | 36 | ~HeapAllocator() { 37 | for (std::map::iterator it = _allocations.begin(); it != _allocations.end(); it++) { 38 | ::free(it->second.address); 39 | } 40 | } 41 | 42 | template 43 | T *alloc(size_t num, const char *file, int32_t line) { 44 | size_t size = sizeof(T) * num; 45 | if (size == 0) return nullptr; 46 | 47 | _totalAllocations++; 48 | 49 | T *ptr = (T *) ::malloc(size); 50 | _allocations[(void *) ptr] = Allocation(ptr, size, file, line); 51 | return ptr; 52 | } 53 | 54 | template 55 | T *allocObject(const char *file, int32_t line, ARGS &&...args) { 56 | T *obj = new(alloc(1, file, line)) T(std::forward(args)...); 57 | return obj; 58 | } 59 | 60 | template 61 | T *calloc(size_t num, const char *file, int32_t line) { 62 | size_t size = sizeof(T) * num; 63 | if (size == 0) return nullptr; 64 | 65 | _totalAllocations++; 66 | 67 | T *ptr = (T *) ::malloc(size); 68 | ::memset(ptr, 0, size); 69 | _allocations[(void *) ptr] = Allocation(ptr, size, file, line); 70 | return ptr; 71 | } 72 | 73 | template 74 | T *realloc(T *ptr, size_t num, const char *file, int32_t line) { 75 | size_t size = sizeof(T) * num; 76 | if (size == 0) return nullptr; 77 | 78 | _totalAllocations++; 79 | 80 | T *result = nullptr; 81 | if (ptr == nullptr) { 82 | result = (T *) ::malloc(size); 83 | } else { 84 | result = (T *) ::realloc(ptr, size); 85 | _allocations.erase(ptr); 86 | } 87 | _allocations[(void *) result] = Allocation(result, size, file, line); 88 | return result; 89 | } 90 | 91 | template 92 | void freeObject(E *ptr, const char *file, int32_t line) { 93 | ptr->~E(); 94 | free(ptr, file, line); 95 | } 96 | 97 | void free(void *ptr, const char *file, int32_t line) { 98 | if (_allocations.count(ptr)) { 99 | ::free((void *) ptr); 100 | _allocations.erase(ptr); 101 | _totalFrees++; 102 | } else { 103 | printf("%s:%i (address %p): Double free or not allocated through qak::memory\n", file, line, (void *) ptr); 104 | } 105 | } 106 | 107 | void printAllocations() { 108 | if (_allocations.size() > 0) { 109 | uint64_t totalSize = 0; 110 | for (std::map::iterator it = _allocations.begin(); it != _allocations.end(); it++) { 111 | printf("%s:%i (%zu bytes at %p)\n", it->second.fileName, it->second.line, it->second.size, it->second.address); 112 | totalSize += it->second.size; 113 | } 114 | printf("Total memory: %llu, #allocations: %zu\n", (unsigned long long) totalSize, _allocations.size()); 115 | } else { 116 | printf("No allocations."); 117 | } 118 | } 119 | 120 | size_t numAllocations() { 121 | return _allocations.size(); 122 | } 123 | 124 | size_t totalAllocations() { 125 | return _totalAllocations; 126 | } 127 | 128 | size_t totalFrees() { 129 | return _totalFrees; 130 | } 131 | }; 132 | 133 | struct Block { 134 | uint8_t *base; 135 | uint8_t *end; 136 | uint8_t *nextFree; 137 | size_t size; 138 | Block *next; 139 | 140 | Block(size_t size) : size(size), next(nullptr) { 141 | base = (uint8_t *) ::malloc(size); 142 | end = base + size; 143 | nextFree = base; 144 | } 145 | 146 | ~Block() { 147 | ::free(base); 148 | } 149 | 150 | QAK_FORCE_INLINE bool canStore(size_t size) { 151 | return nextFree + size < end; 152 | } 153 | 154 | QAK_FORCE_INLINE uint8_t *alloc(size_t size) { 155 | uint8_t *ptr = nextFree; 156 | nextFree += size; 157 | return ptr; 158 | } 159 | }; 160 | 161 | struct BumpAllocator { 162 | HeapAllocator &mem; 163 | Block *head; 164 | size_t blockSize; 165 | 166 | BumpAllocator(HeapAllocator &mem) : mem(mem), head(nullptr), blockSize(QAK_BLOCK_SIZE) { 167 | } 168 | 169 | BumpAllocator(HeapAllocator &mem, size_t blockSize) : mem(mem), head(nullptr), blockSize(blockSize) { 170 | }; 171 | 172 | BumpAllocator(BumpAllocator const &) = delete; 173 | 174 | ~BumpAllocator() { 175 | free(); 176 | } 177 | 178 | template 179 | T *allocObject(ARGS &&...args) { 180 | T *obj = new(alloc(1)) T(std::forward(args)...); 181 | return obj; 182 | } 183 | 184 | template 185 | T *alloc(size_t num) { 186 | size_t size = sizeof(T) * num; 187 | if (size == 0) return nullptr; 188 | 189 | if (head == nullptr || !head->canStore(size)) { 190 | Block *newHead = mem.allocObject(QAK_SRC_LOC, blockSize < size ? size * 2 : blockSize); 191 | newHead->next = head; 192 | head = newHead; 193 | } 194 | 195 | return (T *) head->alloc(size); 196 | } 197 | 198 | void free() { 199 | while (head) { 200 | Block *block = head; 201 | head = block->next; 202 | mem.freeObject(block, QAK_SRC_LOC); 203 | } 204 | } 205 | }; 206 | } 207 | 208 | #endif //QAK_MEMORY_H 209 | -------------------------------------------------------------------------------- /src/parser.cpp: -------------------------------------------------------------------------------- 1 | #include "parser.h" 2 | 3 | using namespace qak; 4 | 5 | using namespace qak::ast; 6 | 7 | /** Used to keep track of obtained arrays from 8 | * an ArrayPool. Frees them when the monitor is 9 | * destructed. Can keep track of a maximum of 10 | * 4 arrays. */ 11 | template 12 | struct ArrayPoolMonitor { 13 | ArrayPool &pool; 14 | Array *tracked[4]; 15 | size_t numTracked; 16 | 17 | ArrayPoolMonitor(ArrayPool &pool) : pool(pool), numTracked(0) {} 18 | 19 | ~ArrayPoolMonitor() { 20 | for (size_t i = 0; i < numTracked; i++) { 21 | pool.free(tracked[i]); 22 | } 23 | } 24 | 25 | QAK_FORCE_INLINE Array *obtain(const char *file, int32_t line) { 26 | Array *result = pool.obtain(file, line); 27 | tracked[numTracked++] = result; 28 | return result; 29 | } 30 | }; 31 | 32 | Module *Parser::parse(Source &source, Errors &errors, BumpAllocator *bumpMem) { 33 | _source = &source; 34 | _errors = &errors; 35 | _bumpMem = bumpMem; 36 | 37 | ArrayPoolMonitor functionArrays(_functionArrayPool); 38 | ArrayPoolMonitor statementArrays(_statementArrayPool); 39 | ArrayPoolMonitor variableArrays(_variableArrayPool); 40 | 41 | _tokens.clear(); 42 | tokenizer::tokenize(source, _tokens, errors); 43 | if (_errors->hasErrors()) return nullptr; 44 | 45 | TokenStream stream(source, _tokens, errors); 46 | _stream = &stream; 47 | 48 | Module *module = parseModule(); 49 | if (!module) return nullptr; 50 | 51 | Array *functions = functionArrays.obtain(QAK_SRC_LOC); 52 | Array *statements = statementArrays.obtain(QAK_SRC_LOC); 53 | Array *variables = variableArrays.obtain(QAK_SRC_LOC); 54 | 55 | while (_stream->hasMore()) { 56 | if (_stream->match(QAK_STR("fun"), false)) { 57 | Function *function = parseFunction(); 58 | if (!function) return nullptr; 59 | 60 | functions->add(function); 61 | } else { 62 | Statement *statement = parseStatement(); 63 | if (!statement) return nullptr; 64 | 65 | if (statement->astType == AstVariable) variables->add(static_cast(statement)); 66 | statements->add(statement); 67 | } 68 | } 69 | 70 | module->variables.set(*variables); 71 | module->statements.set(*statements); 72 | module->functions.set(*functions); 73 | 74 | return module; 75 | } 76 | 77 | Module *Parser::parseModule() { 78 | Token *moduleKeyword = _stream->expect(QAK_STR("module")); 79 | if (!moduleKeyword) return nullptr; 80 | 81 | Token *moduleName = _stream->expect(Identifier); 82 | if (!moduleName) return nullptr; 83 | 84 | Module *module = _bumpMem->allocObject(*_bumpMem, *moduleName); 85 | return module; 86 | } 87 | 88 | Function *Parser::parseFunction() { 89 | _stream->expect(QAK_STR("fun")); 90 | 91 | Token *name = _stream->expect(Identifier); 92 | if (!name) return nullptr; 93 | 94 | ArrayPoolMonitor parameterArrays(_parameterArrayPool); 95 | Array *parameters = parameterArrays.obtain(QAK_SRC_LOC); 96 | if (!parseParameters(*parameters)) return nullptr; 97 | 98 | TypeSpecifier *returnType = nullptr; 99 | if (_stream->match(QAK_STR(":"), true)) { 100 | returnType = parseTypeSpecifier(); 101 | if (!returnType) return nullptr; 102 | } 103 | 104 | ArrayPoolMonitor statementArrays(_statementArrayPool); 105 | Array *statements = statementArrays.obtain(QAK_SRC_LOC); 106 | while (_stream->hasMore() && !_stream->match(QAK_STR("end"), false)) { 107 | Statement *statement = parseStatement(); 108 | if (!statement) return nullptr; 109 | statements->add(statement); 110 | } 111 | 112 | if (!_stream->expect(QAK_STR("end"))) return nullptr; 113 | 114 | Function *function = _bumpMem->allocObject(*_bumpMem, *name, *parameters, returnType, *statements); 115 | return function; 116 | } 117 | 118 | bool Parser::parseParameters(Array ¶meters) { 119 | if (!_stream->expect(QAK_STR("("))) return false; 120 | 121 | while (_stream->match(Identifier, false)) { 122 | Parameter *parameter = parseParameter(); 123 | if (!parameter) return false; 124 | 125 | parameters.add(parameter); 126 | 127 | if (!_stream->match(QAK_STR(","), true)) break; 128 | } 129 | 130 | return _stream->expect(QAK_STR(")")); 131 | } 132 | 133 | ast::Parameter *Parser::parseParameter() { 134 | Token *name = _stream->consume(); 135 | if (!_stream->expect(QAK_STR(":"))) return nullptr; 136 | TypeSpecifier *type = parseTypeSpecifier(); 137 | if (!type) return nullptr; 138 | 139 | Parameter *parameter = _bumpMem->allocObject(*name, type); 140 | return parameter; 141 | } 142 | 143 | Statement *Parser::parseStatement() { 144 | if (_stream->match(QAK_STR("var"), false)) { 145 | return parseVariable(); 146 | } else if (_stream->match(QAK_STR("while"), false)) { 147 | return parseWhile(); 148 | } else if (_stream->match(QAK_STR("if"), false)) { 149 | return parseIf(); 150 | } else if (_stream->match(QAK_STR("return"), false)) { 151 | return parseReturn(); 152 | } else { 153 | return parseExpression(); 154 | } 155 | } 156 | 157 | Variable *Parser::parseVariable() { 158 | _stream->expect(QAK_STR("var")); 159 | 160 | Token *name = _stream->expect(Identifier); 161 | if (!name) return nullptr; 162 | 163 | TypeSpecifier *type = nullptr; 164 | if (_stream->match(QAK_STR(":"), true)) { 165 | type = parseTypeSpecifier(); 166 | if (!type) return nullptr; 167 | } 168 | 169 | Expression *expression = nullptr; 170 | if (_stream->match(QAK_STR("="), true)) { 171 | expression = parseExpression(); 172 | if (!expression) return nullptr; 173 | } 174 | 175 | Variable *variable = _bumpMem->allocObject(*name, type, expression); 176 | return variable; 177 | } 178 | 179 | While *Parser::parseWhile() { 180 | Token *whileToken = _stream->expect(QAK_STR("while")); 181 | 182 | Expression *condition = parseExpression(); 183 | if (!condition) return nullptr; 184 | 185 | ArrayPoolMonitor statementArrays(_statementArrayPool); 186 | Array *statements = statementArrays.obtain(QAK_SRC_LOC); 187 | while (_stream->hasMore() && !_stream->match(QAK_STR("end"), false)) { 188 | Statement *statement = parseStatement(); 189 | if (statement == nullptr) return nullptr; 190 | statements->add(statement); 191 | } 192 | 193 | // BOZO expect should also take a custom error string, so we can 194 | // say something like "Expected a closing 'end' for 'while' statement". 195 | // Fix this up anywhere else we use expect() as well. 196 | Token *endToken = _stream->expect(QAK_STR("end")); 197 | if (!endToken) return nullptr; 198 | 199 | While *whileStmt = _bumpMem->allocObject(*_bumpMem, *whileToken, *endToken, condition, *statements); 200 | 201 | return whileStmt; 202 | } 203 | 204 | If *Parser::parseIf() { 205 | Token *ifToken = _stream->expect(QAK_STR("if")); 206 | 207 | Expression *condition = parseExpression(); 208 | if (!condition) return nullptr; 209 | 210 | ArrayPoolMonitor statementArrays(_statementArrayPool); 211 | Array *trueBlock = statementArrays.obtain(QAK_SRC_LOC); 212 | while (_stream->hasMore() && !_stream->match(QAK_STR("end"), false) && !_stream->match(QAK_STR("else"), false)) { 213 | Statement *statement = parseStatement(); 214 | if (statement == nullptr) return nullptr; 215 | trueBlock->add(statement); 216 | } 217 | 218 | Array *falseBlock = statementArrays.obtain(QAK_SRC_LOC); 219 | if (_stream->match(QAK_STR("else"), true)) { 220 | while (_stream->hasMore() && !_stream->match(QAK_STR("end"), false)) { 221 | Statement *statement = parseStatement(); 222 | if (statement == nullptr) return nullptr; 223 | falseBlock->add(statement); 224 | } 225 | } 226 | 227 | Token *endToken = _stream->expect(QAK_STR("end")); 228 | if (!endToken) return nullptr; 229 | 230 | If *ifStmt = _bumpMem->allocObject(*_bumpMem, *ifToken, *endToken, condition, *trueBlock, *falseBlock); 231 | return ifStmt; 232 | } 233 | 234 | Return *Parser::parseReturn() { 235 | Token *returnToken = _stream->expect(QAK_STR("return")); 236 | 237 | if (_stream->match(QAK_STR(";"), true)) { 238 | return _bumpMem->allocObject(*returnToken, *returnToken, nullptr); 239 | } else { 240 | Expression *returnValue = parseExpression(); 241 | if (!returnValue) return nullptr; 242 | return _bumpMem->allocObject(*returnToken, returnValue->span, returnValue); 243 | } 244 | } 245 | 246 | TypeSpecifier *Parser::parseTypeSpecifier() { 247 | Token *name = _stream->expect(Identifier); 248 | if (!name) return nullptr; 249 | 250 | TypeSpecifier *type = _bumpMem->allocObject(*name); 251 | return type; 252 | } 253 | 254 | Expression *Parser::parseExpression() { 255 | return parseTernaryOperator(); 256 | } 257 | 258 | Expression *Parser::parseTernaryOperator() { 259 | Expression *condition = parseBinaryOperator(0); 260 | if (!condition) return nullptr; 261 | 262 | if (_stream->match(QAK_STR("?"), true)) { 263 | Expression *trueValue = parseTernaryOperator(); 264 | if (!trueValue) return nullptr; 265 | if (!_stream->match(QAK_STR(":"), true)) return nullptr; 266 | Expression *falseValue = parseTernaryOperator(); 267 | if (!falseValue) return nullptr; 268 | TernaryOperation *ternary = _bumpMem->allocObject(condition, trueValue, falseValue); 269 | return ternary; 270 | } else { 271 | return condition; 272 | } 273 | } 274 | 275 | #define OPERATOR_END (TokenType)0xffff 276 | #define OPERATOR_NUM_GROUPS 6 277 | 278 | static TokenType binaryOperators[OPERATOR_NUM_GROUPS][5] = { 279 | {Assignment, OPERATOR_END}, 280 | {Or, And, Xor, OPERATOR_END}, 281 | {Equal, NotEqual, OPERATOR_END}, 282 | {Less, LessEqual, Greater, GreaterEqual, OPERATOR_END}, 283 | {Plus, Minus, OPERATOR_END}, 284 | {ForwardSlash, Asterisk, Percentage, OPERATOR_END} 285 | }; 286 | 287 | Expression *Parser::parseBinaryOperator(uint32_t level) { 288 | int nextLevel = level + 1; 289 | 290 | Expression *left = nextLevel == OPERATOR_NUM_GROUPS ? parseUnaryOperator() : parseBinaryOperator(nextLevel); 291 | if (!left) return nullptr; 292 | 293 | while (_stream->hasMore()) { 294 | TokenType *op = binaryOperators[level]; 295 | while (*op != OPERATOR_END) { 296 | if (_stream->match(*op, false)) break; 297 | op++; 298 | } 299 | if (*op == OPERATOR_END) break; 300 | 301 | Token *opToken = _stream->consume(); 302 | Expression *right = nextLevel == OPERATOR_NUM_GROUPS ? parseUnaryOperator() : parseBinaryOperator(nextLevel); 303 | if (right == nullptr) return nullptr; 304 | 305 | left = _bumpMem->allocObject(*opToken, left, right); 306 | } 307 | return left; 308 | } 309 | 310 | static TokenType unaryOperators[] = {Not, Plus, Minus, OPERATOR_END}; 311 | 312 | Expression *Parser::parseUnaryOperator() { 313 | TokenType *op = unaryOperators; 314 | while (*op != OPERATOR_END) { 315 | if (_stream->match(*op, false)) break; 316 | op++; 317 | } 318 | if (*op != OPERATOR_END) { 319 | Token *op = _stream->consume(); 320 | Expression *expression = parseUnaryOperator(); 321 | if (!expression) return nullptr; 322 | UnaryOperation *operation = _bumpMem->allocObject(*op, expression); 323 | return operation; 324 | } else { 325 | if (_stream->match(QAK_STR("("), true)) { 326 | Expression *expression = parseExpression(); 327 | if (!expression) return nullptr; 328 | if (!_stream->expect(QAK_STR(")"))) return nullptr; 329 | return expression; 330 | } else { 331 | return parseAccessOrCallOrLiteral(); 332 | } 333 | } 334 | return nullptr; 335 | } 336 | 337 | Expression *Parser::parseAccessOrCallOrLiteral() { 338 | if (!_stream->hasMore()) { 339 | if (_stream->getTokens().size() > 0) { 340 | Token token = _stream->getTokens()[_stream->getTokens().size() - 1]; 341 | _errors->add(token, "Expected a variable, field, array, function call, method call, or literal."); 342 | } else { 343 | _errors->add(Span(*_source, 0, 0, 0, 0), "Expected a variable, field, array, function call, method call, or literal."); 344 | } 345 | return nullptr; 346 | } 347 | 348 | TokenType tokenType = _stream->peek()->type; 349 | 350 | switch (tokenType) { 351 | case StringLiteral: 352 | case BooleanLiteral: 353 | case DoubleLiteral: 354 | case FloatLiteral: 355 | case ByteLiteral: 356 | case ShortLiteral: 357 | case IntegerLiteral: 358 | case LongLiteral: 359 | case CharacterLiteral: 360 | case NothingLiteral: { 361 | Token *token = _stream->consume(); 362 | Literal *literal = _bumpMem->allocObject(token->type, *token); 363 | return literal; 364 | } 365 | 366 | case Identifier: 367 | return parseAccessOrCall(); 368 | 369 | default: 370 | _errors->add(*_stream->peek(), "Expected a variable, field, array, function call, method call, or literal."); 371 | return nullptr; 372 | } 373 | } 374 | 375 | Expression *Parser::parseAccessOrCall() { 376 | Token *name = _stream->expect(Identifier); 377 | if (!name) return nullptr; 378 | 379 | Expression *result = _bumpMem->allocObject(*name); 380 | 381 | // If the next token is "(", we have a function call. 382 | if (_stream->match(QAK_STR("("), true)) { 383 | ArrayPoolMonitor expressionArrays(_expressionArrayPool); 384 | Array *arguments = parseArguments(expressionArrays.obtain(QAK_SRC_LOC)); 385 | if (!arguments) return nullptr; 386 | 387 | Token *closingParan = _stream->expect(QAK_STR(")")); 388 | if (!closingParan) return nullptr; 389 | 390 | result = _bumpMem->allocObject(*_bumpMem, *name, *closingParan, result, *arguments); 391 | } 392 | return result; 393 | } 394 | 395 | 396 | Array *Parser::parseArguments(Array *arguments) { 397 | while (_stream->hasMore() && !_stream->match(QAK_STR(")"), false)) { 398 | Expression *argument = parseExpression(); 399 | if (!argument) return nullptr; 400 | arguments->add(argument); 401 | 402 | if (!_stream->match(QAK_STR(")"), false)) { 403 | if (!_stream->hasMore()) { 404 | Token token = _stream->getTokens()[_stream->getTokens().size() - 1]; 405 | _errors->add(token, "Expected ) or , but reached end of file."); 406 | return nullptr; 407 | } 408 | if (!_stream->expect(QAK_STR(","))) return nullptr; 409 | } 410 | } 411 | 412 | return arguments; 413 | } 414 | 415 | Array &Parser::tokens() { 416 | return _tokens; 417 | } 418 | 419 | static void printIndent(int indent) { 420 | printf("%*s", indent, ""); 421 | } 422 | 423 | static void printAstNodeRecursive(ast::AstNode *node, int indent, HeapAllocator &mem) { 424 | switch (node->astType) { 425 | case AstTypeSpecifier: { 426 | TypeSpecifier *n = (TypeSpecifier *) (node); 427 | printIndent(indent); 428 | printf("type: %s\n", n->name.toCString(mem)); 429 | break; 430 | } 431 | case AstParameter: { 432 | Parameter *n = (Parameter *) (node); 433 | printIndent(indent); 434 | printf("Parameter: %s\n", n->name.toCString(mem)); 435 | printAstNodeRecursive(n->typeSpecifier, indent + QAK_AST_INDENT, mem); 436 | break; 437 | } 438 | case AstFunction: { 439 | Function *n = (Function *) (node); 440 | printIndent(indent); 441 | printf("Function: %s\n", n->name.toCString(mem)); 442 | if (n->parameters.size() > 0) { 443 | printIndent(indent + QAK_AST_INDENT); 444 | printf("Parameters:\n"); 445 | for (size_t i = 0; i < n->parameters.size(); i++) 446 | printAstNodeRecursive(n->parameters[i], indent + QAK_AST_INDENT * 2, mem); 447 | } 448 | if (n->returnType) { 449 | printIndent(indent + QAK_AST_INDENT); 450 | printf("Return type:\n"); 451 | printAstNodeRecursive(n->returnType, indent + QAK_AST_INDENT * 2, mem); 452 | } 453 | 454 | if (n->statements.size() > 0) { 455 | printIndent(indent + QAK_AST_INDENT); 456 | printf("Statements:\n"); 457 | for (size_t i = 0; i < n->statements.size(); i++) { 458 | printAstNodeRecursive(n->statements[i], indent + QAK_AST_INDENT * 2, mem); 459 | } 460 | } 461 | break; 462 | } 463 | case AstVariable: { 464 | Variable *n = (Variable *) (node); 465 | printIndent(indent); 466 | printf("Variable: %s\n", n->name.toCString(mem)); 467 | if (n->typeSpecifier) printAstNodeRecursive(n->typeSpecifier, indent + QAK_AST_INDENT, mem); 468 | if (n->initializerExpression) { 469 | printIndent(indent + QAK_AST_INDENT); 470 | printf("Initializer: \n"); 471 | printAstNodeRecursive(n->initializerExpression, indent + QAK_AST_INDENT * 2, mem); 472 | } 473 | break; 474 | } 475 | case AstWhile: { 476 | While *n = (While *) (node); 477 | printIndent(indent); 478 | printf("While\n"); 479 | 480 | printIndent(indent + QAK_AST_INDENT); 481 | printf("Condition: \n"); 482 | printAstNodeRecursive(n->condition, indent + QAK_AST_INDENT * 2, mem); 483 | 484 | if (n->statements.size() > 0) { 485 | printIndent(indent + QAK_AST_INDENT); 486 | printf("Statements: \n"); 487 | for (size_t i = 0; i < n->statements.size(); i++) { 488 | printAstNodeRecursive(n->statements[i], indent + QAK_AST_INDENT * 2, mem); 489 | } 490 | } 491 | break; 492 | } 493 | case AstIf: { 494 | If *n = (If *) (node); 495 | printIndent(indent); 496 | printf("If\n"); 497 | 498 | printIndent(indent + QAK_AST_INDENT); 499 | printf("Condition: \n"); 500 | printAstNodeRecursive(n->condition, indent + QAK_AST_INDENT * 2, mem); 501 | 502 | if (n->trueBlock.size() > 0) { 503 | printIndent(indent + QAK_AST_INDENT); 504 | printf("True-block statements: \n"); 505 | for (size_t i = 0; i < n->trueBlock.size(); i++) { 506 | printAstNodeRecursive(n->trueBlock[i], indent + QAK_AST_INDENT * 2, mem); 507 | } 508 | } 509 | 510 | if (n->falseBlock.size() > 0) { 511 | printIndent(indent + QAK_AST_INDENT); 512 | printf("False-block statements: \n"); 513 | for (size_t i = 0; i < n->falseBlock.size(); i++) { 514 | printAstNodeRecursive(n->falseBlock[i], indent + QAK_AST_INDENT * 2, mem); 515 | } 516 | } 517 | break; 518 | } 519 | case AstReturn: { 520 | Return *n = (Return *) node; 521 | printIndent(indent); 522 | printf("Return:\n"); 523 | 524 | if (n->returnValue) { 525 | printIndent(indent + QAK_AST_INDENT); 526 | printf("Value:\n"); 527 | printAstNodeRecursive(n->returnValue, indent + QAK_AST_INDENT * 2, mem); 528 | } 529 | 530 | break; 531 | } 532 | case AstTernaryOperation: { 533 | TernaryOperation *n = (TernaryOperation *) node; 534 | printIndent(indent); 535 | printf("Ternary operator:\n"); 536 | printAstNodeRecursive(n->condition, indent + QAK_AST_INDENT, mem); 537 | printAstNodeRecursive(n->trueValue, indent + QAK_AST_INDENT, mem); 538 | printAstNodeRecursive(n->falseValue, indent + QAK_AST_INDENT, mem); 539 | break; 540 | } 541 | case AstBinaryOperation: { 542 | BinaryOperation *n = (BinaryOperation *) node; 543 | printIndent(indent); 544 | printf("Binary operator: %s\n", n->op.toCString(mem)); 545 | printAstNodeRecursive(n->left, indent + QAK_AST_INDENT, mem); 546 | printAstNodeRecursive(n->right, indent + QAK_AST_INDENT, mem); 547 | break; 548 | } 549 | case AstUnaryOperation: { 550 | UnaryOperation *n = (UnaryOperation *) node; 551 | printIndent(indent); 552 | printf("Unary op: %s\n", n->op.toCString(mem)); 553 | printAstNodeRecursive(n->value, indent + QAK_AST_INDENT, mem); 554 | break; 555 | } 556 | case AstLiteral: { 557 | Literal *n = (Literal *) node; 558 | printIndent(indent); 559 | printf("%s: %s\n", tokenizer::tokenTypeToString(n->type), n->value.toCString(mem)); 560 | break; 561 | } 562 | case AstVariableAccess: { 563 | VariableAccess *n = (VariableAccess *) node; 564 | printIndent(indent); 565 | printf("Variable access: %s\n", n->name.toCString(mem)); 566 | break; 567 | } 568 | case AstFunctionCall: { 569 | FunctionCall *n = (FunctionCall *) node; 570 | printIndent(indent); 571 | printf("Function call: %s(%s)\n", n->variableAccess->span.toCString(mem), n->arguments.size() > 0 ? "..." : ""); 572 | 573 | if (n->arguments.size() > 0) { 574 | printIndent(indent + QAK_AST_INDENT); 575 | printf("Arguments:\n"); 576 | for (size_t i = 0; i < n->arguments.size(); i++) { 577 | printAstNodeRecursive(n->arguments[i], indent + QAK_AST_INDENT * 2, mem); 578 | } 579 | } 580 | break; 581 | } 582 | case AstModule: { 583 | Module *n = (Module *) node; 584 | printIndent(indent); 585 | printf("Module: %s\n", n->name.toCString(mem)); 586 | 587 | if (n->statements.size() > 0) { 588 | printIndent(indent + QAK_AST_INDENT); 589 | printf("Module statements:\n"); 590 | for (size_t i = 0; i < n->statements.size(); i++) { 591 | printAstNodeRecursive(n->statements[i], indent + QAK_AST_INDENT * 2, mem); 592 | } 593 | } 594 | 595 | for (size_t i = 0; i < n->functions.size(); i++) { 596 | printAstNodeRecursive(n->functions[i], indent + QAK_AST_INDENT, mem); 597 | } 598 | break; 599 | } 600 | } 601 | } 602 | 603 | void parser::printAstNode(ast::AstNode *node, HeapAllocator &mem) { 604 | printAstNodeRecursive(node, 0, mem); 605 | } -------------------------------------------------------------------------------- /src/parser.h: -------------------------------------------------------------------------------- 1 | #ifndef QAK_PARSER_H 2 | #define QAK_PARSER_H 3 | 4 | #include "tokenizer.h" 5 | 6 | namespace qak { 7 | namespace ast { 8 | #define QAK_AST_INDENT 3 9 | 10 | enum AstType { 11 | AstTypeSpecifier, 12 | AstParameter, 13 | AstFunction, 14 | AstTernaryOperation, 15 | AstBinaryOperation, 16 | AstUnaryOperation, 17 | AstLiteral, 18 | AstVariableAccess, 19 | AstFunctionCall, 20 | AstVariable, 21 | AstWhile, 22 | AstIf, 23 | AstReturn, 24 | AstModule 25 | }; 26 | 27 | struct AstNode { 28 | AstType astType; 29 | Span span; 30 | 31 | AstNode(AstType astType, Span &start, Span &end) : 32 | astType(astType), 33 | span(start.source, start.start, start.startLine, end.end, end.endLine) {} 34 | 35 | AstNode(AstType astType, Span &span) : astType(astType), span(span) {} 36 | }; 37 | 38 | struct TypeSpecifier : public AstNode { 39 | Span name; 40 | 41 | TypeSpecifier(Span name) : 42 | AstNode(AstTypeSpecifier, name), 43 | name(name) {} 44 | }; 45 | 46 | struct Statement : public AstNode { 47 | Statement(AstType astType, Span start, Span end) : AstNode(astType, start, end) {} 48 | }; 49 | 50 | struct Parameter : public AstNode { 51 | Span name; 52 | TypeSpecifier *typeSpecifier; 53 | 54 | Parameter(Span name, TypeSpecifier *typeSpecifier) : 55 | AstNode(AstParameter, name, typeSpecifier->span), 56 | name(name), 57 | typeSpecifier(typeSpecifier) {} 58 | }; 59 | 60 | struct Function : public AstNode { 61 | Span name; 62 | FixedArray parameters; 63 | TypeSpecifier *returnType; 64 | FixedArray statements; 65 | 66 | Function(BumpAllocator &bumpMem, Span name, Array ¶meters, TypeSpecifier *returnType, 67 | Array &statements) : 68 | AstNode(AstFunction, name, name), 69 | name(name), 70 | parameters(bumpMem, parameters), 71 | returnType(returnType), 72 | statements(bumpMem, statements) {} 73 | }; 74 | 75 | struct Expression : public Statement { 76 | Expression(AstType astType, Span start, Span end) : Statement(astType, start, end) {} 77 | }; 78 | 79 | struct Variable : public Statement { 80 | Span name; 81 | TypeSpecifier *typeSpecifier; 82 | Expression *initializerExpression; 83 | 84 | Variable(Span name, TypeSpecifier *type, Expression *expression) : 85 | Statement(AstVariable, name, name), 86 | name(name), 87 | typeSpecifier(type), 88 | initializerExpression(expression) {} 89 | }; 90 | 91 | struct While : public Statement { 92 | Expression *condition; 93 | FixedArray statements; 94 | 95 | While(BumpAllocator &bumpMem, Span start, Span end, Expression *condition, Array &statements) : 96 | Statement(AstWhile, start, end), 97 | condition(condition), 98 | statements(bumpMem, statements) {} 99 | }; 100 | 101 | struct If : public Statement { 102 | Expression *condition; 103 | FixedArray trueBlock; 104 | FixedArray falseBlock; 105 | 106 | If(BumpAllocator &bumpMem, Span start, Span end, Expression *condition, Array &trueBlock, 107 | Array &falseBlock) : 108 | Statement(AstIf, start, end), 109 | condition(condition), 110 | trueBlock(bumpMem, trueBlock), 111 | falseBlock(bumpMem, falseBlock) {} 112 | }; 113 | 114 | struct Return : public Statement { 115 | Expression *returnValue; 116 | 117 | Return(Span start, Span end, Expression *returnValue) : 118 | Statement(AstReturn, start, end), 119 | returnValue(returnValue) {} 120 | }; 121 | 122 | struct TernaryOperation : public Expression { 123 | Expression *condition; 124 | Expression *trueValue; 125 | Expression *falseValue; 126 | 127 | TernaryOperation(Expression *condition, Expression *trueValue, Expression *falseValue) : 128 | Expression(AstTernaryOperation, condition->span, falseValue->span), 129 | condition(condition), 130 | trueValue(trueValue), 131 | falseValue(falseValue) {} 132 | }; 133 | 134 | struct BinaryOperation : public Expression { 135 | Span op; 136 | Expression *left; 137 | Expression *right; 138 | 139 | BinaryOperation(Span op, Expression *left, Expression *right) : 140 | Expression(AstBinaryOperation, left->span, right->span), 141 | op(op), left(left), 142 | right(right) {} 143 | }; 144 | 145 | struct UnaryOperation : public Expression { 146 | Span op; 147 | Expression *value; 148 | 149 | UnaryOperation(Span op, Expression *value) : 150 | Expression(AstUnaryOperation, op, op), 151 | op(op), value(value) {} 152 | }; 153 | 154 | struct Literal : public Expression { 155 | TokenType type; 156 | Span value; 157 | 158 | Literal(TokenType type, Span value) : 159 | Expression(AstLiteral, value, value), 160 | type(type), 161 | value(value) {} 162 | }; 163 | 164 | struct VariableAccess : public Expression { 165 | Span name; 166 | 167 | VariableAccess(Span name) : 168 | Expression(AstVariableAccess, name, name), 169 | name(name) {} 170 | }; 171 | 172 | struct FunctionCall : public Expression { 173 | Expression *variableAccess; 174 | FixedArray arguments; 175 | 176 | FunctionCall(BumpAllocator &mem, Span start, Span end, Expression *variableAccess, Array &arguments) : 177 | Expression(AstFunctionCall, start, end), 178 | variableAccess(variableAccess), 179 | arguments(mem, arguments) {} 180 | }; 181 | 182 | struct Module : public AstNode { 183 | BumpAllocator &mem; 184 | Span name; 185 | FixedArray variables; 186 | FixedArray functions; 187 | FixedArray statements; 188 | 189 | Module(BumpAllocator &mem, Span name) : 190 | AstNode(AstModule, name), 191 | mem(mem), 192 | name(name), 193 | variables(mem), 194 | functions(mem), 195 | statements(mem) { 196 | } 197 | }; 198 | } 199 | 200 | class Parser { 201 | private: 202 | Array _tokens; 203 | ArrayPool _variableArrayPool; 204 | ArrayPool _statementArrayPool; 205 | ArrayPool _functionArrayPool; 206 | ArrayPool _parameterArrayPool; 207 | ArrayPool _expressionArrayPool; 208 | 209 | // Set on each call to parse. 210 | Source *_source; 211 | TokenStream *_stream; 212 | Errors *_errors; 213 | BumpAllocator *_bumpMem; 214 | 215 | ast::Module *parseModule(); 216 | 217 | ast::Function *parseFunction(); 218 | 219 | bool parseParameters(Array ¶meters); 220 | 221 | ast::Parameter *parseParameter(); 222 | 223 | ast::Statement *parseStatement(); 224 | 225 | ast::Variable *parseVariable(); 226 | 227 | ast::While *parseWhile(); 228 | 229 | ast::If *parseIf(); 230 | 231 | ast::Return *parseReturn(); 232 | 233 | ast::TypeSpecifier *parseTypeSpecifier(); 234 | 235 | ast::Expression *parseExpression(); 236 | 237 | ast::Expression *parseTernaryOperator(); 238 | 239 | ast::Expression *parseBinaryOperator(uint32_t level); 240 | 241 | ast::Expression *parseUnaryOperator(); 242 | 243 | ast::Expression *parseAccessOrCallOrLiteral(); 244 | 245 | ast::Expression *parseAccessOrCall(); 246 | 247 | Array *parseArguments(Array *arguments); 248 | 249 | public: 250 | Parser(HeapAllocator &mem) : 251 | _tokens(mem), 252 | _variableArrayPool(mem), 253 | _statementArrayPool(mem), 254 | _functionArrayPool(mem), 255 | _parameterArrayPool(mem), 256 | _expressionArrayPool(mem), 257 | _source(nullptr), 258 | _stream(nullptr), 259 | _errors(nullptr), 260 | _bumpMem(nullptr) {} 261 | 262 | ast::Module *parse(Source &source, Errors &errors, BumpAllocator *bumpMem); 263 | 264 | Array &tokens(); 265 | }; 266 | 267 | namespace parser { 268 | void printAstNode(ast::AstNode *node, HeapAllocator &mem); 269 | } 270 | } 271 | 272 | 273 | #endif //QAK_PARSER_H 274 | -------------------------------------------------------------------------------- /src/qak.cpp: -------------------------------------------------------------------------------- 1 | #include "qak.h" 2 | #include "io.h" 3 | #include "parser.h" 4 | 5 | #ifdef WASM 6 | #include 7 | #else 8 | #define EMSCRIPTEN_KEEPALIVE 9 | #endif 10 | 11 | using namespace qak; 12 | 13 | static void spanToQakSpan(Span &span, qak_span &qakSpan) { 14 | qakSpan.data.data = (const char *) span.source.data + span.start; 15 | qakSpan.data.length = span.end - span.start; 16 | qakSpan.start = span.start; 17 | qakSpan.end = span.end; 18 | qakSpan.startLine = span.startLine; 19 | qakSpan.endLine = span.endLine; 20 | } 21 | 22 | /** Keeps track of global memory allocated for Sources and Modules via a HeapAllocator **/ 23 | struct Compiler { 24 | HeapAllocator *mem; 25 | 26 | Compiler(HeapAllocator *mem) : mem(mem) {}; 27 | }; 28 | 29 | /** Keeps track of results from all compilation stages for a module. The module itself 30 | * is memory managed by a Compiler. The data held by the module is memory managed by 31 | * a BumpAllocator owned by the module. The module has ownership of the Source it was 32 | * compiled from and frees it upon desctruction. */ 33 | struct Module { 34 | HeapAllocator &mem; 35 | BumpAllocator *bumpMem; 36 | Source *source; 37 | Array tokens; 38 | ast::Module *astModule; 39 | Array *astNodes; 40 | Errors errors; 41 | 42 | Module(HeapAllocator &mem, BumpAllocator *bumpMem, Source *source, Array &tokens, ast::Module *astModule, Errors &errors) : 43 | mem(mem), bumpMem(bumpMem), 44 | source(source), 45 | tokens(mem), 46 | astModule(astModule), 47 | astNodes(nullptr), 48 | errors(mem, *bumpMem) { 49 | this->tokens.addAll(tokens); 50 | this->errors.addAll(errors); 51 | }; 52 | 53 | ~Module() { 54 | // Free all objects allocated through the bump allocator such 55 | // as the AST. 56 | if (bumpMem) mem.freeObject(bumpMem, QAK_SRC_LOC); 57 | 58 | if (astNodes) mem.freeObject(astNodes, QAK_SRC_LOC); 59 | 60 | // Free all objects allocated through the heap allocator such 61 | // as the source. 62 | mem.freeObject(source, QAK_SRC_LOC); 63 | } 64 | 65 | qak_ast_node_index linearizeAst() { 66 | if (astNodes == nullptr) { 67 | astNodes = mem.allocObject>(QAK_SRC_LOC, mem); 68 | return linearizeAst(*astNodes, astModule); 69 | } else { 70 | return ((qak_ast_node_index)astNodes->size()) - 1; 71 | } 72 | } 73 | 74 | template 75 | void fixedAstNodeArrayToQakAstNodeList(Array &nodes, FixedArray &array, qak_ast_node_list &list) { 76 | list.numNodes = (qak_ast_node_index)array.size(); 77 | if (list.numNodes > 0) { 78 | list.nodes = bumpMem->alloc(list.numNodes); 79 | for (size_t i = 0; i < list.numNodes; i++) { 80 | list.nodes[i] = linearizeAst(nodes, array[i]); 81 | } 82 | } 83 | } 84 | 85 | qak_ast_node_index linearizeAst(Array &nodes, qak::ast::AstNode *node) { 86 | if (node == nullptr) return -1; 87 | 88 | qak_ast_node astNode; 89 | astNode.type = (qak_ast_type) node->astType; 90 | spanToQakSpan(node->span, astNode.span); 91 | 92 | switch (node->astType) { 93 | case qak::ast::AstTypeSpecifier: { 94 | qak::ast::TypeSpecifier *n = (qak::ast::TypeSpecifier *) (node); 95 | spanToQakSpan(n->name, astNode.data.typeSpecifier.name); 96 | break; 97 | } 98 | case qak::ast::AstParameter: { 99 | qak::ast::Parameter *n = (qak::ast::Parameter *) (node); 100 | spanToQakSpan(n->name, astNode.data.parameter.name); 101 | astNode.data.parameter.typeSpecifier = linearizeAst(nodes, n->typeSpecifier); 102 | break; 103 | } 104 | case qak::ast::AstFunction: { 105 | qak::ast::Function *n = (qak::ast::Function *) (node); 106 | spanToQakSpan(n->name, astNode.data.function.name); 107 | fixedAstNodeArrayToQakAstNodeList(nodes, n->parameters, astNode.data.function.parameters); 108 | astNode.data.function.returnType = linearizeAst(nodes, n->returnType); 109 | fixedAstNodeArrayToQakAstNodeList(nodes, n->statements, astNode.data.function.statements); 110 | break; 111 | } 112 | case qak::ast::AstTernaryOperation: { 113 | qak::ast::TernaryOperation *n = (qak::ast::TernaryOperation *) (node); 114 | astNode.data.ternaryOperation.condition = linearizeAst(nodes, n->condition); 115 | astNode.data.ternaryOperation.trueValue = linearizeAst(nodes, n->trueValue); 116 | astNode.data.ternaryOperation.falseValue = linearizeAst(nodes, n->falseValue); 117 | break; 118 | } 119 | case qak::ast::AstBinaryOperation: { 120 | qak::ast::BinaryOperation *n = (qak::ast::BinaryOperation *) (node); 121 | spanToQakSpan(n->op, astNode.data.binaryOperation.op); 122 | astNode.data.binaryOperation.left = linearizeAst(nodes, n->left); 123 | astNode.data.binaryOperation.right = linearizeAst(nodes, n->right); 124 | break; 125 | } 126 | case qak::ast::AstUnaryOperation: { 127 | qak::ast::UnaryOperation *n = (qak::ast::UnaryOperation *) (node); 128 | spanToQakSpan(n->op, astNode.data.unaryOperation.op); 129 | astNode.data.unaryOperation.value = linearizeAst(nodes, n->value); 130 | break; 131 | } 132 | case qak::ast::AstLiteral: { 133 | qak::ast::Literal *n = (qak::ast::Literal *) (node); 134 | astNode.data.literal.type = (qak_token_type) n->type; 135 | spanToQakSpan(n->value, astNode.data.literal.value); 136 | break; 137 | } 138 | case qak::ast::AstVariableAccess: { 139 | qak::ast::VariableAccess *n = (qak::ast::VariableAccess *) (node); 140 | spanToQakSpan(n->name, astNode.data.variableAccess.name); 141 | break; 142 | } 143 | case qak::ast::AstFunctionCall: { 144 | qak::ast::FunctionCall *n = (qak::ast::FunctionCall *) (node); 145 | astNode.data.functionCall.variableAccess = linearizeAst(nodes, n->variableAccess); 146 | fixedAstNodeArrayToQakAstNodeList(nodes, n->arguments, astNode.data.functionCall.arguments); 147 | break; 148 | } 149 | case qak::ast::AstVariable: { 150 | qak::ast::Variable *n = (qak::ast::Variable *) (node); 151 | spanToQakSpan(n->name, astNode.data.variable.name); 152 | astNode.data.variable.typeSpecifier = linearizeAst(nodes, n->typeSpecifier); 153 | astNode.data.variable.initializerExpression = linearizeAst(nodes, n->initializerExpression); 154 | break; 155 | } 156 | case qak::ast::AstWhile: { 157 | qak::ast::While *n = (qak::ast::While *) (node); 158 | astNode.data.whileNode.condition = linearizeAst(nodes, n->condition); 159 | fixedAstNodeArrayToQakAstNodeList(nodes, n->statements, astNode.data.whileNode.statements); 160 | break; 161 | } 162 | case qak::ast::AstIf: { 163 | qak::ast::If *n = (qak::ast::If *) (node); 164 | astNode.data.ifNode.condition = linearizeAst(nodes, n->condition); 165 | fixedAstNodeArrayToQakAstNodeList(nodes, n->trueBlock, astNode.data.ifNode.trueBlock); 166 | fixedAstNodeArrayToQakAstNodeList(nodes, n->falseBlock, astNode.data.ifNode.falseBlock); 167 | break; 168 | } 169 | case qak::ast::AstReturn: { 170 | qak::ast::Return *n = (qak::ast::Return *) (node); 171 | astNode.data.returnNode.returnValue = linearizeAst(nodes, n->returnValue); 172 | break; 173 | } 174 | case qak::ast::AstModule: { 175 | qak::ast::Module *n = (qak::ast::Module *) (node); 176 | spanToQakSpan(n->name, astNode.data.module.name); 177 | fixedAstNodeArrayToQakAstNodeList(nodes, n->variables, astNode.data.module.variables); 178 | fixedAstNodeArrayToQakAstNodeList(nodes, n->functions, astNode.data.module.functions); 179 | fixedAstNodeArrayToQakAstNodeList(nodes, n->statements, astNode.data.module.statements); 180 | break; 181 | } 182 | } 183 | 184 | nodes.add(astNode); 185 | return ((qak_ast_node_index)nodes.size()) - 1; 186 | } 187 | }; 188 | 189 | EMSCRIPTEN_KEEPALIVE qak_compiler qak_compiler_new() { 190 | HeapAllocator *mem = new HeapAllocator(); 191 | Compiler *compiler = mem->allocObject(QAK_SRC_LOC, mem); 192 | return (qak_compiler) compiler; 193 | } 194 | 195 | EMSCRIPTEN_KEEPALIVE void qak_compiler_delete(qak_compiler compilerHandle) { 196 | Compiler *compiler = (Compiler *) compilerHandle; 197 | HeapAllocator *mem = compiler->mem; 198 | mem->freeObject(compiler, QAK_SRC_LOC); 199 | mem->printAllocations(); 200 | delete mem; 201 | } 202 | 203 | EMSCRIPTEN_KEEPALIVE void qak_compiler_print_memory_usage(qak_compiler compilerHandle) { 204 | Compiler *compiler = (Compiler *) compilerHandle; 205 | compiler->mem->printAllocations(); 206 | } 207 | 208 | qak_module qak_compile(Compiler *compiler, Source *source) { 209 | BumpAllocator *bumpMem = compiler->mem->allocObject(QAK_SRC_LOC, *compiler->mem); 210 | Array tokens(*compiler->mem); 211 | Errors errors(*compiler->mem, *bumpMem); 212 | 213 | qak::tokenizer::tokenize(*source, tokens, errors); 214 | if (errors.hasErrors()) { 215 | return (qak_module) compiler->mem->allocObject(QAK_SRC_LOC, *compiler->mem, bumpMem, source, tokens, nullptr, errors);; 216 | } 217 | 218 | qak::Parser parser(*compiler->mem); 219 | ast::Module *astModule = parser.parse(*source, errors, bumpMem); 220 | if (astModule == nullptr) { 221 | return (qak_module) compiler->mem->allocObject(QAK_SRC_LOC, *compiler->mem, bumpMem, source, tokens, nullptr, errors);; 222 | } 223 | 224 | return (qak_module) compiler->mem->allocObject(QAK_SRC_LOC, *compiler->mem, bumpMem, source, tokens, astModule, errors); 225 | } 226 | 227 | EMSCRIPTEN_KEEPALIVE qak_module qak_compiler_compile_file(qak_compiler compilerHandle, const char *fileName) { 228 | Compiler *compiler = (Compiler *) compilerHandle; 229 | Source *source = io::readFile(fileName, *compiler->mem); 230 | if (source == nullptr) return nullptr; 231 | return qak_compile(compiler, source); 232 | } 233 | 234 | EMSCRIPTEN_KEEPALIVE qak_module qak_compiler_compile_source(qak_compiler compilerHandle, const char *fileName, const char *sourceData) { 235 | Compiler *compiler = (Compiler *) compilerHandle; 236 | HeapAllocator &mem = *compiler->mem; 237 | 238 | Source *source = Source::fromMemory(mem, fileName, sourceData); 239 | if (source == nullptr) return nullptr; 240 | 241 | return qak_compile(compiler, source); 242 | } 243 | 244 | EMSCRIPTEN_KEEPALIVE void qak_module_delete(qak_module moduleHandle) { 245 | Module *module = (Module *) moduleHandle; 246 | HeapAllocator &mem = module->mem; 247 | mem.freeObject(module, QAK_SRC_LOC); 248 | } 249 | 250 | EMSCRIPTEN_KEEPALIVE void qak_module_get_source(qak_module moduleHandle, qak_source *source) { 251 | Module *module = (Module *) moduleHandle; 252 | source->fileName.data = module->source->fileName; 253 | source->fileName.length = strlen(module->source->fileName); 254 | source->data.data = (const char *) module->source->data; 255 | source->data.length = module->source->size; 256 | } 257 | 258 | EMSCRIPTEN_KEEPALIVE int qak_module_get_num_errors(qak_module moduleHandle) { 259 | Module *module = (Module *) moduleHandle; 260 | return (int) module->errors.getErrors().size(); 261 | } 262 | 263 | EMSCRIPTEN_KEEPALIVE void qak_module_get_error(qak_module moduleHandle, int errorIndex, qak_error *errorResult) { 264 | Module *module = (Module *) moduleHandle; 265 | Error &error = module->errors.getErrors()[errorIndex]; 266 | Span &span = error.span; 267 | 268 | errorResult->errorMessage.data = error.message; 269 | errorResult->errorMessage.length = strlen(error.message); 270 | spanToQakSpan(span, errorResult->span); 271 | } 272 | 273 | EMSCRIPTEN_KEEPALIVE int qak_module_get_num_tokens(qak_module moduleHandle) { 274 | Module *module = (Module *) moduleHandle; 275 | return (int) module->tokens.size(); 276 | } 277 | 278 | EMSCRIPTEN_KEEPALIVE void qak_module_get_token(qak_module moduleHandle, int tokenIndex, qak_token *tokenResult) { 279 | Module *module = (Module *) moduleHandle; 280 | Token &token = module->tokens[tokenIndex]; 281 | 282 | tokenResult->type = (qak_token_type) token.type; 283 | spanToQakSpan(token, tokenResult->span); 284 | } 285 | 286 | EMSCRIPTEN_KEEPALIVE void qak_module_print_errors(qak_module moduleHandle) { 287 | Module *module = (Module *) moduleHandle; 288 | module->errors.print(); 289 | } 290 | 291 | EMSCRIPTEN_KEEPALIVE void qak_module_print_tokens(qak_module moduleHandle) { 292 | Module *module = (Module *) moduleHandle; 293 | HeapAllocator mem; 294 | tokenizer::printTokens(module->tokens, mem); 295 | } 296 | 297 | EMSCRIPTEN_KEEPALIVE qak_ast_module *qak_module_get_ast(qak_module moduleHandle) { 298 | Module *module = (Module *) moduleHandle; 299 | qak_ast_node_index moduleIndex = module->linearizeAst(); 300 | if (moduleIndex >= 0) return &module->astNodes->buffer()[moduleIndex].data.module; 301 | else return nullptr; 302 | } 303 | 304 | EMSCRIPTEN_KEEPALIVE qak_ast_node *qak_module_get_ast_node(qak_module moduleHandle, qak_ast_node_index nodeIndex) { 305 | Module *module = (Module *) moduleHandle; 306 | if (nodeIndex < 0) return nullptr; 307 | module->linearizeAst(); 308 | return &module->astNodes->buffer()[nodeIndex]; 309 | } 310 | 311 | EMSCRIPTEN_KEEPALIVE void qak_module_print_ast(qak_module moduleHandle) { 312 | Module *module = (Module *) moduleHandle; 313 | if (module->astModule) { 314 | HeapAllocator mem; 315 | parser::printAstNode(module->astModule, mem); 316 | } 317 | } 318 | 319 | #ifdef WASM 320 | EMSCRIPTEN_KEEPALIVE int main(int argc, char** argv) { 321 | return 0; 322 | } 323 | 324 | #define member_size(type, member) sizeof(((type *)0)->member) 325 | 326 | EMSCRIPTEN_KEEPALIVE void qak_print_struct_offsets () { 327 | printf("qak_string (size=%lu)\n", sizeof(qak_string)); 328 | printf(" data: %lu\n", offsetof(qak_string, data)); 329 | printf(" length: %lu\n", offsetof(qak_string, length)); 330 | 331 | printf("qak_source (size=%lu)\n", sizeof(qak_source)); 332 | printf(" data: %lu\n", offsetof(qak_source, data)); 333 | printf(" fileName: %lu\n", offsetof(qak_source, fileName)); 334 | 335 | printf("qak_span (size=%lu)\n", sizeof(qak_span)); 336 | printf(" data: %lu\n", offsetof(qak_span, data)); 337 | printf(" start: %lu\n", offsetof(qak_span, start)); 338 | printf(" end: %lu\n", offsetof(qak_span, end)); 339 | printf(" startLine: %lu\n", offsetof(qak_span, startLine)); 340 | printf(" endLine: %lu\n", offsetof(qak_span, endLine)); 341 | 342 | printf("qak_line (size=%lu)\n", sizeof(qak_line)); 343 | printf(" source: %lu\n", offsetof(qak_line, source)); 344 | printf(" data: %lu\n", offsetof(qak_line, data)); 345 | printf(" lineNumber: %lu\n", offsetof(qak_line, lineNumber)); 346 | 347 | printf("qak_token (size=%lu)\n", sizeof(qak_token)); 348 | printf(" type(=%lu): %lu\n", member_size(qak_token, type), offsetof(qak_token, type)); 349 | printf(" span: %lu\n", offsetof(qak_token, span)); 350 | 351 | printf("qak_error (size=%lu)\n", sizeof(qak_error)); 352 | printf(" errorMessage: %lu\n", offsetof(qak_error, errorMessage)); 353 | printf(" span: %lu\n", offsetof(qak_error, span)); 354 | } 355 | #endif -------------------------------------------------------------------------------- /src/qak.h: -------------------------------------------------------------------------------- 1 | #ifndef QAK_C_API_H 2 | #define QAK_C_API_H 3 | 4 | #include 5 | #include 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | typedef void *qak_compiler; 12 | 13 | typedef void *qak_module; 14 | 15 | /** A UTF-8 string from a Source with length bytes. **/ 16 | typedef struct qak_string { 17 | const char *data; 18 | size_t length; 19 | } qak_string; 20 | 21 | /** A source file with a name **/ 22 | typedef struct qak_source { 23 | qak_string data; 24 | qak_string fileName; 25 | } qak_source; 26 | 27 | typedef struct qak_span { 28 | qak_string data; 29 | uint32_t start; 30 | uint32_t end; 31 | uint32_t startLine; 32 | uint32_t endLine; 33 | } qak_span; 34 | 35 | typedef struct qak_line { 36 | qak_source *source; 37 | qak_string data; 38 | uint32_t lineNumber; 39 | } qak_line; 40 | 41 | typedef enum qak_token_type { 42 | // Simple tokens, sorted by literal length. Longer literals first. 43 | // The list of simple tokens is terminated via LastSimpleTokenType. 44 | QakTokenPeriod, 45 | QakTokenComma, 46 | QakTokenSemicolon, 47 | QakTokenColon, 48 | QakTokenPlus, 49 | QakTokenMinus, 50 | QakTokenAsterisk, 51 | QakTokenForwardSlash, 52 | QakTokenPercentage, 53 | QakTokenLeftParenthesis, 54 | QakTokenRightParenthesis, 55 | QakTokenLeftBracket, 56 | QakTokenRightBracket, 57 | QakTokenLeftCurly, 58 | QakTokenRightCurly, 59 | QakTokenLessEqual, 60 | QakTokenGreaterEqual, 61 | QakTokenNotEqual, 62 | QakTokenEqual, 63 | QakTokenLess, 64 | QakTokenGreater, 65 | QakTokenAssignment, 66 | QakTokenAnd, 67 | QakTokenOr, 68 | QakTokenXor, 69 | QakTokenNot, 70 | QakTokenHash, 71 | QakTokenQuestionMark, 72 | QakTokenUnknown, 73 | 74 | // These don't have a literal representation 75 | QakTokenBooleanLiteral, 76 | QakTokenDoubleLiteral, 77 | QakTokenFloatLiteral, 78 | QakTokenLongLiteral, 79 | QakTokenIntegerLiteral, 80 | QakTokenShortLiteral, 81 | QakTokenByteLiteral, 82 | QakTokenCharacterLiteral, 83 | QakTokenStringLiteral, 84 | QakTokenNothingLiteral, 85 | QakTokenIdentifier 86 | } qak_token_type; 87 | 88 | typedef struct qak_token { 89 | qak_token_type type; 90 | qak_span span; 91 | } qak_token; 92 | 93 | typedef enum qak_ast_type { 94 | QakAstTypeSpecifier, 95 | QakAstParameter, 96 | QakAstFunction, 97 | QakAstTernaryOperation, 98 | QakAstBinaryOperation, 99 | QakAstUnaryOperation, 100 | QakAstLiteral, 101 | QakAstVariableAccess, 102 | QakAstFunctionCall, 103 | QakAstVariable, 104 | QakAstWhile, 105 | QakAstIf, 106 | QakAstReturn, 107 | QakAstModule 108 | } qak_ast_type; 109 | 110 | typedef int32_t qak_ast_node_index; 111 | 112 | typedef struct qak_ast_node_list { 113 | uint32_t numNodes; 114 | qak_ast_node_index *nodes; 115 | } qak_ast_node_list; 116 | 117 | typedef struct qak_ast_type_specifier { 118 | qak_span name; 119 | } qak_ast_type_specifier; 120 | 121 | typedef struct qak_ast_parameter { 122 | qak_span name; 123 | qak_ast_node_index typeSpecifier; 124 | } qak_ast_parameter; 125 | 126 | typedef struct qak_ast_function { 127 | qak_span name; 128 | qak_ast_node_list parameters; 129 | qak_ast_node_index returnType; 130 | qak_ast_node_list statements; 131 | } qak_ast_function; 132 | 133 | typedef struct qak_ast_variable { 134 | qak_span name; 135 | qak_ast_node_index typeSpecifier; 136 | qak_ast_node_index initializerExpression; 137 | } qak_ast_variable; 138 | 139 | typedef struct qak_ast_while { 140 | qak_ast_node_index condition; 141 | qak_ast_node_list statements; 142 | } qak_ast_while; 143 | 144 | typedef struct qak_ast_if { 145 | qak_ast_node_index condition; 146 | qak_ast_node_list trueBlock; 147 | qak_ast_node_list falseBlock; 148 | } qak_ast_if; 149 | 150 | typedef struct qak_ast_return { 151 | qak_ast_node_index returnValue; 152 | } qak_ast_return; 153 | 154 | typedef struct qak_ast_ternary_operation { 155 | qak_ast_node_index condition; 156 | qak_ast_node_index trueValue; 157 | qak_ast_node_index falseValue; 158 | } qak_ast_ternary_operation; 159 | 160 | typedef struct qak_ast_binary_operation { 161 | qak_span op; 162 | qak_ast_node_index left; 163 | qak_ast_node_index right; 164 | } qak_ast_binary_operation; 165 | 166 | typedef struct qak_ast_unary_operation { 167 | qak_span op; 168 | qak_ast_node_index value; 169 | } qak_ast_unary_operation; 170 | 171 | typedef struct qak_ast_literal { 172 | qak_token_type type; 173 | qak_span value; 174 | } qak_ast_literal; 175 | 176 | typedef struct qak_ast_variable_access { 177 | qak_span name; 178 | } qak_ast_variable_access; 179 | 180 | typedef struct qak_ast_function_call { 181 | qak_ast_node_index variableAccess; 182 | qak_ast_node_list arguments; 183 | } qak_ast_function_call; 184 | 185 | typedef struct qak_ast_module { 186 | qak_span name; 187 | qak_ast_node_list variables; 188 | qak_ast_node_list functions; 189 | qak_ast_node_list statements; 190 | } qak_ast_module; 191 | 192 | typedef struct qak_ast_node { 193 | qak_ast_type type; 194 | qak_span span; 195 | union { 196 | qak_ast_type_specifier typeSpecifier; 197 | qak_ast_parameter parameter; 198 | qak_ast_function function; 199 | qak_ast_variable variable; 200 | qak_ast_while whileNode; 201 | qak_ast_if ifNode; 202 | qak_ast_return returnNode; 203 | qak_ast_ternary_operation ternaryOperation; 204 | qak_ast_binary_operation binaryOperation; 205 | qak_ast_unary_operation unaryOperation; 206 | qak_ast_literal literal; 207 | qak_ast_variable_access variableAccess; 208 | qak_ast_function_call functionCall; 209 | qak_ast_module module; 210 | } data; 211 | } qak_ast_node; 212 | 213 | typedef struct qak_error { 214 | qak_string errorMessage; 215 | qak_span span; 216 | } qak_error; 217 | 218 | /** Compiler **/ 219 | qak_compiler qak_compiler_new(); 220 | 221 | void qak_compiler_delete(qak_compiler compiler); 222 | 223 | void qak_compiler_print_memory_usage(qak_compiler compile); 224 | 225 | qak_module qak_compiler_compile_file(qak_compiler compiler, const char *fileName); 226 | 227 | qak_module qak_compiler_compile_source(qak_compiler compiler, const char *fileName, const char *source); 228 | 229 | /** Module **/ 230 | void qak_module_delete(qak_module module); 231 | 232 | void qak_module_get_source(qak_module module, qak_source *source); 233 | 234 | int qak_module_get_num_errors(qak_module module); 235 | 236 | void qak_module_get_error(qak_module moduleHandle, int errorIndex, qak_error *error); 237 | 238 | void qak_module_print_errors(qak_module module); 239 | 240 | int qak_module_get_num_tokens(qak_module module); 241 | 242 | void qak_module_get_token(qak_module moduleHandle, int tokenIndex, qak_token *token); 243 | 244 | void qak_module_print_tokens(qak_module module); 245 | 246 | qak_ast_module *qak_module_get_ast(qak_module module); 247 | 248 | qak_ast_node *qak_module_get_ast_node(qak_module module, qak_ast_node_index nodeIndex); 249 | 250 | void qak_module_print_ast(qak_module module); 251 | 252 | #ifdef WASM 253 | void qak_print_struct_offsets(); 254 | #endif 255 | 256 | #ifdef __cplusplus 257 | } 258 | #endif 259 | 260 | #endif 261 | -------------------------------------------------------------------------------- /src/source.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ifndef QAK_SOURCE_H 4 | #define QAK_SOURCE_H 5 | 6 | #include "memory.h" 7 | #include "array.h" 8 | 9 | namespace qak { 10 | 11 | /* A line stores the start and end byte index of a line in a Source, as well as 12 | * the 1-based line number. A line ends at a \n or the end of the Source. The 13 | * end byte index is the index of the \n or the size of the Source in case the 14 | * end of the file was reached. 15 | * 16 | * See Source::lines() and Source::scanLines(). */ 17 | struct Line { 18 | /* The offset of the first byte of the line in the Source. */ 19 | uint32_t start; 20 | 21 | /* The offset of the last byte of the line in the Source, either point 22 | * at a \n or the byte after the last byte in the Source. */ 23 | uint32_t end; 24 | 25 | /* The 1-based line number. */ 26 | uint32_t lineNumber; 27 | 28 | Line(uint32_t start, uint32_t end, uint32_t lineNumber) : start(start), end(end), lineNumber(lineNumber) {} 29 | 30 | /* The length of the line in bytes, excluding the \n. */ 31 | uint32_t length() { 32 | return end - start; 33 | } 34 | }; 35 | 36 | /* A source stores the raw byte data of a source file along with the file name. 37 | * It can also returns the individual lines making up the source, see Source::lines(). 38 | * The raw byte data is assumed to have been allocated with the HeapAllocator passed 39 | * to the source's constructor. The source owns the raw byte data and will free it 40 | * through the HeapAllocator upon destruction. */ 41 | struct Source { 42 | private: 43 | Array _lines; 44 | 45 | /* Lines are only needed for error reporting, so we create them with this method 46 | * the first time Source::lines() is called. */ 47 | void scanLines() { 48 | if (_lines.size() != 0) return; 49 | _lines.add(Line(0, 0, 0)); 50 | uint32_t lineStart = 0; 51 | for (size_t i = 0; i < size; i++) { 52 | uint8_t c = data[i]; 53 | if (c == '\n') { 54 | _lines.add(Line(lineStart, (uint32_t) i, (uint32_t) _lines.size())); 55 | lineStart = (uint32_t) i + 1; 56 | } 57 | } 58 | 59 | if (lineStart < size) { 60 | _lines.add(Line(lineStart, (uint32_t) size, (uint32_t) _lines.size())); 61 | } 62 | } 63 | 64 | public: 65 | /* The HeapAllocator managing the memory of the data. */ 66 | HeapAllocator &mem; 67 | 68 | /* The name of the file of which the source stores the data. */ 69 | const char *fileName; 70 | 71 | /* The raw data of the source file. May be nullptr. */ 72 | uint8_t *data; 73 | 74 | /* The size of the data in bytes. */ 75 | size_t size; 76 | 77 | Source(HeapAllocator &mem, const char *fileName, uint8_t *data, size_t size) : _lines(mem), mem(mem), fileName(fileName), data(data), size(size) { 78 | } 79 | 80 | ~Source() { 81 | if (fileName) { 82 | mem.free((void *) fileName, QAK_SRC_LOC); 83 | fileName = nullptr; 84 | } 85 | if (data) { 86 | mem.free(data, QAK_SRC_LOC); 87 | data = nullptr; 88 | } 89 | } 90 | 91 | /* Returns the Lines making up this source. Line indexing starts at 1. 92 | * The line at index 0 has no meaning. */ 93 | Array &lines() { 94 | scanLines(); 95 | return _lines; 96 | } 97 | 98 | /* Creates a new Source with the given file name and source code. The name and 99 | * source code are copied defensively.*/ 100 | static Source *fromMemory(HeapAllocator &mem, const char *fileName, const char *sourceCode) { 101 | size_t dataLength = strlen(sourceCode) + 1; 102 | uint8_t *data = mem.alloc(dataLength, QAK_SRC_LOC); 103 | memcpy(data, sourceCode, dataLength); 104 | 105 | size_t fileNameLength = strlen(fileName) + 1; 106 | char *fileNameCopy = mem.alloc(fileNameLength, QAK_SRC_LOC); 107 | memcpy(fileNameCopy, fileName, fileNameLength); 108 | Source *source = mem.allocObject(QAK_SRC_LOC, mem, fileNameCopy, data, dataLength); 109 | return source; 110 | } 111 | }; 112 | 113 | /* A span stores the location of a sequence of bytes in a Source. The location 114 | * is given as start and end byte offsets into the data of the Source, as well 115 | * as the start and end line number spanned by the byte sequence in the Source. 116 | * See Source::lines(). The actual byte data is maintained by the Source. */ 117 | struct Span { 118 | /* The Source the span references */ 119 | Source &source; 120 | 121 | /* The offset of the first byte of the span in the Source data. */ 122 | uint32_t start; 123 | 124 | /* The line number of the line the first byte of the span is contained in. */ 125 | uint32_t startLine; 126 | 127 | /* The offset of the last byte (exclusive) of the span in the Source data. */ 128 | uint32_t end; 129 | 130 | /* The line number of the line the last byte of the span is contained in. */ 131 | uint32_t endLine; 132 | 133 | Span(Source &source, uint32_t start, uint32_t startLine, uint32_t end, uint32_t endLine) : 134 | source(source), 135 | start(start), 136 | startLine(startLine), 137 | end(end), 138 | endLine(endLine) {} 139 | 140 | Span(Source &source, Span &start, Span &end) : 141 | source(source), 142 | start(start.start), 143 | startLine(start.startLine), 144 | end(end.start), 145 | endLine(end.endLine) {} 146 | 147 | /* Converts the span's bytes to a null terminated C-string. Uses 148 | * the provided HeapAllocator to allocate the memory for the C-string. 149 | * The caller is responsible of freeing the returned C-string via the 150 | * HeapAllocator, or let the HeapAllocator automatically free it when 151 | * it is destructed. */ 152 | const char *toCString(HeapAllocator &mem) { 153 | uint8_t *sourceData = source.data; 154 | uint32_t size = end - start + 1; 155 | uint8_t *cString = mem.alloc(size, QAK_SRC_LOC); 156 | memcpy(cString, sourceData + start, size - 1); 157 | cString[size - 1] = 0; 158 | return (const char *) cString; 159 | } 160 | 161 | /* Returns whether the bytes making up the span matches the needle. The length of the needle 162 | * is given in number of bytes. */ 163 | QAK_FORCE_INLINE bool matches(const char *needle, uint32_t length) { 164 | if (end - start != length) return false; 165 | 166 | const uint8_t *sourceData = source.data + start; 167 | for (uint32_t i = 0; i < length; i++) { 168 | if (sourceData[i] != needle[i]) return false; 169 | } 170 | return true; 171 | } 172 | 173 | /* Returns the length of the span in bytes. */ 174 | uint32_t length() { 175 | return end - start; 176 | } 177 | }; 178 | } 179 | 180 | #endif //QAK_SOURCE_H 181 | -------------------------------------------------------------------------------- /src/tokenizer.cpp: -------------------------------------------------------------------------------- 1 | #include "tokenizer.h" 2 | 3 | using namespace qak; 4 | 5 | static const uint32_t literalToTokenType[] = { 6 | 28, 7 | 28, 8 | 28, 9 | 28, 10 | 28, 11 | 28, 12 | 28, 13 | 28, 14 | 28, 15 | 28, 16 | 28, 17 | 28, 18 | 28, 19 | 28, 20 | 28, 21 | 28, 22 | 28, 23 | 28, 24 | 28, 25 | 28, 26 | 28, 27 | 28, 28 | 28, 29 | 28, 30 | 28, 31 | 28, 32 | 28, 33 | 28, 34 | 28, 35 | 28, 36 | 28, 37 | 28, 38 | 28, 39 | 25, // ! 40 | 28, 41 | 26, // # 42 | 28, 43 | 8, // % 44 | 22, // & 45 | 28, 46 | 9, // ( 47 | 10, // ) 48 | 6, // * 49 | 4, // + 50 | 1, // , 51 | 5, // - 52 | 0, // . 53 | 7, // / 54 | 28, 55 | 28, 56 | 28, 57 | 28, 58 | 28, 59 | 28, 60 | 28, 61 | 28, 62 | 28, 63 | 28, 64 | 3, // : 65 | 2, // ; 66 | 19, // < 67 | 21, // = 68 | 20, // > 69 | 27, // ? 70 | 28, 71 | 28, 72 | 28, 73 | 28, 74 | 28, 75 | 28, 76 | 28, 77 | 28, 78 | 28, 79 | 28, 80 | 28, 81 | 28, 82 | 28, 83 | 28, 84 | 28, 85 | 28, 86 | 28, 87 | 28, 88 | 28, 89 | 28, 90 | 28, 91 | 28, 92 | 28, 93 | 28, 94 | 28, 95 | 28, 96 | 28, 97 | 11, // [ 98 | 28, 99 | 12, // ] 100 | 24, // ^ 101 | 28, 102 | 28, 103 | 28, 104 | 28, 105 | 28, 106 | 28, 107 | 28, 108 | 28, 109 | 28, 110 | 28, 111 | 28, 112 | 28, 113 | 28, 114 | 28, 115 | 28, 116 | 28, 117 | 28, 118 | 28, 119 | 28, 120 | 28, 121 | 28, 122 | 28, 123 | 28, 124 | 28, 125 | 28, 126 | 28, 127 | 28, 128 | 28, 129 | 13, // { 130 | 23, // | 131 | 14, // } 132 | }; 133 | 134 | const char *tokenizer::tokenTypeToString(TokenType type) { 135 | switch (type) { 136 | case Period: 137 | return "."; 138 | case Comma: 139 | return ","; 140 | case Semicolon: 141 | return ";"; 142 | case Colon: 143 | return ":"; 144 | case Plus: 145 | return "+"; 146 | case Minus: 147 | return "-"; 148 | case Asterisk: 149 | return "*"; 150 | case ForwardSlash: 151 | return "/"; 152 | case Percentage: 153 | return "%"; 154 | case LeftParenthesis: 155 | return "("; 156 | case RightParenthesis: 157 | return ")"; 158 | case LeftBracket: 159 | return "["; 160 | case RightBracket: 161 | return "]"; 162 | case LeftCurly: 163 | return "{"; 164 | case RightCurly: 165 | return "}"; 166 | case Less: 167 | return "<"; 168 | case Greater: 169 | return ">"; 170 | case LessEqual: 171 | return "<="; 172 | case GreaterEqual: 173 | return ">="; 174 | case Equal: 175 | return "=="; 176 | case NotEqual: 177 | return "!="; 178 | case Assignment: 179 | return "="; 180 | case And: 181 | return "&"; 182 | case Or: 183 | return "|"; 184 | case Xor: 185 | return "^"; 186 | case Not: 187 | return "!"; 188 | case Hash: 189 | return "#"; 190 | case QuestionMark: 191 | return "?"; 192 | case BooleanLiteral: 193 | return "Boolean literal"; 194 | case DoubleLiteral: 195 | return "Double literal"; 196 | case FloatLiteral: 197 | return "Float literal"; 198 | case LongLiteral: 199 | return "Long literal"; 200 | case IntegerLiteral: 201 | return "Integer literal"; 202 | case ShortLiteral: 203 | return "Short literal"; 204 | case ByteLiteral: 205 | return "Byte literal"; 206 | case CharacterLiteral: 207 | return "Character literal"; 208 | case StringLiteral: 209 | return "String literal"; 210 | case NothingLiteral: 211 | return "Nothing literal"; 212 | case Identifier: 213 | return "Identifier"; 214 | case Unknown: 215 | return "Unknown"; 216 | } 217 | return nullptr; 218 | } 219 | 220 | void tokenizer::tokenize(Source &source, Array &tokens, Errors &errors) { 221 | CharacterStream stream(source); 222 | 223 | while (stream.hasMore()) { 224 | stream.skipWhiteSpace(); 225 | if (!stream.hasMore()) break; 226 | stream.startSpan(); 227 | 228 | // Numbers 229 | if (stream.matchDigit(false)) { 230 | TokenType type = IntegerLiteral; 231 | if (stream.match("0x", true)) { 232 | while (stream.matchHex(true)); 233 | } else { 234 | while (stream.matchDigit(true)); 235 | if (stream.match(".", true)) { 236 | type = FloatLiteral; 237 | while (stream.matchDigit(true)); 238 | } 239 | } 240 | if (stream.match("b", true)) { 241 | if (type == FloatLiteral) QAK_ERROR(stream.endSpan(), "Byte literal can not have a decimal point."); 242 | type = ByteLiteral; 243 | } else if (stream.match("s", true)) { 244 | if (type == FloatLiteral) QAK_ERROR(stream.endSpan(), "Short literal can not have a decimal point."); 245 | type = ShortLiteral; 246 | } else if (stream.match("l", true)) { 247 | if (type == FloatLiteral) QAK_ERROR(stream.endSpan(), "Long literal can not have a decimal point."); 248 | type = LongLiteral; 249 | } else if (stream.match("f", true)) { 250 | type = FloatLiteral; 251 | } else if (stream.match("d", true)) { 252 | type = DoubleLiteral; 253 | } 254 | tokens.add({type, stream.endSpan()}); 255 | continue; 256 | } 257 | 258 | // Character literal 259 | if (stream.match("'", true)) { 260 | // Note: escape sequences like \n are parsed in the AST 261 | stream.match("\\", true); 262 | stream.consume(); 263 | if (!stream.match("'", true)) QAK_ERROR(stream.endSpan(), "Expected closing ' for character literal."); 264 | tokens.add({CharacterLiteral, stream.endSpan()}); 265 | continue; 266 | } 267 | 268 | // String literal 269 | if (stream.match("\"", true)) { 270 | bool matchedEndQuote = false; 271 | while (stream.hasMore()) { 272 | // Note: escape sequences like \n are parsed in the AST 273 | if (stream.match("\\", true)) { 274 | stream.consume(); 275 | } 276 | if (stream.match("\"", true)) { 277 | matchedEndQuote = true; 278 | break; 279 | } 280 | if (stream.match("\n", false)) { 281 | QAK_ERROR(stream.endSpan(), "String literal is not closed by double quote"); 282 | } 283 | stream.consume(); 284 | } 285 | if (!matchedEndQuote) QAK_ERROR(stream.endSpan(), "String literal is not closed by double quote"); 286 | Span stringSpan = stream.endSpan(); 287 | tokens.add({StringLiteral, Span(stringSpan.source, stringSpan.start, stringSpan.startLine, stringSpan.end, stringSpan.endLine)}); 288 | continue; 289 | } 290 | 291 | // Identifier, keyword, boolean literal, or null literal 292 | if (stream.matchIdentifierStart(true)) { 293 | while (stream.matchIdentifierPart(true)); 294 | Span identifier = stream.endSpan(); 295 | 296 | if (identifier.matches(QAK_STR("true")) || identifier.matches(QAK_STR("false"))) { 297 | tokens.add({BooleanLiteral, identifier}); 298 | } else if (identifier.matches(QAK_STR("nothing"))) { 299 | tokens.add({NothingLiteral, identifier}); 300 | } else { 301 | tokens.add({Identifier, identifier}); 302 | } 303 | continue; 304 | } 305 | 306 | // Else check for "simple" tokens made up of 307 | // 1 character literals, like ".", or "[", 308 | // and 2 character literals. 309 | uint32_t character = stream.consume(); 310 | TokenType type = Unknown; 311 | if (character < sizeof(literalToTokenType)) { 312 | type = (TokenType) literalToTokenType[character]; 313 | if (type == Unknown) QAK_ERROR(stream.endSpan(), "Unknown token"); 314 | if (!stream.hasMore()) { 315 | tokens.add({(TokenType) type, stream.endSpan()}); 316 | continue; 317 | } 318 | 319 | uint32_t nextCharacter = stream.peek(); 320 | if (nextCharacter == '=') { 321 | stream.consume(); 322 | switch (type) { 323 | case Less: 324 | tokens.add({(TokenType) LessEqual, stream.endSpan()}); 325 | break; 326 | case Greater: 327 | tokens.add({(TokenType) GreaterEqual, stream.endSpan()}); 328 | break; 329 | case Not: 330 | tokens.add({(TokenType) NotEqual, stream.endSpan()}); 331 | break; 332 | case Assignment: 333 | tokens.add({(TokenType) Equal, stream.endSpan()}); 334 | break; 335 | default: QAK_ERROR(stream.endSpan(), "Found unknown two character token"); 336 | } 337 | } else { 338 | tokens.add({(TokenType) type, stream.endSpan()}); 339 | } 340 | continue; 341 | } 342 | 343 | QAK_ERROR(stream.endSpan(), "Unknown token"); 344 | } 345 | } 346 | 347 | void tokenizer::printTokens(Array &tokens, HeapAllocator &mem) { 348 | size_t lastLine = 1; 349 | for (size_t i = 0; i < tokens.size(); i++) { 350 | Token &token = tokens[i]; 351 | if (token.startLine != lastLine) { 352 | printf("\n"); 353 | lastLine = token.startLine; 354 | } 355 | printf("%s (%d:%d:%d): %s\n", tokenizer::tokenTypeToString(token.type), token.startLine, token.start, token.end, token.toCString(mem)); 356 | } 357 | } -------------------------------------------------------------------------------- /src/tokenizer.h: -------------------------------------------------------------------------------- 1 | #ifndef QAK_TOKENIZER_H 2 | #define QAK_TOKENIZER_H 3 | 4 | #include "array.h" 5 | #include "error.h" 6 | 7 | /* Used in places we pass a char* literal to a method that expects 8 | * the length as well. Doesn't work with dynamically allocated 9 | * char* due to the use of sizeof(). Allows us to not call strlen(). */ 10 | #define QAK_STR(str) str, sizeof(str) - 1 11 | 12 | namespace qak { 13 | 14 | /* A CharacterStream is used to traverse the raw bytes of a Source as UTF-8 characters. 15 | * The stream keeps track of the current position within the Source, both as a byte 16 | * offset, as well as a line number offset. 17 | * 18 | * The stream provides various method to check and/or consume the next 19 | * character in the source. 20 | * 21 | * The stream provides CharacterStream::startSpan() to mark the beginning of a span. A subsequent call 22 | * to CharacterStream::endSpan() will generate a new Span starting at the initial mark, and ending 23 | * at the current position of the stream in the Source. */ 24 | class CharacterStream { 25 | private: 26 | /* The source the stream traverses. */ 27 | Source &_source; 28 | 29 | /* The current byte index into the source's data. */ 30 | uint32_t _index; 31 | 32 | /* The current line number in the source's data. */ 33 | uint32_t _line; 34 | 35 | /* The byte index of the last byte in the source's data + 1. Just a minimal optimization. */ 36 | const uint32_t _end; 37 | 38 | /* The byte index of the stream the last time CharacterStream::startSpan() was called. */ 39 | uint32_t _spanStart; 40 | 41 | /* The line number of the stream the last time CharacterStream::startSpan() was called. */ 42 | uint32_t _spanLineStart; 43 | 44 | 45 | /* Reads the next UTF-8 character from the stream and returns it as a UTF-32 character. 46 | * Taken from https://www.cprogramming.com/tutorial/utf8.c */ 47 | static QAK_FORCE_INLINE uint32_t nextUtf8Character(const uint8_t *data, uint32_t *index) { 48 | static const uint32_t utf8Offsets[6] = { 49 | 0x00000000UL, 0x00003080UL, 0x000E2080UL, 50 | 0x03C82080UL, 0xFA082080UL, 0x82082080UL 51 | }; 52 | 53 | uint32_t character = 0; 54 | int sz = 0; 55 | do { 56 | character <<= 6; 57 | character += data[(*index)++]; 58 | sz++; 59 | } while (data[*index] && !(((data[*index]) & 0xC0) != 0x80)); 60 | character -= utf8Offsets[sz - 1]; 61 | 62 | return character; 63 | } 64 | 65 | public: 66 | 67 | CharacterStream(Source &source) : _source(source), _index(0), _line(1), _end((uint32_t) source.size), _spanStart(0), _spanLineStart(1) { 68 | } 69 | 70 | /* Returns whether the stream has more UTF-8 characters */ 71 | QAK_FORCE_INLINE bool hasMore() { 72 | return _index < _end; 73 | } 74 | 75 | /* Returns whether the stream has numChars more UTF-8 characters */ 76 | QAK_FORCE_INLINE bool hasMore(size_t numChars) { 77 | return _index + numChars - 1 < _end; 78 | } 79 | 80 | /* Returns the current UTF-8 character and advances to the next character */ 81 | QAK_FORCE_INLINE uint32_t consume() { 82 | return nextUtf8Character(_source.data, &_index); 83 | } 84 | 85 | /* Returns the current UTF-8 character without advancing to the next character */ 86 | QAK_FORCE_INLINE uint32_t peek() { 87 | uint32_t i = _index; 88 | return nextUtf8Character(_source.data, &i); 89 | } 90 | 91 | /* Returns true if the current UTF-8 character matches the needle, false otherwise. 92 | * Advances to the next character if consume is true */ 93 | QAK_FORCE_INLINE bool match(const char *needleData, bool consume) { 94 | uint32_t needleLength = 0; 95 | const uint8_t *sourceData = _source.data; 96 | for (uint32_t i = 0, j = _index; needleData[i] != 0; i++, needleLength++) { 97 | if (_index >= _end) return false; 98 | uint32_t c = nextUtf8Character(sourceData, &j); 99 | if ((unsigned char) needleData[i] != c) return false; 100 | } 101 | if (consume) _index += needleLength; 102 | return true; 103 | } 104 | 105 | /* Returns true if the current UTF-8 character is a digit ([0-9]), false otherwise. 106 | * Advances to the next character if consume is true */ 107 | QAK_FORCE_INLINE bool matchDigit(bool consume) { 108 | if (!hasMore()) return false; 109 | uint8_t c = _source.data[_index]; 110 | if (c >= '0' && c <= '9') { 111 | if (consume) _index++; 112 | return true; 113 | } 114 | return false; 115 | } 116 | 117 | /* Returns true if the current UTF-8 character is a hex-digit ([0-9a-fA-F]), false otherwise. 118 | * Advances to the next character if consume is true */ 119 | QAK_FORCE_INLINE bool matchHex(bool consume) { 120 | if (!hasMore()) return false; 121 | uint8_t c = _source.data[_index]; 122 | if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) { 123 | if (consume) _index++; 124 | return true; 125 | } 126 | return false; 127 | } 128 | 129 | /* Returns true if the current UTF-8 character is valid as the first character 130 | * of an identifier ([a-zA-Z_] or any unicode character >= 0xc0), e.g. variable 131 | * name, false otherwise. Advances to the next character if consume is true */ 132 | QAK_FORCE_INLINE bool matchIdentifierStart(bool consume) { 133 | if (!hasMore()) return false; 134 | uint32_t idx = _index; 135 | uint32_t c = nextUtf8Character(_source.data, &idx); 136 | if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c >= 0xc0) { 137 | if (consume) _index = idx; 138 | return true; 139 | } 140 | return false; 141 | } 142 | 143 | /* Returns true if the current UTF-8 character is valid as the first character 144 | * of an identifier ([a-zA-Z_] or any unicode character >= 0x80), e.g. variable 145 | * name, false otherwise. Advances to the next character if consume is true */ 146 | QAK_FORCE_INLINE bool matchIdentifierPart(bool consume) { 147 | if (!hasMore()) return false; 148 | uint32_t idx = _index; 149 | uint32_t c = nextUtf8Character(_source.data, &idx); 150 | if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || (c >= '0' && c <= '9') || c >= 0x80) { 151 | if (consume) _index = idx; 152 | return true; 153 | } 154 | return false; 155 | } 156 | 157 | /* Skips all white space characters ([' '\r\n\t]) and single-line comments. 158 | * Comments start with '#' and end at the end of the current line. */ 159 | QAK_FORCE_INLINE void skipWhiteSpace() { 160 | const uint8_t *sourceData = _source.data; 161 | while (true) { 162 | if (_index >= _end) return; 163 | uint8_t c = sourceData[_index]; 164 | switch (c) { 165 | case '#': { 166 | while (_index < _end && c != '\n') { 167 | c = sourceData[_index]; 168 | _index++; 169 | } 170 | _line++; 171 | continue; 172 | } 173 | case ' ': 174 | case '\r': 175 | case '\t': { 176 | _index++; 177 | continue; 178 | } 179 | case '\n': { 180 | _index++; 181 | _line++; 182 | continue; 183 | } 184 | default: 185 | return; 186 | } 187 | } 188 | } 189 | 190 | /* Mark the start of a span at the current position in the stream. See Span::endSpan(). */ 191 | QAK_FORCE_INLINE void startSpan() { 192 | _spanStart = _index; 193 | _spanLineStart = _line; 194 | } 195 | 196 | /* Return the Span ending at the current position, previously started via 197 | * startSpan(). Calls to startSpan() and endSpan() must match. They can 198 | * not be nested.*/ 199 | QAK_FORCE_INLINE Span endSpan() { 200 | return {_source, _spanStart, _spanLineStart, _index, _line}; 201 | } 202 | }; 203 | 204 | /* Enum describing all token types understood by Qak. 205 | * 206 | * "Simple" token types 207 | * are things like operators, brackets, etc. with a single literal representation. 208 | * If two "simple" tokens start with the same character, the longer one must come 209 | * first, e.g. "<=" must come before "<". The list of simple tokens is delimited by 210 | * the enum value LastSimpleTokenType. 211 | * 212 | * Token types with more than one literal representation, e.g. identifiers of variables 213 | * or floating point numbers, come after simple tokens. Their order in the enum is not 214 | * important. 215 | */ 216 | enum TokenType { 217 | // Simple tokens, sorted by literal length. Longer literals first. 218 | // The list of simple tokens is terminated via LastSimpleTokenType. 219 | Period, 220 | Comma, 221 | Semicolon, 222 | Colon, 223 | Plus, 224 | Minus, 225 | Asterisk, 226 | ForwardSlash, 227 | Percentage, 228 | LeftParenthesis, 229 | RightParenthesis, 230 | LeftBracket, 231 | RightBracket, 232 | LeftCurly, 233 | RightCurly, 234 | LessEqual, 235 | GreaterEqual, 236 | NotEqual, 237 | Equal, 238 | Less, 239 | Greater, 240 | Assignment, 241 | And, 242 | Or, 243 | Xor, 244 | Not, 245 | Hash, 246 | QuestionMark, 247 | Unknown, 248 | 249 | // These don't have a literal representation 250 | BooleanLiteral, 251 | DoubleLiteral, 252 | FloatLiteral, 253 | LongLiteral, 254 | IntegerLiteral, 255 | ShortLiteral, 256 | ByteLiteral, 257 | CharacterLiteral, 258 | StringLiteral, 259 | NothingLiteral, 260 | Identifier 261 | }; 262 | 263 | /* A token with a TokenType and location in the Source. See Span. */ 264 | struct Token : public Span { 265 | /* The type of the token. */ 266 | TokenType type; 267 | 268 | Token(TokenType type, Span span) : Span(span), type(type) {} 269 | }; 270 | 271 | namespace tokenizer { 272 | /* Tokenizes the Source and returns the tokens in the tokens array. 273 | * Errors that occurred during tokenization are stored in the Errors instance. */ 274 | void tokenize(Source &source, Array &tokens, Errors &errors); 275 | 276 | /* Returns a string representation for the token type, e.g. TokenType::Identifier 277 | * returns "Identifier". */ 278 | const char *tokenTypeToString(TokenType type); 279 | 280 | /* Prints the tokens to stdout, grouping them by line. */ 281 | void printTokens(Array &tokens, HeapAllocator &mem); 282 | } 283 | 284 | /* A TokenStream is used to traverse a list of Tokens from a Source. The stream 285 | * keeps track of the current token. 286 | * 287 | * The stream provides various method to match and/or consume the next 288 | * token. 289 | * 290 | * Errors encountered during tokenization are written to the Errors instance 291 | * provided during construction. */ 292 | class TokenStream { 293 | private: 294 | /* The Source from which the tokens come. */ 295 | Source &_source; 296 | 297 | /* The tokens to traverse. */ 298 | Array &_tokens; 299 | 300 | /* The Errors instance to write any errors during tokenization to. */ 301 | Errors &_errors; 302 | 303 | /* The index of the current token in the tokens array. */ 304 | size_t _index; 305 | 306 | public: 307 | TokenStream(Source &source, Array &tokens, Errors &errors) : _source(source), _tokens(tokens), _errors(errors), _index(0) {} 308 | 309 | /* Returns whether there are more tokens in the stream. */ 310 | QAK_FORCE_INLINE bool hasMore() { 311 | return _index < _tokens.size(); 312 | } 313 | 314 | /* Consumes the next token and returns it. */ 315 | QAK_FORCE_INLINE Token *consume() { 316 | if (!hasMore()) return nullptr; 317 | return &_tokens[_index++]; 318 | } 319 | 320 | QAK_FORCE_INLINE Token *peek() { 321 | if (!hasMore()) return nullptr; 322 | return &_tokens[_index]; 323 | } 324 | 325 | /* Checks if the next token has the give type and optionally consumes, or throws an error if the next token did not match the 326 | * type. */ 327 | QAK_FORCE_INLINE Token *expect(TokenType type) { 328 | bool result = match(type, true); 329 | if (!result) { 330 | Token *token = (uint64_t) _index < _tokens.size() ? &_tokens[_index] : nullptr; 331 | if (token == nullptr) { 332 | Span errorSpan(_source, (uint32_t) _source.size - 1, (uint32_t) _source.lines().size() - 1, 333 | (uint32_t) _source.size - 1, 334 | (uint32_t) _source.lines().size() - 1); 335 | _errors.add(errorSpan, "Expected '%s', but reached the end of the source.", 336 | tokenizer::tokenTypeToString(type)); 337 | } else { 338 | HeapAllocator mem; 339 | _errors.add(*token, "Expected '%s', but got '%s'", tokenizer::tokenTypeToString(type), token->toCString(mem)); 340 | } 341 | return nullptr; 342 | } else { 343 | return &_tokens[_index - 1]; 344 | } 345 | } 346 | 347 | /* Checks if the next token matches the given text and optionally consumes, or throws an error if the next token did not match 348 | * the text. */ 349 | QAK_FORCE_INLINE Token *expect(const char *text, uint32_t len) { 350 | bool result = match(text, len, true); 351 | if (!result) { 352 | Token *token = (uint64_t) _index < _tokens.size() ? &_tokens[_index] : nullptr; 353 | if (token == nullptr) { 354 | Span errorSpan(_source, (uint32_t) _source.size - 1, (uint32_t) _source.lines().size() - 1, (uint32_t) _source.size - 1, 355 | (uint32_t) _source.lines().size() - 1); 356 | _errors.add(errorSpan, "Expected '%s', but reached the end of the source.", text); 357 | } else { 358 | HeapAllocator mem; 359 | _errors.add(*token, "Expected '%s', but got '%s'", text, token->toCString(mem)); 360 | } 361 | return nullptr; 362 | } else { 363 | return &_tokens[_index - 1]; 364 | } 365 | } 366 | 367 | /* Matches and optionally consumes the next token in case of a match. Returns whether the token matched. */ 368 | QAK_FORCE_INLINE bool match(TokenType type, bool consume) { 369 | if (_index >= _tokens.size()) return false; 370 | if (_tokens[_index].type == type) { 371 | if (consume) _index++; 372 | return true; 373 | } 374 | return false; 375 | } 376 | 377 | /* Matches and optionally consumes the next token in case of a match. Returns whether the token matched. */ 378 | QAK_FORCE_INLINE bool match(const char *text, uint32_t len, bool consume) { 379 | if (_index >= _tokens.size()) return false; 380 | if (_tokens[_index].matches(text, len)) { 381 | if (consume) _index++; 382 | return true; 383 | } 384 | return false; 385 | } 386 | 387 | /* Returns the tokens of the underlying stream */ 388 | QAK_FORCE_INLINE Array &getTokens() { 389 | return _tokens; 390 | } 391 | }; 392 | } 393 | 394 | #endif //QAK_TOKENIZER_H 395 | -------------------------------------------------------------------------------- /src/types.h: -------------------------------------------------------------------------------- 1 | #ifndef QAK_TYPES_H 2 | #define QAK_TYPES_H 3 | 4 | #include 5 | 6 | #ifdef _MSC_VER 7 | # pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ 8 | # define QAK_FORCE_INLINE __forceinline 9 | #else 10 | # if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ 11 | # ifdef __GNUC__ 12 | # define QAK_FORCE_INLINE inline __attribute__((always_inline)) 13 | # else 14 | # define QAK_FORCE_INLINE inline 15 | # endif 16 | # else 17 | # define QAK_FORCE_INLINE 18 | # endif /* __STDC_VERSION__ */ 19 | #endif 20 | 21 | #define QAK_SRC_LOC __FILE__, __LINE__ 22 | 23 | #endif //QAK_TYPES_H 24 | -------------------------------------------------------------------------------- /wasm/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
48 |

Qak playground

49 |
50 |

 51 |     
52 | 53 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /wasm/qak_post.js: -------------------------------------------------------------------------------- 1 | 2 | out = (str) => { console.log(str); } 3 | err = (str) => { console.log(str); } 4 | }; 5 | 6 | qak.init = function(onReady) { 7 | 8 | qak.wasmModule.onRuntimeInitialized = _ => { 9 | const Module = qak.wasmModule; 10 | const qak_print_struct_offsets = Module.cwrap("qak_print_struct_offsets", "void", []); 11 | const qak_compiler_new = Module.cwrap("qak_compiler_new", "ptr", []); 12 | const qak_compiler_delete = Module.cwrap("qak_compiler_delete", "void", ["ptr"]); 13 | const qak_compiler_print_memory_usage = Module.cwrap("qak_compiler_print_memory_usage", "void", ["ptr"]); 14 | const qak_compiler_compile_source = Module.cwrap("qak_compiler_compile_source", "ptr", ["ptr", "ptr", "ptr"]); 15 | const qak_module_delete = Module.cwrap("qak_module_delete", "void", ["ptr"]); 16 | const qak_module_get_source = Module.cwrap("qak_module_get_source", "void", ["ptr", "ptr"]); 17 | const qak_module_get_num_errors = Module.cwrap("qak_module_get_num_errors", "number", ["ptr"]); 18 | const qak_module_get_error = Module.cwrap("qak_module_get_error", "void", ["ptr", "number", "ptr"]); 19 | const qak_module_print_errors = Module.cwrap("qak_module_print_errors", "void", ["ptr"]); 20 | const qak_module_get_num_tokens = Module.cwrap("qak_module_get_num_tokens", "number", ["ptr"]); 21 | const qak_module_get_token = Module.cwrap("qak_module_get_token", "void", ["ptr", "number", "ptr"]); 22 | const qak_module_print_tokens = Module.cwrap("qak_module_print_tokens", "void", ["ptr"]); 23 | const qak_module_print_ast = Module.cwrap("qak_module_print_ast", "void", ["ptr"]); 24 | 25 | qak_print_struct_offsets(); 26 | 27 | var convertQakString = (stringPtr) => { 28 | var dataPtr = Module.HEAPU32[stringPtr / 4]; 29 | var dataLength = Module.HEAPU32[stringPtr / 4 + 1]; 30 | return Module.UTF8ArrayToString(Module.HEAPU8, dataPtr, dataLength); 31 | } 32 | 33 | var convertQakSpan = (spanPtr) => { 34 | var data = convertQakString(spanPtr); 35 | spanPtrU32 = spanPtr / 4 + 2; 36 | var start = Module.HEAPU32[spanPtrU32]; 37 | var end = Module.HEAPU32[spanPtrU32 + 1]; 38 | var startLine = Module.HEAPU32[spanPtrU32 + 2]; 39 | var endLine = Module.HEAPU32[spanPtrU32 + 3]; 40 | return { 41 | data: data, 42 | start: start, 43 | end: end, 44 | startLine: startLine, 45 | endLine: endLine 46 | } 47 | } 48 | 49 | qak.compiler = qak_compiler_new(); 50 | 51 | qak.printMemoryUsage = () => { 52 | qak_compiler_print_memory_usage(qak.compiler); 53 | } 54 | 55 | qak.compileSource = (sourceName, sourceData) => { 56 | var name = Module.allocateUTF8(sourceName); 57 | var data = Module.allocateUTF8(sourceData); 58 | var module = qak_compiler_compile_source(qak.compiler, name, data); 59 | return module; 60 | }; 61 | 62 | qak.deleteModule = (module) => { 63 | qak_module_delete(module); 64 | } 65 | 66 | qak.getSource = (module) => { 67 | var nativeSource = Module._malloc(16); 68 | qak_module_get_source(module, nativeSource); 69 | var source = { 70 | data: convertQakString(nativeSource), 71 | fileName: convertQakString(nativeSource + 8) 72 | } 73 | Module._free(nativeSource); 74 | return source; 75 | } 76 | 77 | qak.hasErrors = (module) => { 78 | return qak_module_get_num_errors(module) != 0; 79 | } 80 | 81 | qak.getErrors = (module) => { 82 | var numErrors = qak_module_get_num_errors(module); 83 | var errors = new Array(numErrors); 84 | var nativeError = Module._malloc(32); 85 | for (var i = 0; i < numErrors; i++) { 86 | qak_module_get_error(module, i, nativeError); 87 | var errorMessage = convertQakString(nativeError); 88 | var span = convertQakSpan(nativeError + 8); 89 | errors[i] = { 90 | errorMessage: errorMessage, 91 | span: span 92 | } 93 | } 94 | Module._free(nativeError); 95 | return errors; 96 | } 97 | 98 | qak.printErrors = (module) => { 99 | qak_module_print_errors(module); 100 | } 101 | 102 | qak.getTokens = (module) => { 103 | var numTokens = qak_module_get_num_tokens(module); 104 | var tokens = new Array(numTokens); 105 | var nativeToken = Module._malloc(28); 106 | var nativeTokenU32 = nativeToken / 4; 107 | for (var i = 0; i < numTokens; i++) { 108 | qak_module_get_token(module, i, nativeToken); 109 | var type = Module.HEAPU32[nativeTokenU32]; 110 | var span = convertQakSpan(nativeToken + 4); 111 | tokens[i] = { 112 | type: type, 113 | span: span 114 | } 115 | } 116 | Module._free(nativeToken); 117 | return tokens; 118 | } 119 | 120 | qak.printTokens = (module) => { 121 | qak_module_print_tokens(module); 122 | } 123 | 124 | qak.printAst = (module) => { 125 | qak_module_print_ast(module); 126 | } 127 | 128 | if (onReady) onReady(); 129 | }; 130 | 131 | qak.initWasm(); 132 | } -------------------------------------------------------------------------------- /wasm/qak_pre.js: -------------------------------------------------------------------------------- 1 | var qak = {}; 2 | qak.wasmModule = {} 3 | 4 | qak.initWasm = () => { 5 | Module = qak.wasmModule; 6 | --------------------------------------------------------------------------------