├── .clang-format ├── .github └── workflows │ └── c-cpp.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── examples ├── expressions_compiler │ └── main.fscript ├── game_of_life │ └── main.fscript ├── test_driver │ └── main.fscript └── test_json_generator │ └── test_json_generator.ost ├── include ├── ConvertUTF.h ├── core │ ├── callstack.hpp │ ├── evalstack.hpp │ ├── heap.hpp │ ├── interpreter.hpp │ ├── stringpool.hpp │ ├── symtable.hpp │ └── values.hpp ├── dbg.hpp ├── expect.hpp ├── io │ ├── fileio.hpp │ ├── glob.hpp │ ├── lexer.hpp │ ├── parser.hpp │ ├── prettyprint.hpp │ ├── processes.hpp │ ├── strings.hpp │ └── termio.hpp ├── linenoise.h ├── prelude │ └── repl.hpp └── std │ ├── arith.hpp │ ├── arrays.hpp │ ├── boolean.hpp │ ├── comparisons.hpp │ ├── controlflow.hpp │ ├── indexing.hpp │ ├── operators.hpp │ ├── os.hpp │ ├── stack.hpp │ ├── strings.hpp │ ├── templates.hpp │ └── typing.hpp ├── src ├── core │ ├── callstack.cpp │ ├── evalstack.cpp │ ├── heap.cpp │ ├── interpreter.cpp │ ├── symtable.cpp │ └── values.cpp ├── io │ ├── encodings.cpp │ ├── fileio.cpp │ ├── glob.cpp │ ├── lexer.cpp │ ├── parser.cpp │ ├── prettyprint.cpp │ ├── processes.cpp │ ├── strings.cpp │ └── termio.cpp ├── linenoise │ ├── ConvertUTF.cpp │ ├── linenoise.cpp │ └── wcwidth.cpp ├── main.cpp ├── prelude │ ├── repl.cpp │ └── repl.fscript └── std │ ├── arith.cpp │ ├── arrays.cpp │ ├── boolean.cpp │ ├── comparisons.cpp │ ├── controlflow.cpp │ ├── indexing.cpp │ ├── os.cpp │ ├── stack.cpp │ ├── strings.cpp │ ├── templates.cpp │ └── typing.cpp ├── test_root └── tests ├── cases ├── abs │ ├── expectedOutput.txt │ └── main.fscript ├── brainfuck │ ├── expectedOutput.txt │ └── main.fscript ├── is_prime │ ├── expectedOutput.txt │ └── main.fscript ├── pow_func_gen │ ├── expectedOutput.txt │ └── main.fscript ├── quicksort │ ├── expectedOutput.txt │ ├── main.fscript │ └── unsorted.fscript └── rot13 │ ├── expectedOutput.txt │ └── main.fscript ├── helper.py ├── requirements.txt ├── simpleCases.json ├── simpleCases ├── arithmetic.json ├── comparisons.json ├── func_chr.json ├── func_dup.json ├── func_len.json ├── func_ord.json ├── func_peek.json ├── func_poke.json ├── func_slice.json └── func_swap.json └── test.py /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | IndentWidth: 4 3 | UseTab: Never -------------------------------------------------------------------------------- /.github/workflows/c-cpp.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | cross-test: 11 | name: '${{ matrix.os }}: build and test' 12 | runs-on: '${{ matrix.os }}' 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | os: [windows-latest, ubuntu-latest, macos-latest, ubuntu-20.04] 17 | python-version: [3.8] 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | - uses: actions/setup-python@v2 22 | with: 23 | python-version: ${{ matrix.python-version }} 24 | - name: Building interpreter 25 | run: mkdir build && cd build && cmake .. && cmake --build ./ && cd .. 26 | - name: Run tests 27 | run: cd tests && python test.py && cd .. 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .vscode 3 | .vs 4 | .idea 5 | *.o 6 | *.exe 7 | __pycache__ 8 | forthscript 9 | CMakeLists.txt.user 10 | CMakeCache.txt 11 | CMakeFiles 12 | CMakeScripts 13 | CMakeSettings.json 14 | Testing 15 | venv 16 | Makefile 17 | cmake_install.cmake 18 | install_manifest.txt 19 | compile_commands.json 20 | CTestTestfile.cmake 21 | _deps 22 | .replit 23 | /forthscript.dir/Debug 24 | /x64/Debug 25 | *.vcxproj 26 | *.vcxproj.filters 27 | *.sln 28 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.9.4) 2 | 3 | include(CheckIPOSupported) 4 | 5 | project(forthscript) 6 | 7 | # https://stackoverflow.com/questions/42582456/cmake-function-to-convert-string-to-c-string-literal 8 | function(convert_to_cstring_literal var value) 9 | string(REGEX REPLACE "([\\\"])" "\\\\\\1" escaped "${value}") 10 | string(REPLACE "\n" "\\n" escaped "${escaped}") 11 | set("${var}" "\"${escaped}\"" PARENT_SCOPE) 12 | endfunction() 13 | 14 | set(CMAKE_CXX_STANDARD 17) 15 | set(REPL_SOURCE_PATH "${CMAKE_SOURCE_DIR}/src/prelude/repl.fscript") 16 | 17 | # Bundle REPL source 18 | message(STATUS "REPL source in ${REPL_SOURCE_PATH}") 19 | file(READ "${REPL_SOURCE_PATH}" REPL_SOURCE) 20 | convert_to_cstring_literal(REPL_SOURCE "${REPL_SOURCE}") 21 | message(STATUS "REPL code listing follows \n${REPL_SOURCE}") 22 | add_definitions(-DFORTHSCRIPT_REPL_SOURCE=${REPL_SOURCE}) 23 | 24 | # Set NDEBUG variable 25 | message(STATUS "Build type selected = ${CMAKE_BUILD_TYPE}") 26 | if (CMAKE_BUILD_TYPE MATCHES Release) 27 | add_definitions(-DNDEBUG) 28 | endif() 29 | 30 | # Enabling warnings as errors 31 | if (MSVC) 32 | add_compile_options(/W1) 33 | else() 34 | add_compile_options(-Wall -Wextra -pedantic -Werror) 35 | endif() 36 | 37 | include_directories(include) 38 | 39 | file(GLOB_RECURSE SOURCES src/*.cpp) 40 | 41 | add_executable(forthscript ${SOURCES}) 42 | 43 | # Enable LTO on release 44 | if (CMAKE_BUILD_TYPE MATCHES Release) 45 | check_ipo_supported(RESULT supported OUTPUT error) 46 | if ( supported ) 47 | message(STATUS "LTO builds are supported") 48 | set_property(TARGET forthscript PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) 49 | endif() 50 | endif() 51 | 52 | set_target_properties(forthscript PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}) 53 | set_target_properties(forthscript PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}) 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 ForthScriptLang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # forthscript 2 | [![Run on Repl.it](https://repl.it/badge/github/ForthScriptLang/forthscript)](https://repl.it/@ForthScriptLang/forthscript) ![C/C++ CI](https://github.com/ForthScriptLang/forthscript/workflows/C/C++%20CI/badge.svg?branch=master) 3 | 4 | This repository contains code for the reference implementation of the ForthScript programming language). 5 | 6 | ForthScript is a stack-based programming language with automatic memory management, heavily inspired by Forth and Lisp. ForthScript and Forth are both stack-based concatenative programming languages. ForthScript extends Forth with additional features that make ForthScript slightly higher level - local and global variables, array operations and tracing garbage collection. 7 | 8 | ForthScript borrows homoiconicity from Lisp - any ForthScript function is an array of instructions. In fact, there is no difference between code and data - all values in ForthScript can be interpreted as either. 9 | 10 | The ForthScript is dynamically typed (variables and arrays can store values of any kind). Yet, types are still a thing in ForthScript, and so are type errors - one cannot add integer to a string. 11 | 12 | ### Building ForthScript 13 | 14 | ForthScript interpreter can be built as any other CMake C/C++ project. All you need is CMake and a C++ compiler. No third-party dependencies required. 15 | 16 | ```bash 17 | mkdir build 18 | cd build 19 | cmake .. 20 | cmake --build . 21 | ``` 22 | 23 | Build directory should now contain `forthscript` (or `forthscript.exe`) executable. 24 | 25 | ## Running ForthScript 26 | 27 | To launch ForthScript interpreter in REPL (Read-Eval-Print-Loop) mode, simply run the `forthscript` executable with no arguments 28 | 29 | ```bash 30 | # Run REPL 31 | ./forthscript 32 | ``` 33 | 34 | To execute a file with ForthScript code, pass the path to the file as the first argument 35 | ```bash 36 | # Run example.fscript 37 | ./forthscript example.fscript 38 | ``` 39 | 40 | ```.fscript``` is a standard file extension for ForthScript source files. 41 | 42 | ## More on ForthScript REPL 43 | 44 | You can try all examples below with REPL. When you run the interpreter in REPL mode (no CLI arguments), you should see 45 | the REPL prompt 46 | 47 | ``` 48 | [] 49 | # 50 | ``` 51 | 52 | The first line visualises values on the stack. As the interpreter stack is empty on startup, there won't be anything between the brackets. 53 | 54 | The second line is the prompt. Everything after `# ` is interpreted as ForthScript code. 55 | 56 | To quit the REPL session, type `quit` and press Enter. 57 | 58 | ## Examples 59 | 60 | ### "Hello, world" 61 | ``` 62 | "Hello, world!" writeln 63 | ``` 64 | 65 | ### Swap function 66 | ``` 67 | # $a declares a local variable and sets it to TOS (which gets removed) 68 | [$a $b a b] $swap 69 | "3" "4" swap! writeln writeln 70 | ``` 71 | 72 | ### FizzBuzz 73 | ``` 74 | # =a assings TOS to a 75 | # for loop creates two scopes - one with $i and one with loop body 76 | [1 $i] [i 30 <] [i 1 + =i] [ 77 | i 15 % 0 == ["FizzBuzz" writeln continue] if 78 | i 5 % 0 == ["Buzz" writeln continue] if 79 | i 3 % 0 == ["Buzz" writeln continue] if 80 | i to_string writeln 81 | ] for 82 | ``` 83 | 84 | ### Infix arithmetic expressions (using homoiconicity) 85 | ``` 86 | # Run examples/expressions_compiler/main.fscript in this scope 87 | # "parse" instruction parses string and outputs ForthScript array with instructions 88 | # "," runs this array in this scope 89 | "examples/expressions_compiler/main.fscript" readfile parse , 90 | 91 | # arith_compile function is now available in this scope 92 | "arith_compile source:" writeln 93 | arith_compile serialize writeln 94 | 95 | # use arith_compile to transform arithmetic expressions 96 | "compiling: " write 97 | [2 + 2 * 2] $source source serialize writeln 98 | "got: " write 99 | source arith_compile! $compiled compiled serialize writeln 100 | "result: " write 101 | # arith_compile also returns an array of instructions, which we can just run 102 | compiled! to_string writeln # 6 103 | ``` 104 | -------------------------------------------------------------------------------- /examples/expressions_compiler/main.fscript: -------------------------------------------------------------------------------- 1 | [ to_array $src 2 | # helper functions 3 | # function to get priority of the operation 4 | [ $word 5 | word to_string "+" == [ 6 | 1 return 7 | ] if 8 | word to_string "-" == [ 9 | 1 return 10 | ] if 11 | word to_string "*" == [ 12 | 2 return 13 | ] if 14 | word to_string "/" == [ 15 | 2 return 16 | ] if 17 | word to_string "%" == [ 18 | 2 return 19 | ] if 20 | ] $get_priority 21 | 22 | # function to add elements from one array to another 23 | [ $arr2 $arr 24 | [0 $i] [i arr2 len <] [i 1 + =i] [ 25 | arr arr2 i peek append 26 | ] for 27 | ] $extend 28 | 29 | # functions to get and remove top elements from the stack 30 | [$arr arr arr len 1 - peek] $back 31 | [$arr arr arr len 1 - Nil resize] $pop_back 32 | 33 | []@ $result 34 | []@ $stack 35 | # iterate over tokens 36 | Nil $prev_token 37 | Nil $token 38 | [0 $i] [i src len <] [token $prev_token i 1 + =i] [ 39 | src i peek =token 40 | # check for numeric or variable type 41 | token type "Numeric" == token type "Word" == or [ 42 | result token append 43 | continue 44 | ] if 45 | # check for native word type 46 | token type "NativeWord" == [ 47 | token get_priority! $cur_priority 48 | stack len 0 == [ 49 | stack token append 50 | ] [ 51 | Nil $tmp 52 | [ stack len 0 != [ 53 | stack back! =tmp 54 | tmp get_priority! $top_priority 55 | top_priority cur_priority >= 56 | ] [False] if_else ] [ 57 | stack pop_back! 58 | result tmp append 59 | ] while 60 | stack token append 61 | ] if_else 62 | continue 63 | ] if 64 | # check for function calls/parens 65 | token type "SplicePlaceholder" == [ 66 | result token arith_compile! extend! 67 | ] if 68 | ] for 69 | [stack len 0 !=] [ 70 | stack back! $tmp 71 | stack pop_back! 72 | result tmp append 73 | ] while 74 | result 75 | ] $arith_compile -------------------------------------------------------------------------------- /examples/game_of_life/main.fscript: -------------------------------------------------------------------------------- 1 | 15 $w 20 $h 2 | 3 | w h * False alloc $field 4 | 5 | field len False alloc $copy 6 | 7 | 8 | # offset: (x y -- w) 9 | [ $y $x 10 | y h + h % =y 11 | x w + w % =x 12 | y w * x + 13 | ] $offset 14 | 15 | # get: (x y -- e) 16 | [ $y $x field x y offset! peek ] $get 17 | 18 | # set: (x y val -- ) 19 | [ $val $y $x copy x y offset! val poke ] $set 20 | 21 | # flush changes to field array 22 | [ 23 | [0 $i] [i field len <] [i 1 + =i] [ 24 | field i copy i peek poke 25 | ] for 26 | ] $flush 27 | 28 | # get alive neighbours count 29 | # alive_neighbours: (x y -- num) 30 | [ $y $x 31 | 0 $num 32 | [num 1 + =num] $body 33 | x 1 - y 1 - get! body if 34 | x 1 + y 1 - get! body if 35 | x 1 - y 1 + get! body if 36 | x 1 + y 1 + get! body if 37 | x y 1 + get! body if 38 | x y 1 - get! body if 39 | x 1 + y get! body if 40 | x 1 - y get! body if 41 | num 42 | ] $alive_neighbours 43 | 44 | # determine new status of the cell 45 | [ $y $x 46 | x y alive_neighbours! $num 47 | x y get! [ 48 | # cell is alive 49 | num 2 != num 3 != and [ 50 | x y False set! 51 | ] if 52 | ] [ 53 | # cell is dead 54 | num 3 == [ 55 | x y True set! 56 | ] if 57 | ] if_else 58 | ] $recalc_at 59 | 60 | # recalculate the whole board 61 | [ 62 | [0 $x] [x w <] [x 1 + =x] [ 63 | [0 $y] [y h <] [y 1 + =y] [ 64 | x y recalc_at! 65 | ] for 66 | ] for 67 | ] $recalc 68 | 69 | # redraw the board 70 | [ 71 | 27 chr "[2J" + 27 chr "[H" + + write 72 | [0 $y] [y h <] [y 1 + =y] [ 73 | [0 $x] [x w <] [x 1 + =x] [ 74 | x y get! [" ▤ "] [" ◻ "] if_else write 75 | ] for 76 | "\n" write 77 | ] for 78 | ] $redraw 79 | 80 | # draw a glider 81 | 0 2 True set! 82 | 1 2 True set! 83 | 2 2 True set! 84 | 2 1 True set! 85 | 1 0 True set! 86 | flush! 87 | 88 | # game loop 89 | [True] [ 90 | redraw! 91 | "Enter \"quit to quit\" or anything else to continue: " readln "quit" == [quit] if 92 | recalc! 93 | flush! 94 | ] while 95 | -------------------------------------------------------------------------------- /examples/test_driver/main.fscript: -------------------------------------------------------------------------------- 1 | # this example has to be ran from the root directory of the source tree 2 | "test_root" readfile "SbQVHcuecUS-lv9xehGSGg\n" != ["run from the repo root" writeln 1 exit] if 3 | 4 | # get list of cases 5 | "tests/cases/" $cases_path 6 | cases_path "*" + $cases_pattern 7 | cases_pattern glob $cases_list 8 | 9 | # path to interpreter 10 | "../build/forthscript" $interpreter_path 11 | 12 | [] $successes 13 | [] $failures 14 | 15 | [0 $i] [i cases_list len <] [i 1 + =i] [ 16 | # remove . and .. from tests 17 | cases_list i peek dup "." == swap ".." == or [ continue ] if 18 | # get path to test folder 19 | "cases/" cases_list i peek + $folder_path 20 | # get path to file with source code 21 | folder_path "/main.fscript" + $input_path 22 | # get path to file with expected output 23 | "tests/" folder_path "/expectedOutput.txt" + + $output_path 24 | # get path to log file in case of error 25 | "tests/" folder_path "/errorlog.txt" + + $log_output_path 26 | # get expected output 27 | output_path readfile $expected_output 28 | # get command to interpreter 29 | "cd tests && " interpreter_path " " input_path + + + $command 30 | # run interpreter 31 | "" command exec [ 32 | # collect result from the stack 33 | $return_code $actual_output 34 | # check the output 35 | actual_output expected_output == [ 36 | # test was a success 37 | successes [{i} {input_path} {command}]@ append 38 | ] [ 39 | # test was a failure 40 | failures [{i} {input_path} {command}]@ append 41 | actual_output log_output_path writefile 42 | ] if_else 43 | ] [ 44 | "Failed to run \'" write 45 | cmd write 46 | "\'" writeln 47 | 1 exit 48 | ] if_else 49 | ] for 50 | 51 | # pretty-printing of tests results 52 | [ $array 53 | [0 $i] [i array len <] [i 1 + =i] [ 54 | array i peek $data 55 | data 0 peek $index 56 | data 1 peen $input_path 57 | data 2 peek $command 58 | i 1 + serialize ". " + cases_list index peek + 59 | " (\"" + command + "\")" + writeln 60 | ] for 61 | ] $report_results 62 | 63 | # output successes if any 64 | successes len 0 != [ 65 | "Successes: " writeln 66 | successes report_results! 67 | "" writeln 68 | ] if 69 | 70 | # output failures if any 71 | failures len 0 != [ 72 | "Failures: " writeln 73 | failures report_results! 74 | "See errorlog.txt files in folders with failed tests for more info\n" writeln 75 | ] if 76 | 77 | # calculate coverage 78 | successes len 100 * successes len failures len + / $percentage 79 | # output statistics 80 | successes len serialize " tests passed, " + write 81 | failures len serialize " tests failed, " + write 82 | "percentage: " percentage serialize + "%" + writeln 83 | 84 | # deside on exit code depending on failures count 85 | failures len 0 != [ 86 | -1 exit 87 | ] [ 88 | quit 89 | ] if_else 90 | -------------------------------------------------------------------------------- /examples/test_json_generator/test_json_generator.ost: -------------------------------------------------------------------------------- 1 | [] $tests 2 | 3 | [ $ind 4 | "Enter test " ind to_string + " source code: " + readln $src 5 | Nil $top 6 | [ 7 | clear 8 | [ 9 | # parse code 10 | src parse $ast 11 | # check for parsing errors 12 | ast type "Nil" == [ 13 | "Parsing error" throw 14 | ] if 15 | # now we have ast on the stack. Execute it 16 | ast! 17 | # get top element 18 | =top 19 | ] 0 try not 20 | ] [] while 21 | []@ $result 22 | result src to_string " to_string write" + append 23 | result top to_string append 24 | result 25 | ] $generate_test 26 | 27 | [ $comma $ind $test 28 | "" 29 | # rendering starting string 30 | " {\n" + 31 | # rendering name string 32 | " \"name\": \"" ind to_string + "\",\n" + + 33 | # rendering input string 34 | " \"input\": \"" test 0 peek + "\",\n" + + 35 | # rendering expected output string 36 | " \"expectedOutput\": \"" test 1 peek + "\"\n" + + 37 | # rendering ending string 38 | " }" + 39 | comma [",\n"] ["\n"] if_else + 40 | ] $render_test_in_json 41 | 42 | "Enter test count: " readln to_numeric $count 43 | 44 | ["{\n \"cases\": [\n"] $strbuf 45 | 46 | [0 $i] [i count <] [i 1 + =i] [ 47 | i generate_test! $test 48 | test i i count 1 - != render_test_in_json! 49 | strbuf swap append 50 | ] for 51 | 52 | strbuf " ]\n}" append 53 | 54 | strbuf "" join writeln -------------------------------------------------------------------------------- /include/ConvertUTF.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2001-2004 Unicode, Inc. 3 | * 4 | * Disclaimer 5 | * 6 | * This source code is provided as is by Unicode, Inc. No claims are 7 | * made as to fitness for any particular purpose. No warranties of any 8 | * kind are expressed or implied. The recipient agrees to determine 9 | * applicability of information provided. If this file has been 10 | * purchased on magnetic or optical media from Unicode, Inc., the 11 | * sole remedy for any claim will be exchange of defective media 12 | * within 90 days of receipt. 13 | * 14 | * Limitations on Rights to Redistribute This Code 15 | * 16 | * Unicode, Inc. hereby grants the right to freely use the information 17 | * supplied in this file in the creation of products supporting the 18 | * Unicode Standard, and to make copies of this file in any form 19 | * for internal or external distribution as long as this notice 20 | * remains attached. 21 | */ 22 | 23 | /* --------------------------------------------------------------------- 24 | 25 | Conversions between UTF32, UTF-16, and UTF-8. Header file. 26 | 27 | Several funtions are included here, forming a complete set of 28 | conversions between the three formats. UTF-7 is not included 29 | here, but is handled in a separate source file. 30 | 31 | Each of these routines takes pointers to input buffers and output 32 | buffers. The input buffers are const. 33 | 34 | Each routine converts the text between *sourceStart and sourceEnd, 35 | putting the result into the buffer between *targetStart and 36 | targetEnd. Note: the end pointers are *after* the last item: e.g. 37 | *(sourceEnd - 1) is the last item. 38 | 39 | The return result indicates whether the conversion was successful, 40 | and if not, whether the problem was in the source or target buffers. 41 | (Only the first encountered problem is indicated.) 42 | 43 | After the conversion, *sourceStart and *targetStart are both 44 | updated to point to the end of last text successfully converted in 45 | the respective buffers. 46 | 47 | Input parameters: 48 | sourceStart - pointer to a pointer to the source buffer. 49 | The contents of this are modified on return so that 50 | it points at the next thing to be converted. 51 | targetStart - similarly, pointer to pointer to the target buffer. 52 | sourceEnd, targetEnd - respectively pointers to the ends of the 53 | two buffers, for overflow checking only. 54 | 55 | These conversion functions take a ConversionFlags argument. When this 56 | flag is set to strict, both irregular sequences and isolated surrogates 57 | will cause an error. When the flag is set to lenient, both irregular 58 | sequences and isolated surrogates are converted. 59 | 60 | Whether the flag is strict or lenient, all illegal sequences will cause 61 | an error return. This includes sequences such as: , , 62 | or in UTF-8, and values above 0x10FFFF in UTF-32. Conformant code 63 | must check for illegal sequences. 64 | 65 | When the flag is set to lenient, characters over 0x10FFFF are converted 66 | to the replacement character; otherwise (when the flag is set to strict) 67 | they constitute an error. 68 | 69 | Output parameters: 70 | The value "sourceIllegal" is returned from some routines if the input 71 | sequence is malformed. When "sourceIllegal" is returned, the source 72 | value will point to the illegal value that caused the problem. E.g., 73 | in UTF-8 when a sequence is malformed, it points to the start of the 74 | malformed sequence. 75 | 76 | Author: Mark E. Davis, 1994. 77 | Rev History: Rick McGowan, fixes & updates May 2001. 78 | Fixes & updates, Sept 2001. 79 | 80 | ------------------------------------------------------------------------ */ 81 | 82 | /* --------------------------------------------------------------------- 83 | The following 4 definitions are compiler-specific. 84 | The C standard does not guarantee that wchar_t has at least 85 | 16 bits, so wchar_t is no less portable than unsigned short! 86 | All should be unsigned values to avoid sign extension during 87 | bit mask & shift operations. 88 | ------------------------------------------------------------------------ */ 89 | 90 | #pragma once 91 | 92 | #if 0 93 | typedef unsigned long UTF32; /* at least 32 bits */ 94 | typedef unsigned short UTF16; /* at least 16 bits */ 95 | typedef unsigned char UTF8; /* typically 8 bits */ 96 | #endif 97 | 98 | #include 99 | 100 | #include 101 | 102 | namespace linenoise_ng { 103 | 104 | typedef uint32_t UTF32; 105 | typedef uint16_t UTF16; 106 | typedef uint8_t UTF8; 107 | typedef unsigned char Boolean; /* 0 or 1 */ 108 | 109 | /* Some fundamental constants */ 110 | #define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD 111 | #define UNI_MAX_BMP (UTF32)0x0000FFFF 112 | #define UNI_MAX_UTF16 (UTF32)0x0010FFFF 113 | #define UNI_MAX_UTF32 (UTF32)0x7FFFFFFF 114 | #define UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF 115 | 116 | typedef enum { 117 | conversionOK, /* conversion successful */ 118 | sourceExhausted, /* partial character in source, but hit end */ 119 | targetExhausted, /* insuff. room in target for conversion */ 120 | sourceIllegal /* source sequence is illegal/malformed */ 121 | } ConversionResult; 122 | 123 | typedef enum { strictConversion = 0, lenientConversion } ConversionFlags; 124 | 125 | // /* This is for C++ and does no harm in C */ 126 | // #ifdef __cplusplus 127 | // extern "C" { 128 | // #endif 129 | 130 | ConversionResult ConvertUTF8toUTF16(const UTF8** sourceStart, 131 | const UTF8* sourceEnd, UTF16** targetStart, 132 | UTF16* targetEnd, ConversionFlags flags); 133 | 134 | ConversionResult ConvertUTF16toUTF8(const UTF16** sourceStart, 135 | const UTF16* sourceEnd, UTF8** targetStart, 136 | UTF8* targetEnd, ConversionFlags flags); 137 | 138 | ConversionResult ConvertUTF8toUTF32(const UTF8** sourceStart, 139 | const UTF8* sourceEnd, UTF32** targetStart, 140 | UTF32* targetEnd, ConversionFlags flags); 141 | 142 | ConversionResult ConvertUTF32toUTF8(const UTF32** sourceStart, 143 | const UTF32* sourceEnd, UTF8** targetStart, 144 | UTF8* targetEnd, ConversionFlags flags); 145 | 146 | ConversionResult ConvertUTF16toUTF32(const UTF16** sourceStart, 147 | const UTF16* sourceEnd, 148 | UTF32** targetStart, UTF32* targetEnd, 149 | ConversionFlags flags); 150 | 151 | ConversionResult ConvertUTF32toUTF16(const UTF32** sourceStart, 152 | const UTF32* sourceEnd, 153 | char16_t** targetStart, 154 | char16_t* targetEnd, 155 | ConversionFlags flags); 156 | 157 | Boolean isLegalUTF8Sequence(const UTF8* source, const UTF8* sourceEnd); 158 | 159 | // #ifdef __cplusplus 160 | // } 161 | // #endif 162 | 163 | } // namespace linenoise_ng 164 | 165 | /* --------------------------------------------------------------------- */ 166 | -------------------------------------------------------------------------------- /include/core/callstack.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | struct CallStack { 7 | Array** callStack = nullptr; 8 | size_t current = 0; 9 | size_t max; 10 | 11 | CallStack(size_t recLimit); 12 | bool pushFrame(Array* arr); 13 | void popFrame(); 14 | void registerRootMarker(Heap& h); 15 | }; -------------------------------------------------------------------------------- /include/core/evalstack.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class EvaluationStack { 9 | std::vector stack; 10 | size_t barrier = 0; 11 | 12 | public: 13 | EvaluationStack(); 14 | void registerRootMarker(Heap &heap); 15 | std::optional popBack(); 16 | void pushBack(Value val); 17 | bool assertDepth(size_t count) const; 18 | void clear(); 19 | void setBarrier(size_t size); 20 | size_t getBarrier(); 21 | size_t getStackSize(); 22 | void resize(size_t new_size); 23 | 24 | inline const std::vector getStack() const { return stack; } 25 | }; -------------------------------------------------------------------------------- /include/core/heap.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define HEAP_LOG(x) DBG_ONLY(std::cerr << "[ Heap ] " << x << std::endl) 11 | #define GC_LOG(x) DBG_ONLY(std::cerr << "[ GC ] " << x << std::endl) 12 | 13 | struct Heap { 14 | Object *head; 15 | void insertObject(Object *obj); 16 | 17 | using RootMarker = std::function; 18 | std::vector rootMarkers; 19 | size_t insertRootMarker(RootMarker marker); 20 | 21 | void markObject(Object *obj); 22 | void collectGarbage(); 23 | void runGCIfOverThreshold(); 24 | Heap(); 25 | 26 | StringPool pool; 27 | String *makeStringObject(const std::u32string &val); 28 | String *makeStringObject(std::u32string &&val); 29 | String *makeStringObject(const std::u32string_view &val); 30 | String *makeStringObject(const char32_t *val); 31 | 32 | size_t objectCount; 33 | size_t prevCount; 34 | 35 | Array *makeArrayObject(Value defaultVal, size_t size); 36 | Array *shallowCopy(Array *other); 37 | Array *deepCopy(Array *other); 38 | }; -------------------------------------------------------------------------------- /include/core/interpreter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | enum class ExecutionResultType { 13 | Error, 14 | Success, 15 | Return, 16 | Break, 17 | Continue, 18 | Custom 19 | }; 20 | 21 | struct ExecutionResult { 22 | ExecutionResultType result = ExecutionResultType::Success; 23 | const char32_t* error = U""; 24 | Value val; 25 | }; 26 | 27 | struct Success : public ExecutionResult { 28 | Success(); 29 | }; 30 | 31 | struct EvalStackUnderflow : public ExecutionResult { 32 | EvalStackUnderflow(); 33 | }; 34 | 35 | struct CallStackOverflow : public ExecutionResult { 36 | CallStackOverflow(); 37 | }; 38 | 39 | struct TypeError : public ExecutionResult { 40 | TypeError(); 41 | }; 42 | 43 | struct Interpreter { 44 | EvaluationStack evalStack; 45 | Heap heap; 46 | SymbolTable symTable; 47 | CallStack callStack; 48 | std::unordered_map symbolsToStrings; 49 | std::unordered_map stringsToSymbols; 50 | 51 | Interpreter(size_t maxRecursionDepth); 52 | void defineNativeWord(const std::u32string& str, NativeWord word); 53 | ExecutionResult callInterpreter(Array* code, bool newScope); 54 | NativeWord queryNativeWord(String* str); 55 | }; -------------------------------------------------------------------------------- /include/core/stringpool.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | struct StringObjectHash { 7 | inline size_t operator()(const String* str) const { 8 | size_t result = 5381; 9 | for (char32_t c : str->get()) { 10 | result = ((result >> 5) | result) + (size_t)c; 11 | } 12 | return result; 13 | } 14 | }; 15 | 16 | struct StringObjectEqualityComparator { 17 | inline bool operator()(const String* lhs, const String* rhs) const { 18 | return lhs->get() == rhs->get(); 19 | } 20 | }; 21 | 22 | using StringPool = std::unordered_set; -------------------------------------------------------------------------------- /include/core/symtable.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class SymbolTable { 11 | std::vector declared; 12 | std::vector numDefined; 13 | 14 | public: 15 | SymbolTable(size_t recLimit) { numDefined.reserve(recLimit); } 16 | void createScope(); 17 | void leaveScope(); 18 | void declareVariable(String *name, Value val); 19 | void setVariable(String *name, Value val); 20 | Value getVariable(String *name); 21 | inline const std::vector &getDeclared() { return declared; }; 22 | }; -------------------------------------------------------------------------------- /include/core/values.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | struct Object { 11 | struct Object *next_to_scan, *next, *prev; 12 | size_t referenceCount : 63; 13 | bool marked : 1; 14 | 15 | virtual Object *addPointedToQueue(struct Object *head) = 0; 16 | virtual ~Object(); 17 | }; 18 | 19 | enum class ValueType { 20 | Nil = 0, 21 | NativeWord = 1, 22 | Numeric = 2, 23 | Boolean = 3, 24 | Word = 128, 25 | WordAssign = 129, 26 | WordDeclare = 130, 27 | String = 131, 28 | NativeHandle = 132, 29 | Array = 388, 30 | Placeholder = 389, 31 | SplicePlaceholder = 390 32 | }; 33 | 34 | inline bool isHeapType(ValueType type) { return (((int)(type)) & 128) != 0; } 35 | inline bool isArrayType(ValueType type) { return (((int)(type)) & 256) != 0; } 36 | 37 | typedef struct ExecutionResult (*NativeWord)(struct Interpreter &); 38 | 39 | struct Value { 40 | ValueType type = ValueType::Nil; 41 | union { 42 | bool booleanValue; 43 | int64_t numericValue; 44 | Object *object; 45 | struct String *str; 46 | struct Array *arr; 47 | struct NativeHandle *handle; 48 | NativeWord word; 49 | }; 50 | }; 51 | 52 | struct String : Object { 53 | virtual ~String(); 54 | virtual Object *addPointedToQueue(struct Object *head); 55 | inline const std::u32string &get() const { return str; } 56 | inline String(const std::u32string &str) { this->str = str; } 57 | inline String(std::u32string &&str) { this->str = str; } 58 | inline String(std::u32string_view sv) { str = sv; } 59 | inline String(const char32_t *s) { str = std::u32string(s); } 60 | 61 | // this is used for symbol table 62 | // as strings are interned 63 | // there is only one object representing a given string 64 | // symbol table can just find this vector directly 65 | // instead of doing costly lookup in hash map 66 | std::vector> *values = nullptr; 67 | void pushValue(Value val, size_t scope); 68 | Value &getLastValue(); 69 | void popValue(); 70 | 71 | private: 72 | std::u32string str; 73 | }; 74 | 75 | struct Array : Object { 76 | std::vector values; 77 | virtual ~Array(); 78 | virtual Object *addPointedToQueue(struct Object *head); 79 | }; 80 | 81 | struct NativeHandle : Object { 82 | virtual ~NativeHandle(); 83 | virtual std::u32string toString(); 84 | virtual void destruct(); 85 | virtual Object *addPointedToQueue(struct Object *head); 86 | 87 | protected: 88 | bool alive = true; 89 | }; 90 | -------------------------------------------------------------------------------- /include/dbg.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef NDEBUG 4 | #define DBG_ONLY(X) 5 | #else 6 | #define DBG_ONLY(X) X 7 | #endif -------------------------------------------------------------------------------- /include/expect.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __GNUC__ 4 | #define likely(x) __builtin_expect(!!(x), 1) 5 | #define unlikely(x) __builtin_expect(!!(x), 0) 6 | #else 7 | #define likely(x) x 8 | #define unlikely(x) x 9 | #endif 10 | 11 | #define if_likely(x) if (likely(x)) 12 | #define if_unlikely(x) if (unlikely(x)) -------------------------------------------------------------------------------- /include/io/fileio.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | std::optional readFile(const char *filename); 7 | bool writeFile(const std::u32string &name, const std::u32string &contents); -------------------------------------------------------------------------------- /include/io/glob.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #ifdef _WIN32 5 | #include 6 | #else 7 | #include 8 | #endif 9 | 10 | namespace glob { 11 | 12 | class glob { 13 | public: 14 | glob(const std::string &pattern); 15 | ~glob(); 16 | 17 | operator bool() const { return is_valid(); } 18 | 19 | void open(const std::string &pattern); 20 | void close(); 21 | 22 | std::string current_match() const; 23 | bool next(); 24 | bool is_valid() const; 25 | 26 | private: 27 | glob(const glob &) = delete; 28 | void operator=(const glob &) = delete; 29 | 30 | private: 31 | #ifdef _WIN32 32 | HANDLE find_handle_; 33 | WIN32_FIND_DATA find_data_; 34 | bool ok_; 35 | #else 36 | std::string file_pattern_; 37 | DIR *dir_; 38 | struct dirent *dir_entry_; 39 | #endif 40 | }; 41 | 42 | } // namespace glob 43 | -------------------------------------------------------------------------------- /include/io/lexer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | enum class LexemeType { 8 | OpenSquareBrace, 9 | CloseSquareBrace, 10 | OpenCurlyBrace, 11 | CloseCurlyBrace, 12 | OpenCircleBrace, 13 | CloseCircleBrace, 14 | Number, 15 | Word, 16 | WordAssignment, 17 | WordDeclare, 18 | String 19 | }; 20 | 21 | struct Lexeme { 22 | LexemeType type; 23 | std::u32string_view val; 24 | size_t pos; 25 | }; 26 | 27 | struct LexResult { 28 | bool error; 29 | int64_t errorPos; 30 | std::u32string errorDescription; 31 | std::vector lexems; 32 | }; 33 | 34 | inline bool isValidIdentStart(char32_t val) { 35 | return val == U'_' || (U'a' <= val && val <= U'z') || 36 | (U'A' <= val && val <= U'Z'); 37 | } 38 | 39 | inline bool isNumericChar(char32_t val) { return (U'0' <= val && val <= U'9'); } 40 | 41 | inline bool isValidIdentChar(char32_t val) { 42 | return isValidIdentStart(val) || isNumericChar(val); 43 | } 44 | 45 | inline bool isWhitespace(char32_t val) { 46 | return val == U' ' || val == U'\t' || val == U'\n' || val == U'\r'; 47 | } 48 | 49 | LexResult lex(const std::u32string& str); -------------------------------------------------------------------------------- /include/io/parser.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | struct ParseResult { 8 | enum class Status { Success, LexerError, ParserError } status; 9 | inline bool isError() { return status != Status::Success; } 10 | std::u32string description; 11 | size_t errorPos; 12 | Array *code; 13 | }; 14 | 15 | ParseResult parse(const std::u32string &str, Interpreter &h); -------------------------------------------------------------------------------- /include/io/prettyprint.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | std::u32string prettyprint(Value val, Interpreter& interp); -------------------------------------------------------------------------------- /include/io/processes.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct ProcessInvokationRequest { 6 | std::u32string name; 7 | }; 8 | 9 | struct ProcessInvokationResponce { 10 | std::u32string out; 11 | int errorCode; 12 | bool error; 13 | }; 14 | 15 | ProcessInvokationResponce executeProcess( 16 | const ProcessInvokationRequest &command); -------------------------------------------------------------------------------- /include/io/strings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | inline int64_t parseIntFrom(const std::u32string_view &view) { 8 | bool sign = false; 9 | int64_t result = 0; 10 | for (char32_t c : view) { 11 | if (c == U'-') { 12 | sign = true; 13 | } else { 14 | result = result * 10 + (int64_t)(c - U'0'); 15 | } 16 | } 17 | if (sign) { 18 | result *= -1; 19 | } 20 | return result; 21 | } 22 | 23 | String *convertFromBackslashed(const std::u32string_view &view, Heap &h); 24 | std::u32string convertToBackslashed(const std::u32string &str); -------------------------------------------------------------------------------- /include/io/termio.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | void initREPL(Interpreter& interpreter); 7 | std::u32string readLine(const std::u32string& prompt, 8 | bool disableAutocomplete = false); 9 | void print(const std::u32string& str); -------------------------------------------------------------------------------- /include/linenoise.h: -------------------------------------------------------------------------------- 1 | /* linenoise.h -- guerrilla line editing library against the idea that a 2 | * line editing lib needs to be 20,000 lines of C code. 3 | * 4 | * See linenoise.c for more information. 5 | * 6 | * Copyright (c) 2010, Salvatore Sanfilippo 7 | * Copyright (c) 2010, Pieter Noordhuis 8 | * 9 | * All rights reserved. 10 | * 11 | * Redistribution and use in source and binary forms, with or without 12 | * modification, are permitted provided that the following conditions are met: 13 | * 14 | * * Redistributions of source code must retain the above copyright notice, 15 | * this list of conditions and the following disclaimer. 16 | * * Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * * Neither the name of Redis nor the names of its contributors may be used 20 | * to endorse or promote products derived from this software without 21 | * specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 24 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 27 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 28 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 29 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 30 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 31 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 32 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | * POSSIBILITY OF SUCH DAMAGE. 34 | */ 35 | 36 | #ifndef __LINENOISE_H 37 | #define __LINENOISE_H 38 | 39 | #define LINENOISE_VERSION "1.0.0" 40 | #define LINENOISE_VERSION_MAJOR 1 41 | #define LINENOISE_VERSION_MINOR 1 42 | 43 | #ifdef __cplusplus 44 | extern "C" { 45 | #endif 46 | 47 | typedef struct linenoiseCompletions linenoiseCompletions; 48 | 49 | typedef void(linenoiseCompletionCallback)(const char*, linenoiseCompletions*); 50 | void linenoiseSetCompletionCallback(linenoiseCompletionCallback* fn); 51 | void linenoiseAddCompletion(linenoiseCompletions* lc, const char* str); 52 | 53 | char* linenoise(const char* prompt); 54 | void linenoisePreloadBuffer(const char* preloadText); 55 | int linenoiseHistoryAdd(const char* line); 56 | int linenoiseHistorySetMaxLen(int len); 57 | char* linenoiseHistoryLine(int index); 58 | int linenoiseHistorySave(const char* filename); 59 | int linenoiseHistoryLoad(const char* filename); 60 | void linenoiseHistoryFree(void); 61 | void linenoiseClearScreen(void); 62 | void linenoiseSetMultiLine(int ml); 63 | void linenoisePrintKeyCodes(void); 64 | /* the following are extensions to the original linenoise API */ 65 | int linenoiseInstallWindowChangeHandler(void); 66 | /* returns type of key pressed: 1 = CTRL-C, 2 = CTRL-D, 0 = other */ 67 | int linenoiseKeyType(void); 68 | void linenoisePrintUTF32(char32_t* text, int len32); 69 | 70 | #ifdef __cplusplus 71 | } 72 | #endif 73 | 74 | #endif /* __LINENOISE_H */ -------------------------------------------------------------------------------- /include/prelude/repl.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | std::u32string getPreludeREPLSource(); 4 | -------------------------------------------------------------------------------- /include/std/arith.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void addArithNativeWords(Interpreter &interpreter); -------------------------------------------------------------------------------- /include/std/arrays.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void addArrayManipulationNativeWords(Interpreter& interp); -------------------------------------------------------------------------------- /include/std/boolean.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void addBooleanNativeWords(Interpreter& interp); -------------------------------------------------------------------------------- /include/std/comparisons.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void addComparisonsNativeWords(Interpreter& interpreter); -------------------------------------------------------------------------------- /include/std/controlflow.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void addControlFlowNativeWords(Interpreter& interp); -------------------------------------------------------------------------------- /include/std/indexing.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void addIndexingNativeWords(Interpreter& interp); -------------------------------------------------------------------------------- /include/std/operators.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define MAKE_FROM_UNARY_OPERATOR(name, op) \ 6 | ExecutionResult name(struct Interpreter& interp) { \ 7 | if_unlikely(!interp.evalStack.assertDepth(1)) { \ 8 | return EvalStackUnderflow(); \ 9 | } \ 10 | Value first = interp.evalStack.popBack().value(); \ 11 | Value result = op(first, interp); \ 12 | interp.evalStack.pushBack(result); \ 13 | return Success(); \ 14 | } 15 | 16 | #define MAKE_FROM_BINARY_OPERATOR(name, op) \ 17 | ExecutionResult name(struct Interpreter& interp) { \ 18 | if_unlikely(!interp.evalStack.assertDepth(2)) { \ 19 | return EvalStackUnderflow(); \ 20 | } \ 21 | Value second = interp.evalStack.popBack().value(); \ 22 | Value first = interp.evalStack.popBack().value(); \ 23 | Value result = op(first, second, interp); \ 24 | interp.evalStack.pushBack(result); \ 25 | return Success(); \ 26 | } 27 | 28 | #define MAKE_FROM_TERNARY_OPERATOR(name, op) \ 29 | ExecutionResult name(struct Interpreter& interp) { \ 30 | if_unlikely(!interp.evalStack.assertDepth(3)) { \ 31 | return EvalStackUnderflow(); \ 32 | } \ 33 | Value third = interp.evalStack.popBack().value(); \ 34 | Value second = interp.evalStack.popBack().value(); \ 35 | Value third = interp.evalStack.popBack().value(); \ 36 | Value result = op(first, second, third, interp); \ 37 | interp.evalStack.pushBack(result); \ 38 | return Success(); \ 39 | } 40 | -------------------------------------------------------------------------------- /include/std/os.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void addOSModuleNativeWords(Interpreter& interp); -------------------------------------------------------------------------------- /include/std/stack.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void addStackManipNativeWords(Interpreter& interp); -------------------------------------------------------------------------------- /include/std/strings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void addStringManipulationNativeWords(Interpreter& interp); -------------------------------------------------------------------------------- /include/std/templates.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void addTemplatesNativeWords(Interpreter& interp); -------------------------------------------------------------------------------- /include/std/typing.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void addTypingNativeWords(Interpreter& interp); -------------------------------------------------------------------------------- /src/core/callstack.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | CallStack::CallStack(size_t recLimit) { 4 | max = recLimit; 5 | callStack = new Array*[recLimit]; 6 | } 7 | 8 | bool CallStack::pushFrame(Array* arr) { 9 | if (current == max) { 10 | return false; 11 | } 12 | callStack[current++] = arr; 13 | return true; 14 | } 15 | 16 | void CallStack::popFrame() { current--; } 17 | 18 | void CallStack::registerRootMarker(Heap& h) { 19 | h.insertRootMarker([this](Heap& heap) { 20 | for (size_t i = 0; i < current; ++i) { 21 | heap.markObject(callStack[i]); 22 | } 23 | }); 24 | } -------------------------------------------------------------------------------- /src/core/evalstack.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | EvaluationStack::EvaluationStack() { stack.reserve(1024); } 4 | 5 | void EvaluationStack::registerRootMarker(Heap &heap) { 6 | heap.insertRootMarker([this](Heap &heap) { 7 | for (const auto &val : stack) { 8 | if (isHeapType(val.type) && !(val.object->marked)) { 9 | heap.markObject(val.object); 10 | } 11 | } 12 | }); 13 | } 14 | 15 | std::optional EvaluationStack::popBack() { 16 | if (stack.size() <= barrier) { 17 | return std::optional(); 18 | } 19 | Value result = stack.back(); 20 | stack.pop_back(); 21 | return result; 22 | } 23 | 24 | void EvaluationStack::pushBack(Value val) { stack.push_back(val); } 25 | 26 | void EvaluationStack::clear() { 27 | stack.resize(barrier); 28 | } 29 | 30 | bool EvaluationStack::assertDepth(size_t count) const { 31 | return stack.size() - barrier >= count; 32 | } 33 | 34 | size_t EvaluationStack::getBarrier() { return barrier; } 35 | 36 | void EvaluationStack::setBarrier(size_t size) { barrier = size; } 37 | 38 | size_t EvaluationStack::getStackSize() { return stack.size(); } 39 | 40 | void EvaluationStack::resize(size_t new_size) { stack.resize(new_size); } 41 | -------------------------------------------------------------------------------- /src/core/heap.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | Heap::Heap() { 10 | head = nullptr; 11 | objectCount = 0; 12 | // give some room before gc starts 13 | prevCount = 128; 14 | } 15 | 16 | void Heap::insertObject(Object *obj) { 17 | obj->next = head; 18 | head = obj; 19 | } 20 | 21 | size_t Heap::insertRootMarker(RootMarker marker) { 22 | rootMarkers.push_back(marker); 23 | return rootMarkers.size() - 1; 24 | } 25 | 26 | void Heap::markObject(Object *obj) { 27 | Object *queue_head = obj; 28 | Object *queue_tail = obj; 29 | queue_tail->next_to_scan = nullptr; 30 | obj->marked = true; 31 | while (queue_head != nullptr) { 32 | queue_tail = queue_head->addPointedToQueue(queue_tail); 33 | queue_head = queue_head->next_to_scan; 34 | } 35 | } 36 | 37 | void Heap::collectGarbage() { 38 | for (const RootMarker &marker : rootMarkers) { 39 | marker(*this); 40 | } 41 | // we are handling symbol table stacks separately 42 | for (String *string : pool) { 43 | if (string->values != nullptr) { 44 | string->marked = true; 45 | for (auto val : *string->values) { 46 | if (isHeapType(val.first.type) && !val.first.object->marked) { 47 | markObject(val.first.object); 48 | } 49 | } 50 | } 51 | } 52 | Object *current = head; 53 | Object *prev = nullptr; 54 | while (current != nullptr) { 55 | if (current->marked) { 56 | current->marked = false; 57 | current->next_to_scan = nullptr; 58 | current = current->next; 59 | } else { 60 | Object *toDelete = current; 61 | current = current->next; 62 | if (prev == nullptr) { 63 | head = current; 64 | } else { 65 | prev->next = current; 66 | } 67 | --objectCount; 68 | delete toDelete; 69 | } 70 | } 71 | // Handling strings (they are referenced by pool instead of list) 72 | for (auto iter = pool.begin(); iter != pool.end();) { 73 | if ((*iter)->marked) { 74 | (*iter)->marked = false; 75 | ++iter; 76 | } else { 77 | auto toDelete = iter; 78 | ++iter; 79 | String *str = *toDelete; 80 | pool.erase(toDelete); 81 | --objectCount; 82 | delete str; 83 | } 84 | } 85 | prevCount = objectCount; 86 | } 87 | 88 | String *Heap::makeStringObject(const std::u32string &val) { 89 | return makeStringObject(std::u32string_view(val)); 90 | } 91 | 92 | String *Heap::makeStringObject(const char32_t *val) { 93 | return makeStringObject(std::u32string_view(val)); 94 | } 95 | 96 | String *Heap::makeStringObject(const std::u32string_view &val) { 97 | String *obj = new String(val); 98 | obj->marked = false; 99 | obj->next = obj->next_to_scan = nullptr; 100 | auto result = pool.insert(obj); 101 | if (result.second) { 102 | objectCount++; 103 | return obj; 104 | } else { 105 | delete obj; 106 | return *result.first; 107 | } 108 | } 109 | 110 | String *Heap::makeStringObject(std::u32string &&val) { 111 | String *obj = new String(val); 112 | obj->marked = false; 113 | obj->next = obj->next_to_scan = nullptr; 114 | auto result = pool.insert(obj); 115 | if (result.second) { 116 | objectCount++; 117 | return obj; 118 | } else { 119 | delete obj; 120 | return *result.first; 121 | } 122 | } 123 | 124 | Array *Heap::makeArrayObject(Value defaultVal, size_t size) { 125 | Array *arr = new Array; 126 | objectCount++; 127 | arr->marked = false; 128 | arr->next = arr->next_to_scan = nullptr; 129 | arr->values = std::vector(size, defaultVal); 130 | insertObject(arr); 131 | return arr; 132 | } 133 | 134 | Array *Heap::shallowCopy(Array *other) { 135 | Array *arr = new Array; 136 | objectCount++; 137 | arr->marked = false; 138 | arr->next = arr->next_to_scan = nullptr; 139 | arr->values.resize(other->values.size()); 140 | for (size_t i = 0; i < other->values.size(); ++i) { 141 | arr->values[i] = other->values[i]; 142 | } 143 | insertObject(arr); 144 | return arr; 145 | } 146 | 147 | Array *Heap::deepCopy(Array *other) { 148 | std::unordered_map encountered; 149 | std::queue> copyTasks; 150 | Array *result = makeArrayObject(Value(), other->values.size()); 151 | copyTasks.push(std::pair(result, other)); 152 | while (!copyTasks.empty()) { 153 | std::pair copyTask = copyTasks.back(); 154 | encountered[copyTask.second] = copyTask.first; 155 | copyTasks.pop(); 156 | for (size_t i = 0; i < copyTask.second->values.size(); ++i) { 157 | copyTask.first->values[i] = copyTask.second->values[i]; 158 | if (isArrayType(copyTask.first->values[i].type)) { 159 | Array *inner = copyTask.first->values[i].arr; 160 | if (encountered.find(inner) != encountered.end()) { 161 | copyTask.first->values[i].arr = encountered[inner]; 162 | } else { 163 | std::pair newCopyTask; 164 | newCopyTask.first = 165 | makeArrayObject(Value(), inner->values.size()); 166 | newCopyTask.second = inner; 167 | copyTasks.push(newCopyTask); 168 | copyTask.first->values[i].arr = newCopyTask.first; 169 | } 170 | } 171 | } 172 | } 173 | return result; 174 | } 175 | 176 | void Heap::runGCIfOverThreshold() { 177 | if (objectCount > 2 * prevCount && objectCount > 256) { 178 | // collectGarbage(); 179 | } 180 | } -------------------------------------------------------------------------------- /src/core/interpreter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | Success::Success() { 5 | result = ExecutionResultType::Success; 6 | error = U"Success"; 7 | } 8 | 9 | EvalStackUnderflow::EvalStackUnderflow() { 10 | result = ExecutionResultType::Error; 11 | error = U"Evaluation stack underflow"; 12 | } 13 | 14 | CallStackOverflow::CallStackOverflow() { 15 | result = ExecutionResultType::Error; 16 | error = U"Call stack overflow"; 17 | } 18 | 19 | TypeError::TypeError() { 20 | result = ExecutionResultType::Error; 21 | error = U"Type error"; 22 | } 23 | 24 | Interpreter::Interpreter(size_t maxRecursionDepth) 25 | : symTable(maxRecursionDepth), callStack(maxRecursionDepth) { 26 | evalStack.registerRootMarker(heap); 27 | callStack.registerRootMarker(heap); 28 | heap.insertRootMarker([this](Heap& h) { 29 | for (const auto& symbol : stringsToSymbols) { 30 | h.markObject(symbol.first); 31 | } 32 | }); 33 | } 34 | 35 | void Interpreter::defineNativeWord(const std::u32string& name, 36 | NativeWord word) { 37 | String* str = heap.makeStringObject(name); 38 | stringsToSymbols[str] = word; 39 | symbolsToStrings[word] = str; 40 | } 41 | 42 | NativeWord Interpreter::queryNativeWord(String* str) { 43 | if (stringsToSymbols.find(str) == stringsToSymbols.end()) { 44 | return nullptr; 45 | } 46 | return stringsToSymbols[str]; 47 | } 48 | 49 | ExecutionResult Interpreter::callInterpreter(Array* code, bool newScope) { 50 | if_unlikely(!callStack.pushFrame(code)) { return CallStackOverflow(); } 51 | if (newScope) { 52 | symTable.createScope(); 53 | } 54 | for (size_t i = 0; i < code->values.size(); ++i) { 55 | Value ins = code->values[i]; 56 | // execute instruction depending on its type 57 | switch (ins.type) { 58 | case ValueType::Nil: 59 | case ValueType::Boolean: 60 | case ValueType::Numeric: 61 | case ValueType::String: 62 | case ValueType::Placeholder: 63 | case ValueType::Array: 64 | case ValueType::NativeHandle: 65 | case ValueType::SplicePlaceholder: 66 | evalStack.pushBack(ins); 67 | break; 68 | case ValueType::Word: { 69 | Value val = symTable.getVariable(ins.str); 70 | evalStack.pushBack(val); 71 | } break; 72 | case ValueType::WordAssign: 73 | case ValueType::WordDeclare: { 74 | std::optional valOptional = evalStack.popBack(); 75 | if_unlikely(!valOptional) { return EvalStackUnderflow(); } 76 | Value val = valOptional.value(); 77 | if (ins.type == ValueType::WordAssign) { 78 | symTable.setVariable(ins.str, val); 79 | } else { 80 | symTable.declareVariable(ins.str, val); 81 | } 82 | } break; 83 | case ValueType::NativeWord: { 84 | ExecutionResult result = ins.word(*this); 85 | if (result.result != ExecutionResultType::Success) { 86 | if (newScope) { 87 | symTable.leaveScope(); 88 | } 89 | callStack.popFrame(); 90 | return result; 91 | } 92 | // check for garbage collection 93 | heap.runGCIfOverThreshold(); 94 | } break; 95 | } 96 | } 97 | if (newScope) { 98 | symTable.leaveScope(); 99 | } 100 | callStack.popFrame(); 101 | return Success(); 102 | } -------------------------------------------------------------------------------- /src/core/symtable.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | void SymbolTable::createScope() { numDefined.push_back(0); } 7 | 8 | void SymbolTable::leaveScope() { 9 | for (size_t i = declared.size() - numDefined.back(); i < declared.size(); 10 | ++i) { 11 | declared[i]->popValue(); 12 | } 13 | declared.resize(declared.size() - numDefined.back()); 14 | numDefined.pop_back(); 15 | } 16 | 17 | void SymbolTable::declareVariable(String* name, Value val) { 18 | if (name->values == nullptr || 19 | name->values->back().second != numDefined.size()) { 20 | // variable was not declared in this scope yet 21 | numDefined[numDefined.size() - 1]++; 22 | declared.push_back(name); 23 | name->pushValue(val, numDefined.size()); 24 | } else { 25 | name->getLastValue() = val; 26 | } 27 | } 28 | 29 | void SymbolTable::setVariable(String* name, Value val) { 30 | if (name->values == nullptr) { 31 | name->pushValue(val, numDefined.size()); 32 | } else { 33 | name->getLastValue() = val; 34 | } 35 | } 36 | 37 | Value SymbolTable::getVariable(String* name) { 38 | if (name->values == nullptr) { 39 | return Value(); 40 | } else { 41 | return name->getLastValue(); 42 | } 43 | } -------------------------------------------------------------------------------- /src/core/values.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | Object::~Object() {} 8 | 9 | String::~String() {} 10 | 11 | Array::~Array() {} 12 | 13 | Object *Array::addPointedToQueue(Object *start) { 14 | Object *new_tail = start; 15 | for (size_t i = 0; i < values.size(); ++i) { 16 | if (isHeapType(values[i].type) && !(values[i].object->marked)) { 17 | new_tail->next_to_scan = values[i].object; 18 | new_tail = values[i].object; 19 | new_tail->marked = true; 20 | new_tail->next_to_scan = nullptr; 21 | } 22 | } 23 | return new_tail; 24 | } 25 | 26 | Object *String::addPointedToQueue(Object *start) { return start; } 27 | 28 | void String::pushValue(Value val, size_t scope) { 29 | if (values == nullptr) { 30 | values = new std::vector>; 31 | } 32 | values->push_back(std::pair(val, scope)); 33 | } 34 | 35 | Value &String::getLastValue() { 36 | if (values == nullptr) { 37 | throw std::runtime_error("no reference on the stack"); 38 | } 39 | return std::get<0>(values->back()); 40 | } 41 | 42 | void String::popValue() { 43 | values->pop_back(); 44 | if (values->empty()) { 45 | delete values; 46 | values = nullptr; 47 | } 48 | } 49 | 50 | NativeHandle::~NativeHandle() { 51 | if (alive) { 52 | destruct(); 53 | } 54 | } 55 | 56 | std::u32string NativeHandle::toString() { return U""; } 57 | 58 | void NativeHandle::destruct() {} 59 | 60 | Object *NativeHandle::addPointedToQueue(struct Object *head) { return head; } -------------------------------------------------------------------------------- /src/io/encodings.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | using namespace linenoise_ng; 7 | 8 | #ifdef __WIN32 9 | #define _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS 10 | #endif 11 | 12 | std::string fromUTF32(const std::u32string &s) { 13 | std::wstring_convert, char32_t> conv; 14 | return conv.to_bytes(s); 15 | } 16 | 17 | std::u32string toUTF32(const std::string &s) { 18 | std::wstring_convert, char32_t> conv; 19 | return conv.from_bytes(s); 20 | } 21 | 22 | bool validUTF32(char32_t ch) { 23 | char32_t utf32buf[2] = {ch, U'\0'}; 24 | char utf8buf[5]; 25 | UTF32 *sourceStart = (UTF32 *)utf32buf; 26 | UTF32 *sourceEnd = (UTF32 *)(utf32buf + 1); 27 | UTF8 *targetStart = (UTF8 *)utf8buf; 28 | UTF8 *targetEnd = (UTF8 *)(utf8buf + 5); 29 | 30 | ConversionResult st = 31 | ConvertUTF32toUTF8((const UTF32 **)&sourceStart, sourceEnd, 32 | &targetStart, targetEnd, strictConversion); 33 | return st == conversionOK; 34 | } -------------------------------------------------------------------------------- /src/io/fileio.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | std::u32string toUTF32(const std::string &s); 6 | std::string fromUTF32(const std::u32string &s); 7 | 8 | std::optional readFile(const char *filename) { 9 | std::ifstream input(filename); 10 | if (!input.is_open()) { 11 | return std::optional(); 12 | } 13 | std::stringstream buffer; 14 | buffer << input.rdbuf(); 15 | 16 | return toUTF32(buffer.str()); 17 | } 18 | 19 | bool writeFile(const std::u32string &name, const std::u32string &contents) { 20 | std::ofstream output(fromUTF32(name)); 21 | if (!output.is_open()) { 22 | return false; 23 | } 24 | output << fromUTF32(contents); 25 | if (output.bad()) { 26 | return false; 27 | } 28 | return true; 29 | } -------------------------------------------------------------------------------- /src/io/glob.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #ifndef _WIN32 3 | #include 4 | #endif 5 | 6 | namespace glob { 7 | 8 | #ifdef _WIN32 9 | 10 | glob::glob(const std::string &pattern) 11 | : ok_(false), find_handle_(INVALID_HANDLE_VALUE) { 12 | open(pattern); 13 | } 14 | 15 | glob::~glob() { close(); } 16 | 17 | void glob::open(const std::string &pattern) { 18 | find_handle_ = FindFirstFileA(pattern.c_str(), &find_data_); 19 | ok_ = find_handle_ != INVALID_HANDLE_VALUE; 20 | } 21 | 22 | void glob::close() { 23 | if (find_handle_ != INVALID_HANDLE_VALUE) { 24 | FindClose(find_handle_); 25 | find_handle_ = INVALID_HANDLE_VALUE; 26 | ok_ = false; 27 | } 28 | } 29 | 30 | std::string glob::current_match() const { return find_data_.cFileName; } 31 | 32 | bool glob::is_valid() const { return ok_; } 33 | 34 | bool glob::next() { 35 | ok_ = FindNextFileA(find_handle_, &find_data_) != 0; 36 | return ok_; 37 | } 38 | 39 | #else // _WIN32 40 | 41 | namespace { 42 | 43 | std::pair split_path(const std::string &path) { 44 | std::string::size_type last_sep = path.find_last_of("/"); 45 | if (last_sep != std::string::npos) { 46 | return std::make_pair( 47 | std::string(path.begin(), path.begin() + last_sep), 48 | std::string(path.begin() + last_sep + 1, path.end())); 49 | } 50 | return std::make_pair(".", path); 51 | } 52 | 53 | } // namespace 54 | 55 | glob::glob(const std::string &pattern) : dir_(nullptr), dir_entry_(nullptr) { 56 | open(pattern); 57 | } 58 | 59 | glob::~glob() { close(); } 60 | 61 | void glob::open(const std::string &pattern) { 62 | auto dir_and_file = split_path(pattern); 63 | dir_ = opendir(dir_and_file.first.c_str()); 64 | file_pattern_ = dir_and_file.second; 65 | 66 | if (dir_ != nullptr) { 67 | next(); 68 | } 69 | } 70 | 71 | void glob::close() { 72 | if (dir_ != nullptr) { 73 | closedir(dir_); 74 | dir_ = nullptr; 75 | } 76 | } 77 | 78 | std::string glob::current_match() const { return dir_entry_->d_name; } 79 | 80 | bool glob::next() { 81 | while ((dir_entry_ = readdir(dir_)) != nullptr) { 82 | if (!fnmatch(file_pattern_.c_str(), dir_entry_->d_name, 83 | FNM_CASEFOLD | FNM_NOESCAPE | FNM_PERIOD)) { 84 | return true; 85 | } 86 | } 87 | return false; 88 | } 89 | 90 | bool glob::is_valid() const { return dir_ != nullptr && dir_entry_ != nullptr; } 91 | 92 | #endif // !_WIN32 93 | 94 | } // namespace glob -------------------------------------------------------------------------------- /src/io/lexer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | enum class LexerState { Undefined, String, Identifier, Number, Comment }; 4 | 5 | size_t lookupOperator(const std::u32string& str, size_t pos) { 6 | switch (str[pos]) { 7 | case U'+': 8 | return 1; 9 | case U'-': 10 | return 1; 11 | case U'*': 12 | return 1; 13 | case U'/': 14 | return 1; 15 | case U'!': 16 | if (pos == str.length() - 1) { 17 | return 1; 18 | } else if (str[pos + 1] == U'=') { 19 | return 2; 20 | } 21 | return 1; 22 | case U'@': 23 | return 1; 24 | case U',': 25 | return 1; 26 | case U'%': 27 | return 1; 28 | case U'<': 29 | if (pos == str.length() - 1) { 30 | return 1; 31 | } else if (str[pos + 1] == U'=') { 32 | return 2; 33 | } 34 | return 1; 35 | case U'>': 36 | if (pos == str.length() - 1) { 37 | return 1; 38 | } else if (str[pos + 1] == U'=') { 39 | return 2; 40 | } 41 | return 1; 42 | case U'=': 43 | if (pos == str.length() - 1) { 44 | return 0; 45 | } else if (str[pos + 1] == U'=') { 46 | return 2; 47 | } 48 | return 0; 49 | default: 50 | return 0; 51 | } 52 | } 53 | 54 | LexResult lex(const std::u32string& str) { 55 | LexResult result; 56 | result.error = false; 57 | result.errorPos = 0; 58 | std::vector& lexems = result.lexems; 59 | size_t len = str.length(); 60 | size_t bufStart = 0; 61 | LexemeType identType; 62 | LexerState state = LexerState::Undefined; 63 | for (size_t i = 0; i < len; ++i) { 64 | char32_t current = str[i]; 65 | switch (state) { 66 | case LexerState::String: 67 | if (current == '\"') { 68 | Lexeme lexeme; 69 | lexeme.type = LexemeType::String; 70 | lexeme.val = std::u32string_view(str.data() + bufStart, 71 | i - bufStart); 72 | lexeme.pos = bufStart; 73 | lexems.push_back(lexeme); 74 | state = LexerState::Undefined; 75 | } else if (current == '\\') { 76 | if (i != len - 1 && str[i + 1] == '\"') { 77 | i++; 78 | } 79 | } 80 | break; 81 | case LexerState::Identifier: 82 | if (!isValidIdentChar(current)) { 83 | Lexeme lexeme; 84 | lexeme.type = identType; 85 | lexeme.val = std::u32string_view(str.data() + bufStart, 86 | i - bufStart); 87 | lexeme.pos = bufStart; 88 | lexems.push_back(lexeme); 89 | state = LexerState::Undefined; 90 | i--; 91 | } 92 | break; 93 | case LexerState::Number: 94 | if (!isNumericChar(current)) { 95 | Lexeme lexeme; 96 | lexeme.type = LexemeType::Number; 97 | lexeme.val = std::u32string_view(str.data() + bufStart, 98 | i - bufStart); 99 | lexeme.pos = bufStart; 100 | lexems.push_back(lexeme); 101 | state = LexerState::Undefined; 102 | i--; 103 | } 104 | break; 105 | case LexerState::Comment: 106 | if (current == U'\n') { 107 | state = LexerState::Undefined; 108 | } 109 | break; 110 | case LexerState::Undefined: 111 | if (isNumericChar(current) || 112 | (current == U'-' && i != (len - 1) && 113 | isNumericChar(str[i + 1]))) { 114 | state = LexerState::Number; 115 | bufStart = i; 116 | } else if (isValidIdentStart(current)) { 117 | state = LexerState::Identifier; 118 | identType = LexemeType::Word; 119 | bufStart = i; 120 | } else if (current == U'\"') { 121 | if (i == len - 1) { 122 | result.errorPos = i; 123 | result.error = true; 124 | result.errorDescription = 125 | U"Missing string literal after \""; 126 | result.lexems.clear(); 127 | return result; 128 | } 129 | state = LexerState::String; 130 | bufStart = i + 1; 131 | } else if (current == U'[') { 132 | Lexeme lexeme; 133 | lexeme.pos = i; 134 | lexeme.type = LexemeType::OpenSquareBrace; 135 | lexems.push_back(lexeme); 136 | } else if (current == U']') { 137 | Lexeme lexeme; 138 | lexeme.pos = i; 139 | lexeme.type = LexemeType::CloseSquareBrace; 140 | lexems.push_back(lexeme); 141 | } else if (current == U'(') { 142 | Lexeme lexeme; 143 | lexeme.pos = i; 144 | lexeme.type = LexemeType::OpenCircleBrace; 145 | lexems.push_back(lexeme); 146 | } else if (current == U')') { 147 | Lexeme lexeme; 148 | lexeme.pos = i; 149 | lexeme.type = LexemeType::CloseCircleBrace; 150 | lexems.push_back(lexeme); 151 | } else if (current == U'{') { 152 | Lexeme lexeme; 153 | lexeme.pos = i; 154 | lexeme.type = LexemeType::OpenCurlyBrace; 155 | lexems.push_back(lexeme); 156 | } else if (current == U'}') { 157 | Lexeme lexeme; 158 | lexeme.pos = i; 159 | lexeme.type = LexemeType::CloseCurlyBrace; 160 | lexems.push_back(lexeme); 161 | } else if (lookupOperator(str, i) != 0) { 162 | size_t size = lookupOperator(str, i); 163 | Lexeme lexeme; 164 | lexeme.type = LexemeType::Word; 165 | lexeme.pos = i; 166 | lexeme.val = std::u32string_view(str.data() + i, size); 167 | lexems.push_back(lexeme); 168 | i += size - 1; 169 | } else if (current == U'$') { 170 | if (i == len - 1 || !isValidIdentStart(str[i + 1])) { 171 | result.errorPos = i; 172 | result.error = true; 173 | result.errorDescription = 174 | U"Missing identifier after $ sign"; 175 | result.lexems.clear(); 176 | return result; 177 | } 178 | identType = LexemeType::WordDeclare; 179 | state = LexerState::Identifier; 180 | bufStart = ++i; 181 | } else if (current == U'=') { 182 | if (i == len - 1 || !isValidIdentStart(str[i + 1])) { 183 | result.errorPos = i; 184 | result.error = true; 185 | result.errorDescription = 186 | U"Missing identifier after = sign"; 187 | result.lexems.clear(); 188 | return result; 189 | } 190 | identType = LexemeType::WordAssignment; 191 | state = LexerState::Identifier; 192 | bufStart = ++i; 193 | } else if (current == U'#') { 194 | state = LexerState::Comment; 195 | } else if (!isWhitespace(current)) { 196 | result.errorPos = i; 197 | result.error = true; 198 | result.errorDescription = U"Unexpected symbol"; 199 | result.lexems.clear(); 200 | return result; 201 | } 202 | break; 203 | } 204 | } 205 | switch (state) { 206 | case LexerState::String: 207 | result.errorPos = len; 208 | result.error = true; 209 | result.errorDescription = U"Not terminated string"; 210 | result.lexems.clear(); 211 | return result; 212 | case LexerState::Identifier: { 213 | Lexeme lexeme; 214 | lexeme.type = identType; 215 | lexeme.val = 216 | std::u32string_view(str.data() + bufStart, len - bufStart); 217 | lexeme.pos = bufStart; 218 | lexems.push_back(lexeme); 219 | break; 220 | } 221 | case LexerState::Number: { 222 | Lexeme lexeme; 223 | lexeme.type = LexemeType::Number; 224 | lexeme.val = 225 | std::u32string_view(str.data() + bufStart, len - bufStart); 226 | lexeme.pos = bufStart; 227 | lexems.push_back(lexeme); 228 | break; 229 | } 230 | default: 231 | break; 232 | } 233 | return result; 234 | } -------------------------------------------------------------------------------- /src/io/parser.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | ValueType getBraceType(LexemeType type) { 7 | if (type == LexemeType::OpenCircleBrace || 8 | type == LexemeType::CloseCircleBrace) { 9 | return ValueType::SplicePlaceholder; 10 | } else if (type == LexemeType::OpenCurlyBrace || 11 | type == LexemeType::CloseCurlyBrace) { 12 | return ValueType::Placeholder; 13 | } 14 | return ValueType::Array; 15 | } 16 | 17 | ParseResult parse(const std::u32string &str, Interpreter &interp) { 18 | ParseResult result; 19 | result.status = ParseResult::Status::Success; 20 | 21 | LexResult lexResult = lex(str); 22 | if (lexResult.error) { 23 | result.status = ParseResult::Status::LexerError; 24 | result.description = std::move(lexResult.errorDescription); 25 | result.errorPos = lexResult.errorPos; 26 | result.code = nullptr; 27 | return result; 28 | } 29 | 30 | using ParserTask = std::pair; 31 | std::stack tasks; 32 | 33 | result.code = interp.heap.makeArrayObject(Value(), 0); 34 | tasks.push(ParserTask(ValueType::Array, result.code)); 35 | 36 | for (size_t i = 0; i < lexResult.lexems.size(); ++i) { 37 | ParserTask &topTask = tasks.top(); 38 | Lexeme current = lexResult.lexems[i]; 39 | switch (current.type) { 40 | case LexemeType::OpenSquareBrace: 41 | case LexemeType::OpenCurlyBrace: 42 | case LexemeType::OpenCircleBrace: { 43 | Array *newScope = interp.heap.makeArrayObject(Value(), 0); 44 | ValueType correspondingType = getBraceType(current.type); 45 | tasks.push(ParserTask(correspondingType, newScope)); 46 | break; 47 | } 48 | case LexemeType::CloseCircleBrace: 49 | case LexemeType::CloseCurlyBrace: 50 | case LexemeType::CloseSquareBrace: { 51 | ValueType correspondingType = getBraceType(current.type); 52 | if (topTask.first != correspondingType) { 53 | result.status = ParseResult::Status::ParserError; 54 | result.description = U"Unmatched array termination"; 55 | result.errorPos = current.pos; 56 | result.code = nullptr; 57 | return result; 58 | } 59 | 60 | Value arrayLiteral; 61 | arrayLiteral.type = correspondingType; 62 | arrayLiteral.arr = topTask.second; 63 | tasks.pop(); 64 | 65 | if (tasks.empty()) { 66 | result.status = ParseResult::Status::ParserError; 67 | result.description = U"Unmatched ] symbol"; 68 | result.errorPos = current.pos; 69 | result.code = nullptr; 70 | return result; 71 | } 72 | 73 | ParserTask &newTopTask = tasks.top(); 74 | newTopTask.second->values.push_back(arrayLiteral); 75 | break; 76 | } 77 | case LexemeType::Number: { 78 | Value integerLiteral; 79 | integerLiteral.type = ValueType::Numeric; 80 | if (current.val.size() >= 2 && current.val != U"-0" && 81 | current.val.substr(0, 2) == U"-0") { 82 | result.status = ParseResult::Status::ParserError; 83 | result.description = U"Leading zeros are not permitted"; 84 | result.errorPos = current.pos; 85 | result.code = nullptr; 86 | return result; 87 | } else if (current.val.size() >= 1 && current.val != U"0" && 88 | current.val[0] == U'0') { 89 | result.status = ParseResult::Status::ParserError; 90 | result.description = U"Leading zeros are not permitted"; 91 | result.errorPos = current.pos; 92 | result.code = nullptr; 93 | return result; 94 | } 95 | integerLiteral.numericValue = parseIntFrom(current.val); 96 | topTask.second->values.push_back(integerLiteral); 97 | break; 98 | } 99 | case LexemeType::String: { 100 | Value stringLiteral; 101 | stringLiteral.type = ValueType::String; 102 | String *strObject = 103 | convertFromBackslashed(current.val, interp.heap); 104 | if (strObject == nullptr) { 105 | result.status = ParseResult::Status::ParserError; 106 | result.description = U"Invalid string literal"; 107 | result.errorPos = current.pos; 108 | result.code = nullptr; 109 | return result; 110 | } 111 | stringLiteral.str = strObject; 112 | topTask.second->values.push_back(stringLiteral); 113 | break; 114 | } 115 | case LexemeType::Word: { 116 | if (current.val == U"True" || current.val == U"False") { 117 | Value boolLiteral; 118 | boolLiteral.type = ValueType::Boolean; 119 | boolLiteral.booleanValue = current.val == U"True"; 120 | topTask.second->values.push_back(boolLiteral); 121 | } else { 122 | Value wordLiteral; 123 | String *name = interp.heap.makeStringObject(current.val); 124 | NativeWord nativeWord = interp.queryNativeWord(name); 125 | if (nativeWord != nullptr) { 126 | wordLiteral.type = ValueType::NativeWord; 127 | wordLiteral.word = nativeWord; 128 | } else { 129 | wordLiteral.type = ValueType::Word; 130 | wordLiteral.str = name; 131 | } 132 | topTask.second->values.push_back(wordLiteral); 133 | } 134 | break; 135 | } 136 | case LexemeType::WordAssignment: { 137 | Value wordLiteral; 138 | wordLiteral.type = ValueType::WordAssign; 139 | wordLiteral.str = interp.heap.makeStringObject(current.val); 140 | topTask.second->values.push_back(wordLiteral); 141 | break; 142 | } 143 | case LexemeType::WordDeclare: { 144 | Value wordLiteral; 145 | wordLiteral.type = ValueType::WordDeclare; 146 | wordLiteral.str = interp.heap.makeStringObject(current.val); 147 | topTask.second->values.push_back(wordLiteral); 148 | break; 149 | } 150 | } 151 | } 152 | if (tasks.size() > 1) { 153 | result.status = ParseResult::Status::ParserError; 154 | result.description = U"Close brackets missing"; 155 | result.errorPos = str.size(); 156 | result.code = nullptr; 157 | return result; 158 | } 159 | return result; 160 | } -------------------------------------------------------------------------------- /src/io/prettyprint.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | void prettyprintNumeric(int64_t val, std::u32string& str, 8 | bool firstCall = true) { 9 | if (val < 0) { 10 | str.push_back(U'-'); 11 | val *= -1; 12 | } 13 | if (val == 0) { 14 | if (firstCall) { 15 | str.push_back(U'0'); 16 | } 17 | return; 18 | } 19 | prettyprintNumeric(val / 10LL, str, false); 20 | str.push_back(U'0' + (char32_t)(val % 10)); 21 | } 22 | 23 | void prettyprintPrimitive(const Value& val, std::u32string& str, 24 | Interpreter& interp) { 25 | switch (val.type) { 26 | case ValueType::Boolean: 27 | if (val.booleanValue) { 28 | str.append(U"True"); 29 | } else { 30 | str.append(U"False"); 31 | } 32 | break; 33 | case ValueType::Numeric: 34 | prettyprintNumeric(val.numericValue, str); 35 | break; 36 | case ValueType::Nil: 37 | str.append(U"Nil"); 38 | break; 39 | case ValueType::String: { 40 | std::u32string backslashed = convertToBackslashed(val.str->get()); 41 | str.push_back(U'\"'); 42 | str.append(backslashed); 43 | str.push_back(U'\"'); 44 | break; 45 | } 46 | case ValueType::Word: 47 | str.append(val.str->get()); 48 | break; 49 | case ValueType::WordAssign: 50 | str.push_back(U'='); 51 | str.append(val.str->get()); 52 | break; 53 | case ValueType::WordDeclare: 54 | str.push_back(U'$'); 55 | str.append(val.str->get()); 56 | break; 57 | case ValueType::NativeWord: 58 | str.append(interp.symbolsToStrings[val.word]->get()); 59 | break; 60 | case ValueType::NativeHandle: 61 | str.append(val.handle->toString()); 62 | default: 63 | break; 64 | } 65 | } 66 | 67 | std::u32string prettyprint(Value val, Interpreter& interp) { 68 | std::u32string result; 69 | using PrintArrayTask = std::pair; 70 | std::unordered_set visited; 71 | std::stack tasks; 72 | if (!isArrayType(val.type)) { 73 | prettyprintPrimitive(val, result, interp); 74 | return result; 75 | } 76 | tasks.push(PrintArrayTask(val, 0)); 77 | while (!tasks.empty()) { 78 | PrintArrayTask& topTask = tasks.top(); 79 | visited.insert(topTask.first.arr); 80 | if (topTask.second == 0) { 81 | if (topTask.first.type == ValueType::Array) { 82 | result.append(U"["); 83 | } else if (topTask.first.type == ValueType::Placeholder) { 84 | result.append(U"{"); 85 | } else { 86 | result.append(U"("); 87 | } 88 | } 89 | if (topTask.second >= topTask.first.arr->values.size()) { 90 | if (topTask.first.type == ValueType::Array) { 91 | result.push_back(U']'); 92 | } else if (topTask.first.type == ValueType::Placeholder) { 93 | result.append(U"}"); 94 | } else { 95 | result.append(U")"); 96 | } 97 | tasks.pop(); 98 | if (!tasks.empty()) { 99 | if (tasks.top().second < tasks.top().first.arr->values.size()) { 100 | result.push_back(U' '); 101 | } 102 | } 103 | visited.erase(topTask.first.arr); 104 | continue; 105 | } 106 | Value& toPrint = topTask.first.arr->values[topTask.second]; 107 | topTask.second++; 108 | if (!isArrayType(toPrint.type)) { 109 | prettyprintPrimitive(toPrint, result, interp); 110 | if (topTask.second < topTask.first.arr->values.size()) { 111 | result.push_back(U' '); 112 | } 113 | } else { 114 | if (visited.find(toPrint.arr) != visited.end()) { 115 | if (toPrint.type == ValueType::Array) { 116 | result.append(U"[...]"); 117 | } else if (topTask.first.type == ValueType::Placeholder) { 118 | result.append(U"{...}"); 119 | } else { 120 | result.append(U"(...)"); 121 | } 122 | if (topTask.second < topTask.first.arr->values.size()) { 123 | result.push_back(U' '); 124 | } 125 | } else { 126 | tasks.push(PrintArrayTask(toPrint, 0)); 127 | } 128 | } 129 | } 130 | return result; 131 | } 132 | -------------------------------------------------------------------------------- /src/io/processes.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | std::u32string toUTF32(const std::string &s); 5 | std::string fromUTF32(const std::u32string &s); 6 | 7 | #ifdef _WIN32 8 | #define POPEN _popen 9 | #define PCLOSE _pclose 10 | #else 11 | #define POPEN popen 12 | #define PCLOSE pclose 13 | #endif 14 | 15 | ProcessInvokationResponce executeProcess( 16 | const ProcessInvokationRequest &command) { 17 | ProcessInvokationResponce result; 18 | 19 | FILE *file = POPEN(fromUTF32(command.name).c_str(), "r"); 20 | if (file == NULL) { 21 | result.error = true; 22 | return result; 23 | } 24 | char buffer[128]; 25 | std::string out; 26 | while (fgets(buffer, sizeof(buffer), file) != NULL) { 27 | out += buffer; 28 | } 29 | result.errorCode = PCLOSE(file); 30 | result.out = toUTF32(out); 31 | result.error = false; 32 | return result; 33 | } -------------------------------------------------------------------------------- /src/io/strings.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | String *convertFromBackslashed(const std::u32string_view &view, Heap &h) { 4 | std::u32string resultStr; 5 | size_t len = view.size(); 6 | for (size_t i = 0; i < len; ++i) { 7 | if (view[i] == U'\\') { 8 | if (i == len - 1) { 9 | return nullptr; 10 | } 11 | ++i; 12 | char sym = view[i]; 13 | switch (sym) { 14 | case U'n': 15 | resultStr.push_back(U'\n'); 16 | break; 17 | case U'b': 18 | resultStr.push_back(U'\b'); 19 | break; 20 | case U'r': 21 | resultStr.push_back(U'\r'); 22 | break; 23 | case U'a': 24 | resultStr.push_back(U'\a'); 25 | break; 26 | case U't': 27 | resultStr.push_back(U'\t'); 28 | break; 29 | case U'\"': 30 | resultStr.push_back(U'\"'); 31 | break; 32 | case U'\'': 33 | resultStr.push_back(U'\''); 34 | break; 35 | default: 36 | return nullptr; 37 | } 38 | } else { 39 | if (view[i] == U'\'') { 40 | return nullptr; 41 | } 42 | resultStr.push_back(view[i]); 43 | } 44 | } 45 | String *result = h.makeStringObject(resultStr); 46 | return result; 47 | } 48 | 49 | std::u32string convertToBackslashed(const std::u32string &str) { 50 | std::u32string result; 51 | size_t len = str.size(); 52 | for (size_t i = 0; i < len; ++i) { 53 | if (str[i] == '\n') { 54 | result.append(U"\\n"); 55 | } else if (str[i] == '\b') { 56 | result.append(U"\\b"); 57 | } else if (str[i] == '\r') { 58 | result.append(U"\\r"); 59 | } else if (str[i] == '\a') { 60 | result.append(U"\\a"); 61 | } else if (str[i] == '\t') { 62 | result.append(U"\\t"); 63 | } else if (str[i] == '\'') { 64 | result.append(U"\\\'"); 65 | } else if (str[i] == '\"') { 66 | result.append(U"\\\""); 67 | } else { 68 | result.push_back(str[i]); 69 | } 70 | } 71 | return result; 72 | } -------------------------------------------------------------------------------- /src/io/termio.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | std::string fromUTF32(const std::u32string &s); 11 | std::u32string toUTF32(const std::string &s); 12 | 13 | std::u32string readLine(const std::u32string &prompt, 14 | [[maybe_unused]] bool disableAutocomplete) { 15 | std::string promptUtf8 = fromUTF32(prompt); 16 | char *raw = linenoise(promptUtf8.c_str()); 17 | std::string result(raw); 18 | free(raw); 19 | linenoiseHistoryAdd(result.c_str()); 20 | return toUTF32(result); 21 | } 22 | 23 | void print(const std::u32string &str) { 24 | std::u32string copy = str; 25 | linenoisePrintUTF32(copy.data(), copy.size()); 26 | std::flush(std::cout); 27 | } 28 | 29 | void initREPL([[maybe_unused]] Interpreter &interpreter) { 30 | linenoiseInstallWindowChangeHandler(); 31 | } 32 | -------------------------------------------------------------------------------- /src/linenoise/wcwidth.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This is an implementation of wcwidth() and wcswidth() (defined in 3 | * IEEE Std 1002.1-2001) for Unicode. 4 | * 5 | * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html 6 | * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html 7 | * 8 | * In fixed-width output devices, Latin characters all occupy a single 9 | * "cell" position of equal width, whereas ideographic CJK characters 10 | * occupy two such cells. Interoperability between terminal-line 11 | * applications and (teletype-style) character terminals using the 12 | * UTF-8 encoding requires agreement on which character should advance 13 | * the cursor by how many cell positions. No established formal 14 | * standards exist at present on which Unicode character shall occupy 15 | * how many cell positions on character terminals. These routines are 16 | * a first attempt of defining such behavior based on simple rules 17 | * applied to data provided by the Unicode Consortium. 18 | * 19 | * For some graphical characters, the Unicode standard explicitly 20 | * defines a character-cell width via the definition of the East Asian 21 | * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes. 22 | * In all these cases, there is no ambiguity about which width a 23 | * terminal shall use. For characters in the East Asian Ambiguous (A) 24 | * class, the width choice depends purely on a preference of backward 25 | * compatibility with either historic CJK or Western practice. 26 | * Choosing single-width for these characters is easy to justify as 27 | * the appropriate long-term solution, as the CJK practice of 28 | * displaying these characters as double-width comes from historic 29 | * implementation simplicity (8-bit encoded characters were displayed 30 | * single-width and 16-bit ones double-width, even for Greek, 31 | * Cyrillic, etc.) and not any typographic considerations. 32 | * 33 | * Much less clear is the choice of width for the Not East Asian 34 | * (Neutral) class. Existing practice does not dictate a width for any 35 | * of these characters. It would nevertheless make sense 36 | * typographically to allocate two character cells to characters such 37 | * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be 38 | * represented adequately with a single-width glyph. The following 39 | * routines at present merely assign a single-cell width to all 40 | * neutral characters, in the interest of simplicity. This is not 41 | * entirely satisfactory and should be reconsidered before 42 | * establishing a formal standard in this area. At the moment, the 43 | * decision which Not East Asian (Neutral) characters should be 44 | * represented by double-width glyphs cannot yet be answered by 45 | * applying a simple rule from the Unicode database content. Setting 46 | * up a proper standard for the behavior of UTF-8 character terminals 47 | * will require a careful analysis not only of each Unicode character, 48 | * but also of each presentation form, something the author of these 49 | * routines has avoided to do so far. 50 | * 51 | * http://www.unicode.org/unicode/reports/tr11/ 52 | * 53 | * Markus Kuhn -- 2007-05-26 (Unicode 5.0) 54 | * 55 | * Permission to use, copy, modify, and distribute this software 56 | * for any purpose and without fee is hereby granted. The author 57 | * disclaims all warranties with regard to this software. 58 | * 59 | * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c 60 | */ 61 | 62 | #include 63 | #include 64 | #include 65 | 66 | namespace linenoise_ng { 67 | 68 | struct interval { 69 | char32_t first; 70 | char32_t last; 71 | }; 72 | 73 | /* auxiliary function for binary search in interval table */ 74 | static int bisearch(char32_t ucs, const struct interval *table, int max) { 75 | int min = 0; 76 | int mid; 77 | 78 | if (ucs < table[0].first || ucs > table[max].last) 79 | return 0; 80 | while (max >= min) { 81 | mid = (min + max) / 2; 82 | if (ucs > table[mid].last) 83 | min = mid + 1; 84 | else if (ucs < table[mid].first) 85 | max = mid - 1; 86 | else 87 | return 1; 88 | } 89 | 90 | return 0; 91 | } 92 | 93 | 94 | /* The following two functions define the column width of an ISO 10646 95 | * character as follows: 96 | * 97 | * - The null character (U+0000) has a column width of 0. 98 | * 99 | * - Other C0/C1 control characters and DEL will lead to a return 100 | * value of -1. 101 | * 102 | * - Non-spacing and enclosing combining characters (general 103 | * category code Mn or Me in the Unicode database) have a 104 | * column width of 0. 105 | * 106 | * - SOFT HYPHEN (U+00AD) has a column width of 1. 107 | * 108 | * - Other format characters (general category code Cf in the Unicode 109 | * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0. 110 | * 111 | * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) 112 | * have a column width of 0. 113 | * 114 | * - Spacing characters in the East Asian Wide (W) or East Asian 115 | * Full-width (F) category as defined in Unicode Technical 116 | * Report #11 have a column width of 2. 117 | * 118 | * - All remaining characters (including all printable 119 | * ISO 8859-1 and WGL4 characters, Unicode control characters, 120 | * etc.) have a column width of 1. 121 | * 122 | * This implementation assumes that wchar_t characters are encoded 123 | * in ISO 10646. 124 | */ 125 | 126 | int mk_wcwidth(char32_t ucs) 127 | { 128 | /* sorted list of non-overlapping intervals of non-spacing characters */ 129 | /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ 130 | static const struct interval combining[] = { 131 | { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 }, 132 | { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, 133 | { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, 134 | { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 }, 135 | { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, 136 | { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, 137 | { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 }, 138 | { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D }, 139 | { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, 140 | { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, 141 | { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C }, 142 | { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, 143 | { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, 144 | { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, 145 | { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, 146 | { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D }, 147 | { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, 148 | { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 }, 149 | { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC }, 150 | { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, 151 | { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, 152 | { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, 153 | { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, 154 | { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, 155 | { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, 156 | { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, 157 | { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, 158 | { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, 159 | { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, 160 | { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F }, 161 | { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, 162 | { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, 163 | { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, 164 | { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, 165 | { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B }, 166 | { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, 167 | { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, 168 | { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF }, 169 | { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 }, 170 | { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F }, 171 | { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, 172 | { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, 173 | { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, 174 | { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F }, 175 | { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 }, 176 | { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD }, 177 | { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F }, 178 | { 0xE0100, 0xE01EF } 179 | }; 180 | 181 | /* test for 8-bit control characters */ 182 | if (ucs == 0) 183 | return 0; 184 | if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) 185 | return -1; 186 | 187 | /* binary search in table of non-spacing characters */ 188 | if (bisearch(ucs, combining, 189 | sizeof(combining) / sizeof(struct interval) - 1)) 190 | return 0; 191 | 192 | /* if we arrive here, ucs is not a combining or C0/C1 control character */ 193 | 194 | return 1 + 195 | (ucs >= 0x1100 && 196 | (ucs <= 0x115f || /* Hangul Jamo init. consonants */ 197 | ucs == 0x2329 || ucs == 0x232a || 198 | (ucs >= 0x2e80 && ucs <= 0xa4cf && 199 | ucs != 0x303f) || /* CJK ... Yi */ 200 | (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */ 201 | (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */ 202 | (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */ 203 | (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ 204 | (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */ 205 | (ucs >= 0xffe0 && ucs <= 0xffe6) || 206 | (ucs >= 0x20000 && ucs <= 0x2fffd) || 207 | (ucs >= 0x30000 && ucs <= 0x3fffd))); 208 | } 209 | 210 | 211 | int mk_wcswidth(const char32_t* pwcs, size_t n) 212 | { 213 | int w, width = 0; 214 | 215 | for (;*pwcs && n-- > 0; pwcs++) 216 | if ((w = mk_wcwidth(*pwcs)) < 0) 217 | return -1; 218 | else 219 | width += w; 220 | 221 | return width; 222 | } 223 | 224 | 225 | /* 226 | * The following functions are the same as mk_wcwidth() and 227 | * mk_wcswidth(), except that spacing characters in the East Asian 228 | * Ambiguous (A) category as defined in Unicode Technical Report #11 229 | * have a column width of 2. This variant might be useful for users of 230 | * CJK legacy encodings who want to migrate to UCS without changing 231 | * the traditional terminal character-width behaviour. It is not 232 | * otherwise recommended for general use. 233 | */ 234 | int mk_wcwidth_cjk(wchar_t ucs) 235 | { 236 | /* sorted list of non-overlapping intervals of East Asian Ambiguous 237 | * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */ 238 | static const struct interval ambiguous[] = { 239 | { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 }, 240 | { 0x00AA, 0x00AA }, { 0x00AE, 0x00AE }, { 0x00B0, 0x00B4 }, 241 | { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 }, 242 | { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 }, 243 | { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED }, 244 | { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA }, 245 | { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 }, 246 | { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B }, 247 | { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 }, 248 | { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 }, 249 | { 0x0148, 0x014B }, { 0x014D, 0x014D }, { 0x0152, 0x0153 }, 250 | { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE }, 251 | { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 }, 252 | { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA }, 253 | { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 }, 254 | { 0x02C4, 0x02C4 }, { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB }, 255 | { 0x02CD, 0x02CD }, { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB }, 256 | { 0x02DD, 0x02DD }, { 0x02DF, 0x02DF }, { 0x0391, 0x03A1 }, 257 | { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, { 0x03C3, 0x03C9 }, 258 | { 0x0401, 0x0401 }, { 0x0410, 0x044F }, { 0x0451, 0x0451 }, 259 | { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 }, 260 | { 0x201C, 0x201D }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 }, 261 | { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 }, 262 | { 0x203B, 0x203B }, { 0x203E, 0x203E }, { 0x2074, 0x2074 }, 263 | { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC }, 264 | { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 }, 265 | { 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 }, 266 | { 0x2126, 0x2126 }, { 0x212B, 0x212B }, { 0x2153, 0x2154 }, 267 | { 0x215B, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 }, 268 | { 0x2190, 0x2199 }, { 0x21B8, 0x21B9 }, { 0x21D2, 0x21D2 }, 269 | { 0x21D4, 0x21D4 }, { 0x21E7, 0x21E7 }, { 0x2200, 0x2200 }, 270 | { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, { 0x220B, 0x220B }, 271 | { 0x220F, 0x220F }, { 0x2211, 0x2211 }, { 0x2215, 0x2215 }, 272 | { 0x221A, 0x221A }, { 0x221D, 0x2220 }, { 0x2223, 0x2223 }, 273 | { 0x2225, 0x2225 }, { 0x2227, 0x222C }, { 0x222E, 0x222E }, 274 | { 0x2234, 0x2237 }, { 0x223C, 0x223D }, { 0x2248, 0x2248 }, 275 | { 0x224C, 0x224C }, { 0x2252, 0x2252 }, { 0x2260, 0x2261 }, 276 | { 0x2264, 0x2267 }, { 0x226A, 0x226B }, { 0x226E, 0x226F }, 277 | { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, { 0x2295, 0x2295 }, 278 | { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, { 0x22BF, 0x22BF }, 279 | { 0x2312, 0x2312 }, { 0x2460, 0x24E9 }, { 0x24EB, 0x254B }, 280 | { 0x2550, 0x2573 }, { 0x2580, 0x258F }, { 0x2592, 0x2595 }, 281 | { 0x25A0, 0x25A1 }, { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 }, 282 | { 0x25B6, 0x25B7 }, { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 }, 283 | { 0x25C6, 0x25C8 }, { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 }, 284 | { 0x25E2, 0x25E5 }, { 0x25EF, 0x25EF }, { 0x2605, 0x2606 }, 285 | { 0x2609, 0x2609 }, { 0x260E, 0x260F }, { 0x2614, 0x2615 }, 286 | { 0x261C, 0x261C }, { 0x261E, 0x261E }, { 0x2640, 0x2640 }, 287 | { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 }, 288 | { 0x2667, 0x266A }, { 0x266C, 0x266D }, { 0x266F, 0x266F }, 289 | { 0x273D, 0x273D }, { 0x2776, 0x277F }, { 0xE000, 0xF8FF }, 290 | { 0xFFFD, 0xFFFD }, { 0xF0000, 0xFFFFD }, { 0x100000, 0x10FFFD } 291 | }; 292 | 293 | /* binary search in table of non-spacing characters */ 294 | if (bisearch(ucs, ambiguous, 295 | sizeof(ambiguous) / sizeof(struct interval) - 1)) 296 | return 2; 297 | 298 | return mk_wcwidth(ucs); 299 | } 300 | 301 | 302 | int mk_wcswidth_cjk(const wchar_t *pwcs, size_t n) 303 | { 304 | int w, width = 0; 305 | 306 | for (;*pwcs && n-- > 0; pwcs++) 307 | if ((w = mk_wcwidth_cjk(*pwcs)) < 0) 308 | return -1; 309 | else 310 | width += w; 311 | 312 | return width; 313 | } 314 | 315 | } 316 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | void initStd(Interpreter& interp) { 23 | addArithNativeWords(interp); 24 | addComparisonsNativeWords(interp); 25 | addStackManipNativeWords(interp); 26 | addControlFlowNativeWords(interp); 27 | addOSModuleNativeWords(interp); 28 | addStringManipulationNativeWords(interp); 29 | addIndexingNativeWords(interp); 30 | addTemplatesNativeWords(interp); 31 | addArrayManipulationNativeWords(interp); 32 | addTypingNativeWords(interp); 33 | addBooleanNativeWords(interp); 34 | } 35 | 36 | void reportSyntaxError(ParseResult res) { 37 | if (res.status == ParseResult::Status::LexerError) { 38 | print(U"Lexer error at position "); 39 | } else { 40 | print(U"Parser error at position "); 41 | } 42 | std::cout << res.errorPos; 43 | print(U" : "); 44 | print(res.description); 45 | print(U"\n"); 46 | } 47 | 48 | void reportRuntimeError(ExecutionResult res, Interpreter& interp) { 49 | print(U"Runtime error: "); 50 | if (res.result == ExecutionResultType::Custom) { 51 | print(prettyprint(res.val, interp)); 52 | } else { 53 | print(std::u32string(res.error)); 54 | } 55 | print(U"\n"); 56 | } 57 | 58 | int hostRepl() { 59 | Interpreter interp(1024); 60 | // initStd requires scope for adding native words 61 | interp.symTable.createScope(); 62 | initStd(interp); 63 | initREPL(interp); 64 | ParseResult result = parse(getPreludeREPLSource(), interp); 65 | if (result.isError()) { 66 | std::cerr << "The following parser error was encountered while parsing " 67 | "REPL prelude. This is the bug in Forthscript " 68 | "interpreter. Please report it"; 69 | reportSyntaxError(result); 70 | interp.heap.collectGarbage(); 71 | return -1; 72 | } 73 | ExecutionResult res = interp.callInterpreter(result.code, false); 74 | if (res.result != ExecutionResultType::Success) { 75 | std::cerr << "Prelude has not handled the following error:\n"; 76 | reportRuntimeError(res, interp); 77 | interp.evalStack.clear(); 78 | interp.heap.collectGarbage(); 79 | return -1; 80 | } 81 | return 0; 82 | } 83 | 84 | int main(int argc, char** argv) { 85 | if (argc < 2) { 86 | return hostRepl(); 87 | } 88 | char* filename = argv[1]; 89 | Interpreter interp(1024); 90 | // initStd requires scope for adding native words 91 | interp.symTable.createScope(); 92 | initStd(interp); 93 | std::optional source = readFile(filename); 94 | if (!source.has_value()) { 95 | std::cout << "Can't open file " << filename << std::endl; 96 | return 0; 97 | } 98 | ParseResult result = parse(source.value(), interp); 99 | if (result.isError()) { 100 | reportSyntaxError(result); 101 | interp.heap.collectGarbage(); 102 | return -1; 103 | } 104 | ExecutionResult res = interp.callInterpreter(result.code, false); 105 | if (res.result != ExecutionResultType::Success) { 106 | reportRuntimeError(res, interp); 107 | interp.evalStack.clear(); 108 | interp.heap.collectGarbage(); 109 | return -1; 110 | } 111 | return 0; 112 | } 113 | -------------------------------------------------------------------------------- /src/prelude/repl.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | std::u32string getPreludeREPLSource() { 4 | // REPL source code is stored in FORTHSCRIPT_REPL_SOURCE variable 5 | // If not defined, exit 6 | #ifndef FORTHSCRIPT_REPL_SOURCE 7 | return U"\"usage: forthscript \" writeln 1 exit"; 8 | #else 9 | std::u32string toUTF32(const std::string &src); 10 | return toUTF32(FORTHSCRIPT_REPL_SOURCE); 11 | #endif 12 | } 13 | -------------------------------------------------------------------------------- /src/prelude/repl.fscript: -------------------------------------------------------------------------------- 1 | "Simple REPL implementation" drop 2 | "Bundled in preprocessor macro" drop 3 | "Normal comments are not available, so we have to improvise" drop 4 | 5 | "Display repl prompt" drop 6 | [get_stack_copy to_string "\n> " +] $__repl_prompt 7 | 8 | "Display REPL prompt and ask user for input" drop 9 | [__repl_prompt! readln parse] $__repl_get_input 10 | 11 | "Try to interpret user input. Catch type errors" drop 12 | [[__repl_get_input!,] get_stack_size 1 - inline_try not] $__repl_loop_try_iter 13 | 14 | "Try to interpret user input and print an error message on failure" drop 15 | [__repl_loop_try_iter, ["error: " swap + ". Clearing stack..." + writeln] if] $__repl_loop_iter 16 | 17 | "Run __repl_get_input forever" drop 18 | [True] [__repl_loop_iter,] inline_while 19 | -------------------------------------------------------------------------------- /src/std/arith.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | ExecutionResult addOp(Interpreter& interp) { 8 | if (!interp.evalStack.assertDepth(2)) { 9 | return EvalStackUnderflow(); 10 | } 11 | Value val2 = interp.evalStack.popBack().value(); 12 | Value val1 = interp.evalStack.popBack().value(); 13 | if (val1.type == ValueType::Numeric && val2.type == ValueType::Numeric) { 14 | Value intResult; 15 | intResult.type = ValueType::Numeric; 16 | intResult.numericValue = val1.numericValue + val2.numericValue; 17 | interp.evalStack.pushBack(intResult); 18 | return Success(); 19 | } else if (val1.type == ValueType::String && 20 | val2.type == ValueType::String) { 21 | Value stringResult; 22 | stringResult.type = ValueType::String; 23 | std::u32string result = val1.str->get() + val2.str->get(); 24 | stringResult.str = interp.heap.makeStringObject(std::move(result)); 25 | interp.evalStack.pushBack(stringResult); 26 | return Success(); 27 | } else if (val1.type == ValueType::Array && val2.type == ValueType::Array) { 28 | Value arrayResult; 29 | arrayResult.type = ValueType::Array; 30 | arrayResult.arr = interp.heap.makeArrayObject(Value(), 0); 31 | arrayResult.arr->values.reserve(val1.arr->values.size() + 32 | val2.arr->values.size()); 33 | for (const Value& val : val1.arr->values) { 34 | arrayResult.arr->values.push_back(val); 35 | } 36 | for (const Value& val : val2.arr->values) { 37 | arrayResult.arr->values.push_back(val); 38 | } 39 | interp.evalStack.pushBack(arrayResult); 40 | return Success(); 41 | } 42 | return TypeError(); 43 | } 44 | 45 | ExecutionResult mulOp(Interpreter& interp) { 46 | if (!interp.evalStack.assertDepth(2)) { 47 | return EvalStackUnderflow(); 48 | } 49 | Value val2 = interp.evalStack.popBack().value(); 50 | Value val1 = interp.evalStack.popBack().value(); 51 | if (val1.type == ValueType::Numeric && val2.type == ValueType::Numeric) { 52 | Value intResult; 53 | intResult.type = ValueType::Numeric; 54 | intResult.numericValue = val1.numericValue * val2.numericValue; 55 | interp.evalStack.pushBack(intResult); 56 | return Success(); 57 | } else if (val1.type == ValueType::String && 58 | val2.type == ValueType::Numeric) { 59 | if (val2.numericValue < 0) { 60 | return ExecutionResult{ 61 | ExecutionResultType::Error, 62 | U"Multiplication of a string by a negative number", Value()}; 63 | } 64 | Value stringResult; 65 | stringResult.type = ValueType::String; 66 | std::u32string copy; 67 | copy.reserve(val1.str->get().size() * val2.numericValue); 68 | for (size_t i = 0; i < (size_t)val2.numericValue; ++i) { 69 | copy.append(val1.str->get()); 70 | } 71 | stringResult.str = interp.heap.makeStringObject(std::move(copy)); 72 | interp.evalStack.pushBack(stringResult); 73 | return Success(); 74 | } else if (val1.type == ValueType::Array && 75 | val2.type == ValueType::Numeric) { 76 | if (val2.numericValue < 0) { 77 | return ExecutionResult{ 78 | ExecutionResultType::Error, 79 | U"Multiplication of an array by a negative number", Value()}; 80 | } 81 | Value arrayResult; 82 | arrayResult.type = ValueType::Array; 83 | arrayResult.arr = interp.heap.makeArrayObject(Value(), 0); 84 | arrayResult.arr->values.reserve(val1.arr->values.size() * 85 | val2.numericValue); 86 | for (size_t i = 0; i < (size_t)val2.numericValue; ++i) { 87 | for (const Value& val : val1.arr->values) { 88 | arrayResult.arr->values.push_back(val); 89 | } 90 | } 91 | interp.evalStack.pushBack(arrayResult); 92 | return Success(); 93 | } 94 | return TypeError(); 95 | } 96 | 97 | ExecutionResult divOp(Interpreter& interp) { 98 | if (!interp.evalStack.assertDepth(2)) { 99 | return EvalStackUnderflow(); 100 | } 101 | Value val2 = interp.evalStack.popBack().value(); 102 | Value val1 = interp.evalStack.popBack().value(); 103 | if (val1.type != ValueType::Numeric || val2.type != ValueType::Numeric) { 104 | return TypeError(); 105 | } 106 | Value result; 107 | result.type = ValueType::Numeric; 108 | if (val2.numericValue == 0) { 109 | return ExecutionResult{ExecutionResultType::Error, U"Division by zero", 110 | Value()}; 111 | } 112 | result.numericValue = val1.numericValue / val2.numericValue; 113 | interp.evalStack.pushBack(result); 114 | return Success(); 115 | } 116 | 117 | ExecutionResult modOp(Interpreter& interp) { 118 | if (!interp.evalStack.assertDepth(2)) { 119 | return EvalStackUnderflow(); 120 | } 121 | Value val2 = interp.evalStack.popBack().value(); 122 | Value val1 = interp.evalStack.popBack().value(); 123 | if (val1.type != ValueType::Numeric || val2.type != ValueType::Numeric) { 124 | return TypeError(); 125 | } 126 | Value result; 127 | result.type = ValueType::Numeric; 128 | if (val2.numericValue == 0) { 129 | return ExecutionResult{ExecutionResultType::Error, U"Division by zero", 130 | Value()}; 131 | } 132 | result.numericValue = val1.numericValue % val2.numericValue; 133 | interp.evalStack.pushBack(result); 134 | return Success(); 135 | } 136 | 137 | ExecutionResult subOp(Interpreter& interp) { 138 | if (!interp.evalStack.assertDepth(2)) { 139 | return EvalStackUnderflow(); 140 | } 141 | Value val2 = interp.evalStack.popBack().value(); 142 | Value val1 = interp.evalStack.popBack().value(); 143 | if (val1.type != ValueType::Numeric || val2.type != ValueType::Numeric) { 144 | return TypeError(); 145 | } 146 | Value result; 147 | result.type = ValueType::Numeric; 148 | result.numericValue = val1.numericValue - val2.numericValue; 149 | interp.evalStack.pushBack(result); 150 | return Success(); 151 | } 152 | 153 | void addArithNativeWords(Interpreter& interpreter) { 154 | interpreter.defineNativeWord(U"+", addOp); 155 | interpreter.defineNativeWord(U"-", subOp); 156 | interpreter.defineNativeWord(U"*", mulOp); 157 | interpreter.defineNativeWord(U"/", divOp); 158 | interpreter.defineNativeWord(U"%", modOp); 159 | } 160 | -------------------------------------------------------------------------------- /src/std/arrays.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | ExecutionResult allocOp(Interpreter& interp) { 4 | if (!interp.evalStack.assertDepth(2)) { 5 | return EvalStackUnderflow(); 6 | } 7 | Value elem = interp.evalStack.popBack().value(); 8 | Value size = interp.evalStack.popBack().value(); 9 | if (size.type != ValueType::Numeric) { 10 | return TypeError(); 11 | } 12 | if (size.numericValue < 0) { 13 | return ExecutionResult{ExecutionResultType::Error, 14 | U"Array allocation with negative size", Value()}; 15 | } 16 | Array* result = 17 | interp.heap.makeArrayObject(elem, (size_t)(size.numericValue)); 18 | Value pointerToArray; 19 | pointerToArray.arr = result; 20 | pointerToArray.type = ValueType::Array; 21 | interp.evalStack.pushBack(pointerToArray); 22 | return Success(); 23 | } 24 | 25 | ExecutionResult copyOp(Interpreter& interp) { 26 | if (!interp.evalStack.assertDepth(1)) { 27 | return EvalStackUnderflow(); 28 | } 29 | Value val = interp.evalStack.popBack().value(); 30 | if (val.type != ValueType::Array) { 31 | return TypeError(); 32 | } 33 | Array* newArray = interp.heap.shallowCopy(val.arr); 34 | Value pointerToArray; 35 | pointerToArray.arr = newArray; 36 | pointerToArray.type = ValueType::Array; 37 | interp.evalStack.pushBack(pointerToArray); 38 | return Success(); 39 | } 40 | 41 | ExecutionResult deepCopyOp(Interpreter& interp) { 42 | if (!interp.evalStack.assertDepth(1)) { 43 | return EvalStackUnderflow(); 44 | } 45 | Value val = interp.evalStack.popBack().value(); 46 | if (val.type != ValueType::Array) { 47 | return TypeError(); 48 | } 49 | Array* newArray = interp.heap.deepCopy(val.arr); 50 | Value pointerToArray; 51 | pointerToArray.arr = newArray; 52 | pointerToArray.type = ValueType::Array; 53 | interp.evalStack.pushBack(pointerToArray); 54 | return Success(); 55 | } 56 | 57 | ExecutionResult appendOp(Interpreter& interp) { 58 | if (!interp.evalStack.assertDepth(2)) { 59 | return EvalStackUnderflow(); 60 | } 61 | Value val = interp.evalStack.popBack().value(); 62 | Value array = interp.evalStack.popBack().value(); 63 | if (array.type != ValueType::Array) { 64 | return TypeError(); 65 | } 66 | array.arr->values.push_back(val); 67 | return Success(); 68 | } 69 | 70 | ExecutionResult resizeOp(Interpreter& interp) { 71 | if (!interp.evalStack.assertDepth(3)) { 72 | return EvalStackUnderflow(); 73 | } 74 | Value val = interp.evalStack.popBack().value(); 75 | Value count = interp.evalStack.popBack().value(); 76 | Value array = interp.evalStack.popBack().value(); 77 | if (array.type != ValueType::Array && count.type != ValueType::Numeric) { 78 | return TypeError(); 79 | } 80 | if (count.numericValue < 0) { 81 | return ExecutionResult{ExecutionResultType::Error, 82 | U"Resize for negative sizes is not permitted", 83 | Value()}; 84 | } 85 | array.arr->values.resize((size_t)count.numericValue, val); 86 | return Success(); 87 | } 88 | 89 | void addArrayManipulationNativeWords(Interpreter& interp) { 90 | interp.defineNativeWord(U"alloc", allocOp); 91 | interp.defineNativeWord(U"shallow_copy", copyOp); 92 | interp.defineNativeWord(U"deep_copy", deepCopyOp); 93 | interp.defineNativeWord(U"append", appendOp); 94 | interp.defineNativeWord(U"resize", resizeOp); 95 | } -------------------------------------------------------------------------------- /src/std/boolean.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | ExecutionResult andOp(Interpreter& interp) { 4 | if (!interp.evalStack.assertDepth(2)) { 5 | return EvalStackUnderflow(); 6 | } 7 | Value val1 = interp.evalStack.popBack().value(); 8 | Value val2 = interp.evalStack.popBack().value(); 9 | if (val1.type != ValueType::Boolean || val2.type != ValueType::Boolean) { 10 | return TypeError(); 11 | } 12 | Value result; 13 | result.type = ValueType::Boolean; 14 | result.booleanValue = val1.booleanValue && val2.booleanValue; 15 | interp.evalStack.pushBack(result); 16 | return Success(); 17 | } 18 | 19 | ExecutionResult orOp(Interpreter& interp) { 20 | if (!interp.evalStack.assertDepth(2)) { 21 | return EvalStackUnderflow(); 22 | } 23 | Value val1 = interp.evalStack.popBack().value(); 24 | Value val2 = interp.evalStack.popBack().value(); 25 | if (val1.type != ValueType::Boolean || val2.type != ValueType::Boolean) { 26 | return TypeError(); 27 | } 28 | Value result; 29 | result.type = ValueType::Boolean; 30 | result.booleanValue = val1.booleanValue || val2.booleanValue; 31 | interp.evalStack.pushBack(result); 32 | return Success(); 33 | } 34 | 35 | ExecutionResult notOp(Interpreter& interp) { 36 | if (!interp.evalStack.assertDepth(1)) { 37 | return EvalStackUnderflow(); 38 | } 39 | Value val = interp.evalStack.popBack().value(); 40 | if (val.type != ValueType::Boolean) { 41 | return TypeError(); 42 | } 43 | val.booleanValue = !val.booleanValue; 44 | interp.evalStack.pushBack(val); 45 | return Success(); 46 | } 47 | 48 | void addBooleanNativeWords(Interpreter& interp) { 49 | interp.defineNativeWord(U"and", andOp); 50 | interp.defineNativeWord(U"or", orOp); 51 | interp.defineNativeWord(U"not", notOp); 52 | } -------------------------------------------------------------------------------- /src/std/comparisons.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define MAKE_COMP_OPERATOR(name, op) \ 6 | ExecutionResult name(Interpreter& interp) { \ 7 | if_unlikely(!interp.evalStack.assertDepth(2)) { \ 8 | return EvalStackUnderflow(); \ 9 | } \ 10 | Value val2 = interp.evalStack.popBack().value(); \ 11 | Value val1 = interp.evalStack.popBack().value(); \ 12 | if (val1.type != ValueType::Numeric || \ 13 | val2.type != ValueType::Numeric) { \ 14 | return TypeError(); \ 15 | } \ 16 | Value result; \ 17 | result.type = ValueType::Boolean; \ 18 | result.booleanValue = op(val1.numericValue, val2.numericValue); \ 19 | interp.evalStack.pushBack(result); \ 20 | return Success(); \ 21 | } 22 | 23 | Value eqOperator(Value val1, Value val2, [[maybe_unused]] Interpreter& interp) { 24 | Value result; 25 | result.type = ValueType::Boolean; 26 | if (val1.type != val2.type) { 27 | result.booleanValue = false; 28 | return result; 29 | } 30 | switch (val1.type) { 31 | case ValueType::Array: 32 | case ValueType::Placeholder: 33 | case ValueType::SplicePlaceholder: 34 | result.booleanValue = val1.arr == val2.arr; 35 | break; 36 | case ValueType::Numeric: 37 | result.booleanValue = val1.numericValue == val2.numericValue; 38 | break; 39 | case ValueType::Boolean: 40 | result.booleanValue = val1.booleanValue == val2.booleanValue; 41 | break; 42 | case ValueType::String: 43 | case ValueType::Word: 44 | case ValueType::WordAssign: 45 | case ValueType::WordDeclare: 46 | result.booleanValue = val1.str == val2.str; 47 | break; 48 | case ValueType::NativeWord: 49 | result.booleanValue = val1.word == val2.word; 50 | break; 51 | case ValueType::Nil: 52 | result.booleanValue = true; 53 | break; 54 | case ValueType::NativeHandle: 55 | result.booleanValue = val1.handle == val2.handle; 56 | break; 57 | } 58 | return result; 59 | } 60 | 61 | Value neOperator(Value val1, Value val2, Interpreter& interp) { 62 | Value result = eqOperator(val1, val2, interp); 63 | result.booleanValue = !result.booleanValue; 64 | return result; 65 | } 66 | 67 | MAKE_FROM_BINARY_OPERATOR(eqNativeWord, eqOperator) 68 | MAKE_FROM_BINARY_OPERATOR(neNativeWord, neOperator) 69 | 70 | bool ltOperator(int64_t num1, int64_t num2) { return num1 < num2; } 71 | bool leOperator(int64_t num1, int64_t num2) { return num1 <= num2; } 72 | bool gtOperator(int64_t num1, int64_t num2) { return num1 > num2; } 73 | bool geOperator(int64_t num1, int64_t num2) { return num1 >= num2; } 74 | 75 | MAKE_COMP_OPERATOR(ltNativeWord, ltOperator) 76 | MAKE_COMP_OPERATOR(leNativeWord, leOperator) 77 | MAKE_COMP_OPERATOR(gtNativeWord, gtOperator) 78 | MAKE_COMP_OPERATOR(geNativeWord, geOperator) 79 | 80 | void addComparisonsNativeWords(Interpreter& interpreter) { 81 | interpreter.defineNativeWord(U"<", ltNativeWord); 82 | interpreter.defineNativeWord(U"<=", leNativeWord); 83 | interpreter.defineNativeWord(U">", gtNativeWord); 84 | interpreter.defineNativeWord(U">=", geNativeWord); 85 | interpreter.defineNativeWord(U"==", eqNativeWord); 86 | interpreter.defineNativeWord(U"!=", neNativeWord); 87 | } 88 | -------------------------------------------------------------------------------- /src/std/controlflow.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | ExecutionResult doIfElse(Interpreter& interp, bool makeNewScope) { 5 | if (!interp.evalStack.assertDepth(3)) { 6 | return EvalStackUnderflow(); 7 | } 8 | 9 | Value elseCodeVal = interp.evalStack.popBack().value(); 10 | Value ifCodeVal = interp.evalStack.popBack().value(); 11 | Value condition = interp.evalStack.popBack().value(); 12 | if_unlikely(elseCodeVal.type != ValueType::Array || 13 | ifCodeVal.type != ValueType::Array || 14 | condition.type != ValueType::Boolean) { 15 | return TypeError(); 16 | } 17 | if (condition.booleanValue) { 18 | ExecutionResult res = 19 | interp.callInterpreter(ifCodeVal.arr, makeNewScope); 20 | if (res.result != ExecutionResultType::Success) { 21 | return res; 22 | } 23 | } else { 24 | ExecutionResult res = 25 | interp.callInterpreter(elseCodeVal.arr, makeNewScope); 26 | if (res.result != ExecutionResultType::Success) { 27 | return res; 28 | } 29 | } 30 | return Success(); 31 | } 32 | 33 | ExecutionResult ifElseOp(Interpreter& interp) { return doIfElse(interp, true); } 34 | ExecutionResult inlineIfElseOp(Interpreter& interp) { 35 | return doIfElse(interp, false); 36 | } 37 | 38 | ExecutionResult doIf(Interpreter& interp, bool makeNewScope) { 39 | if (!interp.evalStack.assertDepth(2)) { 40 | return EvalStackUnderflow(); 41 | } 42 | 43 | Value ifCodeVal = interp.evalStack.popBack().value(); 44 | Value condition = interp.evalStack.popBack().value(); 45 | if_unlikely(ifCodeVal.type != ValueType::Array || 46 | condition.type != ValueType::Boolean) { 47 | return TypeError(); 48 | } 49 | if (condition.booleanValue) { 50 | ExecutionResult res = 51 | interp.callInterpreter(ifCodeVal.arr, makeNewScope); 52 | if (res.result != ExecutionResultType::Success) { 53 | return res; 54 | } 55 | } 56 | return Success(); 57 | } 58 | 59 | ExecutionResult ifOp(Interpreter& interp) { return doIf(interp, true); } 60 | ExecutionResult inlineIfOp(Interpreter& interp) { return doIf(interp, false); } 61 | 62 | ExecutionResult doWhile(Interpreter& interp, bool makeNewScope) { 63 | if (!interp.evalStack.assertDepth(2)) { 64 | return EvalStackUnderflow(); 65 | } 66 | 67 | Value loopCode = interp.evalStack.popBack().value(); 68 | Value condCode = interp.evalStack.popBack().value(); 69 | if_unlikely(condCode.type != ValueType::Array || 70 | loopCode.type != ValueType::Array) { 71 | return TypeError(); 72 | } 73 | if (makeNewScope) { 74 | interp.symTable.createScope(); 75 | } 76 | while (true) { 77 | ExecutionResult res = interp.callInterpreter(condCode.arr, false); 78 | if_unlikely(res.result != ExecutionResultType::Success) { 79 | if (makeNewScope) { 80 | interp.symTable.leaveScope(); 81 | } 82 | return res; 83 | } 84 | std::optional testResult = interp.evalStack.popBack(); 85 | if_unlikely(!testResult.has_value()) { 86 | if (makeNewScope) { 87 | interp.symTable.leaveScope(); 88 | } 89 | return EvalStackUnderflow(); 90 | } 91 | if_unlikely(testResult.value().type != ValueType::Boolean) { 92 | if (makeNewScope) { 93 | interp.symTable.leaveScope(); 94 | } 95 | return TypeError(); 96 | } 97 | if (!testResult.value().booleanValue) { 98 | break; 99 | } 100 | res = interp.callInterpreter(loopCode.arr, makeNewScope); 101 | if (res.result != ExecutionResultType::Success) { 102 | if (res.result == ExecutionResultType::Break) { 103 | if (makeNewScope) { 104 | interp.symTable.leaveScope(); 105 | } 106 | return Success(); 107 | } else if (res.result == ExecutionResultType::Continue) { 108 | continue; 109 | } 110 | if (makeNewScope) { 111 | interp.symTable.leaveScope(); 112 | } 113 | return res; 114 | } 115 | } 116 | if (makeNewScope) { 117 | interp.symTable.leaveScope(); 118 | } 119 | return Success(); 120 | } 121 | 122 | ExecutionResult whileOp(Interpreter& interp) { return doWhile(interp, true); } 123 | ExecutionResult inlineWhileOp(Interpreter& interp) { 124 | return doWhile(interp, false); 125 | } 126 | 127 | ExecutionResult doFor(Interpreter& interp, bool makeNewScope) { 128 | if (!interp.evalStack.assertDepth(4)) { 129 | return EvalStackUnderflow(); 130 | } 131 | Value loopCode = interp.evalStack.popBack().value(); 132 | Value iterCode = interp.evalStack.popBack().value(); 133 | Value condCode = interp.evalStack.popBack().value(); 134 | Value initCode = interp.evalStack.popBack().value(); 135 | if (initCode.type != ValueType::Array || 136 | condCode.type != ValueType::Array || 137 | iterCode.type != ValueType::Array || 138 | loopCode.type != ValueType::Array) { 139 | return TypeError(); 140 | } 141 | if (makeNewScope) { 142 | interp.symTable.createScope(); 143 | } 144 | ExecutionResult res = interp.callInterpreter(initCode.arr, false); 145 | if_unlikely(res.result != ExecutionResultType::Success) { return res; } 146 | while (true) { 147 | ExecutionResult res = interp.callInterpreter(condCode.arr, false); 148 | if_unlikely(res.result != ExecutionResultType::Success) { return res; } 149 | std::optional testResult = interp.evalStack.popBack(); 150 | if_unlikely(!testResult.has_value()) { 151 | if (makeNewScope) { 152 | interp.symTable.leaveScope(); 153 | } 154 | return EvalStackUnderflow(); 155 | } 156 | if_unlikely(testResult.value().type != ValueType::Boolean) { 157 | if (makeNewScope) { 158 | interp.symTable.leaveScope(); 159 | } 160 | return TypeError(); 161 | } 162 | if (!testResult.value().booleanValue) { 163 | break; 164 | } 165 | res = interp.callInterpreter(loopCode.arr, true); 166 | if (res.result != ExecutionResultType::Success) { 167 | if (res.result == ExecutionResultType::Break) { 168 | if (makeNewScope) { 169 | interp.symTable.leaveScope(); 170 | } 171 | return Success(); 172 | } else if (res.result != ExecutionResultType::Continue) { 173 | return res; 174 | } 175 | } 176 | res = interp.callInterpreter(iterCode.arr, false); 177 | if_unlikely(res.result != ExecutionResultType::Success) { 178 | interp.symTable.leaveScope(); 179 | return res; 180 | } 181 | } 182 | interp.symTable.leaveScope(); 183 | return Success(); 184 | } 185 | 186 | ExecutionResult forOp(Interpreter& interp) { return doFor(interp, true); } 187 | ExecutionResult inlineForOp(Interpreter& interp) { 188 | return doFor(interp, false); 189 | } 190 | 191 | ExecutionResult breakOp(Interpreter&) { 192 | return ExecutionResult{ExecutionResultType::Break, U"Nowhere to break", 193 | Value()}; 194 | } 195 | 196 | ExecutionResult returnOp(Interpreter&) { 197 | return ExecutionResult{ExecutionResultType::Return, U"Nowhere to return", 198 | Value()}; 199 | } 200 | 201 | ExecutionResult continueOp(Interpreter&) { 202 | return ExecutionResult{ExecutionResultType::Continue, 203 | U"Nowhere to continue", Value()}; 204 | } 205 | 206 | ExecutionResult scopeCall(Interpreter& interp) { 207 | std::optional newTraceOptional = interp.evalStack.popBack(); 208 | if_unlikely(!newTraceOptional) { return EvalStackUnderflow(); } 209 | Value newTrace = newTraceOptional.value(); 210 | if (newTrace.type == ValueType::Array) { 211 | ExecutionResult callResult = interp.callInterpreter(newTrace.arr, true); 212 | if (callResult.result == ExecutionResultType::Return) { 213 | return Success(); 214 | } 215 | return callResult; 216 | } else if (newTrace.type == ValueType::NativeWord) { 217 | ExecutionResult callResult = newTrace.word(interp); 218 | if (callResult.result == ExecutionResultType::Return) { 219 | return Success(); 220 | } 221 | return callResult; 222 | } 223 | return TypeError(); 224 | } 225 | 226 | ExecutionResult noScopeCall(Interpreter& interp) { 227 | std::optional newTraceOptional = interp.evalStack.popBack(); 228 | if_unlikely(!newTraceOptional) { return EvalStackUnderflow(); } 229 | Value newTrace = newTraceOptional.value(); 230 | if (newTrace.type == ValueType::Array) { 231 | ExecutionResult callResult = 232 | interp.callInterpreter(newTrace.arr, false); 233 | if (callResult.result == ExecutionResultType::Return) { 234 | return Success(); 235 | } 236 | return callResult; 237 | } else if (newTrace.type == ValueType::NativeWord) { 238 | ExecutionResult callResult = newTrace.word(interp); 239 | if (callResult.result == ExecutionResultType::Return) { 240 | return Success(); 241 | } 242 | return callResult; 243 | } 244 | return TypeError(); 245 | } 246 | 247 | ExecutionResult doTry(Interpreter& interp, bool makeNewScope) { 248 | if_unlikely(!interp.evalStack.assertDepth(2)) { 249 | return EvalStackUnderflow(); 250 | } 251 | Value argsCount = interp.evalStack.popBack().value(); 252 | Value newTrace = interp.evalStack.popBack().value(); 253 | if_unlikely(newTrace.type != ValueType::Array) { return TypeError(); } 254 | if_unlikely(argsCount.type != ValueType::Numeric) { return TypeError(); } 255 | if_unlikely(!interp.evalStack.assertDepth(argsCount.numericValue)) { 256 | return EvalStackUnderflow(); 257 | } 258 | size_t cleanSize = interp.evalStack.getStackSize() - argsCount.numericValue; 259 | size_t oldBarrier = interp.evalStack.getBarrier(); 260 | interp.evalStack.setBarrier(cleanSize); 261 | ExecutionResult callResult = 262 | interp.callInterpreter(newTrace.arr, makeNewScope); 263 | Value val; 264 | val.type = ValueType::Boolean; 265 | interp.evalStack.setBarrier(oldBarrier); 266 | if (callResult.result == ExecutionResultType::Success) { 267 | val.booleanValue = true; 268 | } else if (callResult.result == ExecutionResultType::Custom) { 269 | interp.evalStack.resize(cleanSize); 270 | interp.evalStack.pushBack(callResult.val); 271 | val.booleanValue = false; 272 | } else { 273 | interp.evalStack.resize(cleanSize); 274 | Value errorMessage; 275 | errorMessage.type = ValueType::String; 276 | errorMessage.str = interp.heap.makeStringObject(callResult.error); 277 | interp.evalStack.pushBack(errorMessage); 278 | val.booleanValue = false; 279 | } 280 | interp.evalStack.pushBack(val); 281 | return Success(); 282 | } 283 | 284 | ExecutionResult tryOp(Interpreter& interp) { return doTry(interp, true); } 285 | ExecutionResult inlineTryOp(Interpreter& interp) { 286 | return doTry(interp, false); 287 | } 288 | 289 | ExecutionResult throwOp(Interpreter& interp) { 290 | if_unlikely(!interp.evalStack.assertDepth(1)) { 291 | return EvalStackUnderflow(); 292 | } 293 | Value val = interp.evalStack.popBack().value(); 294 | return ExecutionResult{ExecutionResultType::Custom, U"", val}; 295 | } 296 | 297 | void addControlFlowNativeWords(Interpreter& interp) { 298 | interp.defineNativeWord(U"inline_while", inlineWhileOp); 299 | interp.defineNativeWord(U"while", whileOp); 300 | interp.defineNativeWord(U"inline_if_else", inlineIfElseOp); 301 | interp.defineNativeWord(U"if_else", ifElseOp); 302 | interp.defineNativeWord(U"inline_if", inlineIfOp); 303 | interp.defineNativeWord(U"if", ifOp); 304 | interp.defineNativeWord(U"inline_for", inlineForOp); 305 | interp.defineNativeWord(U"for", forOp); 306 | interp.defineNativeWord(U"break", breakOp); 307 | interp.defineNativeWord(U"return", returnOp); 308 | interp.defineNativeWord(U"continue", continueOp); 309 | interp.defineNativeWord(U"!", scopeCall); 310 | interp.defineNativeWord(U",", noScopeCall); 311 | interp.defineNativeWord(U"try", tryOp); 312 | interp.defineNativeWord(U"inline_try", inlineTryOp); 313 | interp.defineNativeWord(U"throw", throwOp); 314 | } 315 | -------------------------------------------------------------------------------- /src/std/indexing.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | ExecutionResult getAtOp(Interpreter& interp) { 7 | if (!interp.evalStack.assertDepth(2)) { 8 | return EvalStackUnderflow(); 9 | } 10 | Value index = interp.evalStack.popBack().value(); 11 | Value indexable = interp.evalStack.popBack().value(); 12 | if (index.type != ValueType::Numeric) { 13 | return TypeError(); 14 | } 15 | if (index.numericValue < 0) { 16 | return ExecutionResult{ExecutionResultType::Error, U"Out of bounds", 17 | Value()}; 18 | } 19 | switch (indexable.type) { 20 | case ValueType::Array: 21 | if (indexable.arr->values.size() <= (size_t)(index.numericValue)) { 22 | return ExecutionResult{ExecutionResultType::Error, 23 | U"Out of bounds", Value()}; 24 | } 25 | interp.evalStack.pushBack( 26 | indexable.arr->values[index.numericValue]); 27 | return Success(); 28 | case ValueType::String: { 29 | if (indexable.str->get().size() <= (size_t)(index.numericValue)) { 30 | return ExecutionResult{ExecutionResultType::Error, 31 | U"Out of bounds", Value()}; 32 | } 33 | char32_t newCStr[2] = {indexable.str->get()[index.numericValue], 34 | '\0'}; 35 | String* newStr = 36 | interp.heap.makeStringObject(std::u32string_view(newCStr)); 37 | Value result; 38 | result.type = ValueType::String; 39 | result.str = newStr; 40 | interp.evalStack.pushBack(result); 41 | return Success(); 42 | } 43 | default: 44 | break; 45 | } 46 | return ExecutionResult{ExecutionResultType::Error, 47 | U"Value is not indexable", Value()}; 48 | } 49 | 50 | ExecutionResult setAtOp(Interpreter& interp) { 51 | if (!interp.evalStack.assertDepth(3)) { 52 | return EvalStackUnderflow(); 53 | } 54 | Value element = interp.evalStack.popBack().value(); 55 | Value index = interp.evalStack.popBack().value(); 56 | Value indexable = interp.evalStack.popBack().value(); 57 | if (index.type != ValueType::Numeric) { 58 | return TypeError(); 59 | } 60 | if (index.numericValue < 0) { 61 | return ExecutionResult{ExecutionResultType::Error, U"Out of bounds", 62 | Value()}; 63 | } 64 | switch (indexable.type) { 65 | case ValueType::Array: 66 | if (indexable.arr->values.size() <= (size_t)(index.numericValue)) { 67 | return ExecutionResult{ExecutionResultType::Error, 68 | U"Out of bounds", Value()}; 69 | } 70 | indexable.arr->values[index.numericValue] = element; 71 | return Success(); 72 | default: 73 | return ExecutionResult{ExecutionResultType::Error, 74 | U"Value is not indexable or immutable", 75 | Value()}; 76 | } 77 | } 78 | 79 | ExecutionResult lenOp(Interpreter& interp) { 80 | if (!interp.evalStack.assertDepth(1)) { 81 | return EvalStackUnderflow(); 82 | } 83 | Value indexable = interp.evalStack.popBack().value(); 84 | Value integerResult; 85 | integerResult.type = ValueType::Numeric; 86 | switch (indexable.type) { 87 | case ValueType::Array: 88 | integerResult.numericValue = 89 | (int64_t)(indexable.arr->values.size()); 90 | interp.evalStack.pushBack(integerResult); 91 | return Success(); 92 | case ValueType::String: 93 | integerResult.numericValue = (int64_t)(indexable.str->get().size()); 94 | interp.evalStack.pushBack(integerResult); 95 | return Success(); 96 | default: 97 | break; 98 | } 99 | return TypeError(); 100 | } 101 | 102 | ExecutionResult sliceOp(Interpreter& interp) { 103 | if (!interp.evalStack.assertDepth(3)) { 104 | return EvalStackUnderflow(); 105 | } 106 | Value end = interp.evalStack.popBack().value(); 107 | Value start = interp.evalStack.popBack().value(); 108 | Value indexable = interp.evalStack.popBack().value(); 109 | if (end.type != ValueType::Numeric && start.type != ValueType::Numeric) { 110 | return TypeError(); 111 | } 112 | if (indexable.type == ValueType::Array) { 113 | if (start.numericValue > end.numericValue || 114 | (size_t)end.numericValue > indexable.arr->values.size() || 115 | start.numericValue < 0 || end.numericValue < 0) { 116 | return ExecutionResult{ExecutionResultType::Error, U"Out of bounds", 117 | Value()}; 118 | } 119 | Array* result = interp.heap.makeArrayObject(Value(), 0); 120 | std::copy(indexable.arr->values.begin() + start.numericValue, 121 | indexable.arr->values.begin() + end.numericValue, 122 | std::back_inserter(result->values)); 123 | Value pointer; 124 | pointer.type = ValueType::Array; 125 | pointer.arr = result; 126 | interp.evalStack.pushBack(pointer); 127 | return Success(); 128 | } else if (indexable.type == ValueType::String) { 129 | if (start.numericValue > end.numericValue || 130 | (size_t)end.numericValue > indexable.str->get().size() || 131 | start.numericValue < 0 || end.numericValue < 0) { 132 | return ExecutionResult{ExecutionResultType::Error, U"Out of bounds", 133 | Value()}; 134 | } 135 | String* result = 136 | interp.heap.makeStringObject(indexable.str->get().substr( 137 | start.numericValue, end.numericValue - start.numericValue)); 138 | Value pointer; 139 | pointer.type = ValueType::String; 140 | pointer.str = result; 141 | interp.evalStack.pushBack(pointer); 142 | return Success(); 143 | } else { 144 | return TypeError(); 145 | } 146 | } 147 | 148 | void addIndexingNativeWords(Interpreter& interp) { 149 | interp.defineNativeWord(U"len", lenOp); 150 | interp.defineNativeWord(U"peek", getAtOp); 151 | interp.defineNativeWord(U"poke", setAtOp); 152 | interp.defineNativeWord(U"slice", sliceOp); 153 | } -------------------------------------------------------------------------------- /src/std/os.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | std::string fromUTF32(const std::u32string& s); 8 | std::u32string toUTF32(const std::string& s); 9 | 10 | ExecutionResult printStrOp(Interpreter& interp) { 11 | std::optional top = interp.evalStack.popBack(); 12 | if (!top) { 13 | return EvalStackUnderflow(); 14 | } 15 | if (top.value().type != ValueType::String) { 16 | return TypeError(); 17 | } 18 | print(top.value().str->get()); 19 | return Success(); 20 | } 21 | 22 | ExecutionResult printlnStrOp(Interpreter& interp) { 23 | std::optional top = interp.evalStack.popBack(); 24 | if (!top) { 25 | return EvalStackUnderflow(); 26 | } 27 | if (top.value().type != ValueType::String) { 28 | return TypeError(); 29 | } 30 | print(top.value().str->get()); 31 | print(U"\n"); 32 | return Success(); 33 | } 34 | 35 | ExecutionResult quitOp([[maybe_unused]] Interpreter& interp) { 36 | exit(0); 37 | // not sure what we are doing here 38 | return Success(); 39 | } 40 | 41 | ExecutionResult exitOp(Interpreter& interp) { 42 | if (!interp.evalStack.assertDepth(1)) { 43 | return EvalStackUnderflow(); 44 | } 45 | Value val = interp.evalStack.popBack().value(); 46 | if (val.type != ValueType::Numeric) { 47 | return TypeError(); 48 | } 49 | exit((int)val.numericValue); 50 | // again, not sure what we are doing here 51 | return Success(); 52 | } 53 | 54 | ExecutionResult readFileOp(Interpreter& interp) { 55 | if (!interp.evalStack.assertDepth(1)) { 56 | return EvalStackUnderflow(); 57 | } 58 | Value filename = interp.evalStack.popBack().value(); 59 | if (filename.type != ValueType::String) { 60 | return TypeError(); 61 | } 62 | std::string UTF8filename = fromUTF32(filename.str->get()); 63 | std::optional result = readFile(UTF8filename.data()); 64 | Value val; 65 | if (!result.has_value()) { 66 | val.type = ValueType::Nil; 67 | } else { 68 | val.type = ValueType::String; 69 | val.str = interp.heap.makeStringObject(result.value()); 70 | } 71 | interp.evalStack.pushBack(val); 72 | return Success(); 73 | } 74 | 75 | ExecutionResult readLineOp(Interpreter& interp) { 76 | if (!interp.evalStack.assertDepth(1)) { 77 | return EvalStackUnderflow(); 78 | } 79 | Value prompt = interp.evalStack.popBack().value(); 80 | if (prompt.type != ValueType::String) { 81 | return TypeError(); 82 | } 83 | std::u32string line = readLine(prompt.str->get().c_str()); 84 | Value result; 85 | result.type = ValueType::String; 86 | result.str = interp.heap.makeStringObject(line); 87 | interp.evalStack.pushBack(result); 88 | return Success(); 89 | } 90 | 91 | ExecutionResult writeFileOp(Interpreter& interp) { 92 | if (!interp.evalStack.assertDepth(2)) { 93 | return EvalStackUnderflow(); 94 | } 95 | Value filename = interp.evalStack.popBack().value(); 96 | Value contents = interp.evalStack.popBack().value(); 97 | if (filename.type != ValueType::String || 98 | contents.type != ValueType::String) { 99 | return TypeError(); 100 | } 101 | bool result = writeFile(filename.str->get(), contents.str->get()); 102 | Value valResult; 103 | valResult.type = ValueType::Boolean; 104 | valResult.booleanValue = result; 105 | interp.evalStack.pushBack(valResult); 106 | return Success(); 107 | } 108 | 109 | ExecutionResult execOp(Interpreter& interp) { 110 | if (!interp.evalStack.assertDepth(1)) { 111 | return EvalStackUnderflow(); 112 | } 113 | Value cmd = interp.evalStack.popBack().value(); 114 | if (cmd.type != ValueType::String) { 115 | return TypeError(); 116 | } 117 | ProcessInvokationRequest request; 118 | request.name = cmd.str->get(); 119 | ProcessInvokationResponce response = executeProcess(request); 120 | Value booleanResult, outResult, codeResult; 121 | booleanResult.type = ValueType::Boolean; 122 | outResult.type = ValueType::String; 123 | codeResult.type = ValueType::Numeric; 124 | if (response.error) { 125 | booleanResult.booleanValue = false; 126 | interp.evalStack.pushBack(booleanResult); 127 | } else { 128 | outResult.str = interp.heap.makeStringObject(response.out); 129 | booleanResult.booleanValue = true; 130 | codeResult.numericValue = response.errorCode; 131 | interp.evalStack.pushBack(outResult); 132 | interp.evalStack.pushBack(codeResult); 133 | interp.evalStack.pushBack(booleanResult); 134 | } 135 | return Success(); 136 | } 137 | 138 | ExecutionResult globOp(Interpreter& interp) { 139 | if (!interp.evalStack.assertDepth(1)) { 140 | return EvalStackUnderflow(); 141 | } 142 | Value pattern = interp.evalStack.popBack().value(); 143 | if (pattern.type != ValueType::String) { 144 | return TypeError(); 145 | } 146 | std::string patternUtf8 = fromUTF32(pattern.str->get()); 147 | glob::glob g(patternUtf8); 148 | Value result; 149 | result.type = ValueType::Array; 150 | result.arr = interp.heap.makeArrayObject(Value(), 0); 151 | while (g) { 152 | std::u32string current = toUTF32(g.current_match()); 153 | Value curVal; 154 | curVal.type = ValueType::String; 155 | curVal.str = interp.heap.makeStringObject(current); 156 | result.arr->values.push_back(curVal); 157 | g.next(); 158 | } 159 | interp.evalStack.pushBack(result); 160 | return Success(); 161 | } 162 | 163 | void addOSModuleNativeWords(Interpreter& interp) { 164 | interp.defineNativeWord(U"readln", readLineOp); 165 | interp.defineNativeWord(U"readfile", readFileOp); 166 | interp.defineNativeWord(U"writefile", writeFileOp); 167 | interp.defineNativeWord(U"writeln", printlnStrOp); 168 | interp.defineNativeWord(U"write", printStrOp); 169 | interp.defineNativeWord(U"glob", globOp); 170 | interp.defineNativeWord(U"exec", execOp); 171 | interp.defineNativeWord(U"exit", exitOp); 172 | interp.defineNativeWord(U"quit", quitOp); 173 | } -------------------------------------------------------------------------------- /src/std/stack.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | ExecutionResult dropOp(Interpreter& interp) { 4 | if (!interp.evalStack.popBack()) { 5 | return EvalStackUnderflow(); 6 | } 7 | return Success(); 8 | } 9 | 10 | ExecutionResult swapOp(Interpreter& interp) { 11 | if (!interp.evalStack.assertDepth(2)) { 12 | return EvalStackUnderflow(); 13 | } 14 | Value b = interp.evalStack.popBack().value(); 15 | Value a = interp.evalStack.popBack().value(); 16 | interp.evalStack.pushBack(b); 17 | interp.evalStack.pushBack(a); 18 | return Success(); 19 | } 20 | 21 | ExecutionResult dupOp(Interpreter& interp) { 22 | std::optional a = interp.evalStack.popBack(); 23 | if (!a) { 24 | return EvalStackUnderflow(); 25 | } 26 | interp.evalStack.pushBack(a.value()); 27 | interp.evalStack.pushBack(a.value()); 28 | return Success(); 29 | } 30 | 31 | ExecutionResult overOp(Interpreter& interp) { 32 | std::optional a, b; 33 | b = interp.evalStack.popBack(); 34 | a = interp.evalStack.popBack(); 35 | if (!a || !b) { 36 | return EvalStackUnderflow(); 37 | } 38 | interp.evalStack.pushBack(a.value()); 39 | interp.evalStack.pushBack(b.value()); 40 | interp.evalStack.pushBack(a.value()); 41 | return Success(); 42 | } 43 | 44 | ExecutionResult clearOp(Interpreter& interp) { 45 | interp.evalStack.clear(); 46 | return Success(); 47 | } 48 | 49 | ExecutionResult getStackCopyOp(Interpreter& interp) { 50 | const std::vector& stack = interp.evalStack.getStack(); 51 | Array* result = interp.heap.makeArrayObject(Value{}, stack.size()); 52 | for (size_t i = 0; i < stack.size(); ++i) { 53 | result->values[i] = stack[i]; 54 | } 55 | Value stackCopy; 56 | stackCopy.type = ValueType::Array; 57 | stackCopy.arr = result; 58 | interp.evalStack.pushBack(stackCopy); 59 | return Success(); 60 | } 61 | 62 | ExecutionResult getStackSizeOp(Interpreter& interp) { 63 | Value result; 64 | result.type = ValueType::Numeric; 65 | result.numericValue = interp.evalStack.getStackSize(); 66 | interp.evalStack.pushBack(result); 67 | return Success(); 68 | } 69 | 70 | void addStackManipNativeWords(Interpreter& interp) { 71 | interp.defineNativeWord(U"get_stack_size", getStackSizeOp); 72 | interp.defineNativeWord(U"get_stack_copy", getStackCopyOp); 73 | interp.defineNativeWord(U"drop", dropOp); 74 | interp.defineNativeWord(U"swap", swapOp); 75 | interp.defineNativeWord(U"over", overOp); 76 | interp.defineNativeWord(U"dup", dupOp); 77 | interp.defineNativeWord(U"clear", clearOp); 78 | } 79 | -------------------------------------------------------------------------------- /src/std/strings.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | Value toStringOp(Value val, Interpreter& interp) { 9 | std::u32string resultCppStr = prettyprint(val, interp); 10 | Value result; 11 | result.type = ValueType::String; 12 | result.str = interp.heap.makeStringObject(std::move(resultCppStr)); 13 | return result; 14 | } 15 | 16 | ExecutionResult fromStringOp(Interpreter& interp) { 17 | if (!interp.evalStack.assertDepth(1)) { 18 | return EvalStackUnderflow(); 19 | } 20 | Value val = interp.evalStack.popBack().value(); 21 | if (val.type != ValueType::String) { 22 | return TypeError(); 23 | } 24 | ParseResult result = parse(val.str->get(), interp); 25 | if (result.isError()) { 26 | interp.heap.collectGarbage(); 27 | return ExecutionResult{ExecutionResultType::Error, 28 | U"Failed to parse input", Value()}; 29 | } 30 | Value valueResult; 31 | valueResult.arr = result.code; 32 | valueResult.type = ValueType::Array; 33 | interp.evalStack.pushBack(valueResult); 34 | return Success(); 35 | } 36 | 37 | ExecutionResult splitOp(Interpreter& interp) { 38 | if (!interp.evalStack.assertDepth(2)) { 39 | return EvalStackUnderflow(); 40 | } 41 | Value delString = interp.evalStack.popBack().value(); 42 | Value splitString = interp.evalStack.popBack().value(); 43 | if (delString.type != ValueType::String || 44 | splitString.type != ValueType::String) { 45 | return TypeError(); 46 | } 47 | Value result; 48 | result.type = ValueType::Array; 49 | result.arr = interp.heap.makeArrayObject(Value(), 0); 50 | size_t start = 0; 51 | for (size_t i = 0; i < splitString.str->get().size(); ++i) { 52 | char32_t ch = splitString.str->get()[i]; 53 | for (size_t j = 0; j < delString.str->get().size(); ++j) { 54 | if (delString.str->get()[j] == ch) { 55 | std::u32string substr = 56 | splitString.str->get().substr(start, i - start); 57 | start = i + 1; 58 | String* str = interp.heap.makeStringObject(substr); 59 | Value val; 60 | val.str = str; 61 | val.type = ValueType::String; 62 | result.arr->values.push_back(val); 63 | } 64 | } 65 | } 66 | if (start < splitString.str->get().size()) { 67 | std::u32string substr = splitString.str->get().substr( 68 | start, splitString.str->get().size() - start); 69 | String* str = interp.heap.makeStringObject(substr); 70 | Value val; 71 | val.str = str; 72 | val.type = ValueType::String; 73 | result.arr->values.push_back(val); 74 | } 75 | interp.evalStack.pushBack(result); 76 | return Success(); 77 | } 78 | 79 | ExecutionResult joinOp(Interpreter& interp) { 80 | if (!interp.evalStack.assertDepth(2)) { 81 | return EvalStackUnderflow(); 82 | } 83 | Value sepString = interp.evalStack.popBack().value(); 84 | Value strings = interp.evalStack.popBack().value(); 85 | if (sepString.type != ValueType::String || 86 | strings.type != ValueType::Array) { 87 | return TypeError(); 88 | } 89 | for (size_t i = 0; i < strings.arr->values.size(); ++i) { 90 | if (strings.arr->values[i].type != ValueType::String) { 91 | return TypeError(); 92 | } 93 | } 94 | Value result; 95 | result.type = ValueType::String; 96 | if (strings.arr->values.size() == 0) { 97 | result.str = interp.heap.makeStringObject(U""); 98 | interp.evalStack.pushBack(result); 99 | return Success(); 100 | } 101 | std::u32string str = strings.arr->values[0].str->get(); 102 | for (size_t i = 1; i < strings.arr->values.size(); ++i) { 103 | str.append(sepString.str->get()); 104 | str.append(strings.arr->values[i].str->get()); 105 | } 106 | result.str = interp.heap.makeStringObject(str); 107 | interp.evalStack.pushBack(result); 108 | return Success(); 109 | } 110 | 111 | MAKE_FROM_UNARY_OPERATOR(toStringNativeWord, toStringOp) 112 | 113 | void addStringManipulationNativeWords(Interpreter& interp) { 114 | interp.defineNativeWord(U"serialize", toStringNativeWord); 115 | interp.defineNativeWord(U"parse", fromStringOp); 116 | interp.defineNativeWord(U"join", joinOp); 117 | interp.defineNativeWord(U"split", splitOp); 118 | } 119 | -------------------------------------------------------------------------------- /src/std/templates.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | struct FormatStatus { 7 | Object *formatted; 8 | ExecutionResult result; 9 | }; 10 | 11 | FormatStatus formatArray(Array *format, Interpreter &interp) { 12 | std::unordered_set encountered; 13 | struct FormatTask { 14 | Array *copy; 15 | Array *format; 16 | size_t pos; 17 | }; 18 | std::stack tasks; 19 | Array *copy = interp.heap.makeArrayObject(Value(), 0); 20 | tasks.push(FormatTask{copy, format, 0}); 21 | encountered.insert(format); 22 | while (!tasks.empty()) { 23 | FormatTask &task = tasks.top(); 24 | if (task.pos >= task.format->values.size()) { 25 | tasks.pop(); 26 | continue; 27 | } 28 | Value val = task.format->values[task.pos]; 29 | switch (val.type) { 30 | case ValueType::Placeholder: { 31 | ExecutionResult result = interp.callInterpreter(val.arr, true); 32 | if_unlikely(result.result != ExecutionResultType::Success) { 33 | return FormatStatus{nullptr, result}; 34 | } 35 | if_unlikely(!interp.evalStack.assertDepth(1)) { 36 | return FormatStatus{nullptr, EvalStackUnderflow()}; 37 | } 38 | task.copy->values.push_back(interp.evalStack.popBack().value()); 39 | } break; 40 | case ValueType::SplicePlaceholder: { 41 | ExecutionResult result = interp.callInterpreter(val.arr, true); 42 | if_unlikely(result.result != ExecutionResultType::Success) { 43 | return FormatStatus{nullptr, result}; 44 | } 45 | if_unlikely(!interp.evalStack.assertDepth(1)) { 46 | return FormatStatus{nullptr, EvalStackUnderflow()}; 47 | } 48 | Value val = interp.evalStack.popBack().value(); 49 | if (val.type != ValueType::Array) { 50 | return FormatStatus{nullptr, TypeError()}; 51 | } 52 | for (Value elem : val.arr->values) { 53 | task.copy->values.push_back(elem); 54 | } 55 | } break; 56 | case ValueType::Array: { 57 | if (encountered.find(val.arr) != encountered.end()) { 58 | return FormatStatus{ 59 | nullptr, 60 | ExecutionResult{ExecutionResultType::Error, 61 | U"Recursive template", Value()}}; 62 | } 63 | encountered.insert(val.arr); 64 | Array *copy = interp.heap.makeArrayObject(Value(), 0); 65 | tasks.push(FormatTask{copy, val.arr, 0}); 66 | Value pointerToCopy; 67 | pointerToCopy.type = ValueType::Array; 68 | pointerToCopy.arr = copy; 69 | task.copy->values.push_back(pointerToCopy); 70 | } break; 71 | default: 72 | task.copy->values.push_back(val); 73 | break; 74 | } 75 | task.pos++; 76 | } 77 | return FormatStatus{copy, Success()}; 78 | } 79 | 80 | ExecutionResult templateEvaluateOp(Interpreter &interp) { 81 | if (!interp.evalStack.assertDepth(1)) { 82 | return EvalStackUnderflow(); 83 | } 84 | Value val = interp.evalStack.popBack().value(); 85 | FormatStatus result; 86 | if (val.type != ValueType::Array) { 87 | return TypeError(); 88 | } 89 | result = formatArray(val.arr, interp); 90 | if (result.result.result != ExecutionResultType::Success) { 91 | return result.result; 92 | } 93 | Value valResult; 94 | valResult.type = ValueType::Array; 95 | valResult.object = result.formatted; 96 | interp.evalStack.pushBack(valResult); 97 | return Success(); 98 | } 99 | 100 | void addTemplatesNativeWords(Interpreter &interp) { 101 | interp.defineNativeWord(U"@", templateEvaluateOp); 102 | } -------------------------------------------------------------------------------- /src/std/typing.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | Value getTypeOp(Value arg, Interpreter& interp) { 10 | Value result; 11 | result.type = ValueType::String; 12 | switch (arg.type) { 13 | case ValueType::Array: 14 | result.str = interp.heap.makeStringObject(U"Array"); 15 | break; 16 | case ValueType::Boolean: 17 | result.str = interp.heap.makeStringObject(U"Boolean"); 18 | break; 19 | case ValueType::NativeWord: 20 | result.str = interp.heap.makeStringObject(U"NativeWord"); 21 | break; 22 | case ValueType::Nil: 23 | result.str = interp.heap.makeStringObject(U"Nil"); 24 | break; 25 | case ValueType::Numeric: 26 | result.str = interp.heap.makeStringObject(U"Numeric"); 27 | break; 28 | case ValueType::Placeholder: 29 | result.str = interp.heap.makeStringObject(U"Placeholder"); 30 | break; 31 | case ValueType::String: 32 | result.str = interp.heap.makeStringObject(U"String"); 33 | break; 34 | case ValueType::Word: 35 | result.str = interp.heap.makeStringObject(U"Word"); 36 | break; 37 | case ValueType::WordAssign: 38 | result.str = interp.heap.makeStringObject(U"WordAssign"); 39 | break; 40 | case ValueType::WordDeclare: 41 | result.str = interp.heap.makeStringObject(U"WordDeclare"); 42 | break; 43 | case ValueType::NativeHandle: 44 | result.str = interp.heap.makeStringObject(U"NativeHandle"); 45 | break; 46 | case ValueType::SplicePlaceholder: 47 | result.str = interp.heap.makeStringObject(U"SplicePlaceholder"); 48 | break; 49 | } 50 | return result; 51 | } 52 | 53 | bool validUTF32(char32_t ch); 54 | 55 | ExecutionResult intToCharOp(Interpreter& interp) { 56 | if (!interp.evalStack.assertDepth(1)) { 57 | return EvalStackUnderflow(); 58 | } 59 | Value val = interp.evalStack.popBack().value(); 60 | if (val.type != ValueType::Numeric) { 61 | return TypeError(); 62 | } 63 | char32_t arr[2]; 64 | if (val.numericValue < 0 || !validUTF32((char32_t)val.numericValue)) { 65 | return ExecutionResult{ExecutionResultType::Error, 66 | U"Unknown unicode character", Value()}; 67 | } 68 | arr[0] = (char32_t)(val.numericValue); 69 | arr[1] = U'\0'; 70 | 71 | String* obj = interp.heap.makeStringObject(arr); 72 | Value result; 73 | result.type = ValueType::String; 74 | result.str = obj; 75 | interp.evalStack.pushBack(result); 76 | return Success(); 77 | } 78 | 79 | ExecutionResult charToIntOp(Interpreter& interp) { 80 | if (!interp.evalStack.assertDepth(1)) { 81 | return EvalStackUnderflow(); 82 | } 83 | Value val = interp.evalStack.popBack().value(); 84 | if (val.type != ValueType::String) { 85 | return TypeError(); 86 | } 87 | if (val.str->get().length() != 1) { 88 | return ExecutionResult{ 89 | ExecutionResultType::Error, 90 | U"Can't convert not one character string to char", Value()}; 91 | } 92 | char32_t character = val.str->get()[0]; 93 | Value result; 94 | result.type = ValueType::Numeric; 95 | result.numericValue = (int64_t)character; 96 | interp.evalStack.pushBack(result); 97 | return Success(); 98 | } 99 | 100 | ExecutionResult toArrayOp(Interpreter& interp) { 101 | if (!interp.evalStack.assertDepth(1)) { 102 | return EvalStackUnderflow(); 103 | } 104 | Value val = interp.evalStack.popBack().value(); 105 | if (val.type != ValueType::Placeholder && 106 | val.type != ValueType::SplicePlaceholder && 107 | val.type != ValueType::Array) { 108 | return TypeError(); 109 | } 110 | val.type = ValueType::Array; 111 | interp.evalStack.pushBack(val); 112 | return Success(); 113 | } 114 | 115 | ExecutionResult toPlaceholderOp(Interpreter& interp) { 116 | if (!interp.evalStack.assertDepth(1)) { 117 | return EvalStackUnderflow(); 118 | } 119 | Value val = interp.evalStack.popBack().value(); 120 | if (val.type != ValueType::Placeholder && 121 | val.type != ValueType::SplicePlaceholder && 122 | val.type != ValueType::Array) { 123 | return TypeError(); 124 | } 125 | val.type = ValueType::Placeholder; 126 | interp.evalStack.pushBack(val); 127 | return Success(); 128 | } 129 | 130 | ExecutionResult toSplicePlaceholderOp(Interpreter& interp) { 131 | if (!interp.evalStack.assertDepth(1)) { 132 | return EvalStackUnderflow(); 133 | } 134 | Value val = interp.evalStack.popBack().value(); 135 | if (val.type != ValueType::Placeholder && 136 | val.type != ValueType::SplicePlaceholder && 137 | val.type != ValueType::Array) { 138 | return TypeError(); 139 | } 140 | val.type = ValueType::SplicePlaceholder; 141 | interp.evalStack.pushBack(val); 142 | return Success(); 143 | } 144 | 145 | ExecutionResult toIntegerOp(Interpreter& interp) { 146 | if (!interp.evalStack.assertDepth(1)) { 147 | return EvalStackUnderflow(); 148 | } 149 | Value val = interp.evalStack.popBack().value(); 150 | if (val.type != ValueType::String) { 151 | return TypeError(); 152 | } 153 | Value result; 154 | result.type = ValueType::Numeric; 155 | size_t i = 0; 156 | if (val.str->get().empty()) { 157 | goto failure; 158 | } 159 | if (val.str->get() == U"-") { 160 | goto failure; 161 | } 162 | if (val.str->get() == U"0" || val.str->get() == U"-0") { 163 | result.numericValue = 0; 164 | goto success; 165 | } 166 | if (val.str->get()[0] == U'0' || val.str->get().substr(0, 2) == U"-0") { 167 | goto success; 168 | } 169 | if (val.str->get()[0] == U'-') { 170 | i = 1; 171 | } 172 | for (; i < val.str->get().size(); ++i) { 173 | if (U'0' > val.str->get()[i] || U'9' < val.str->get()[i]) { 174 | goto failure; 175 | } 176 | } 177 | result.numericValue = parseIntFrom(val.str->get()); 178 | success: 179 | interp.evalStack.pushBack(result); 180 | return Success(); 181 | failure: 182 | interp.evalStack.pushBack(Value()); 183 | return Success(); 184 | } 185 | 186 | ExecutionResult toWordOp(Interpreter& interp) { 187 | if (!interp.evalStack.assertDepth(1)) { 188 | return EvalStackUnderflow(); 189 | } 190 | Value val = interp.evalStack.popBack().value(); 191 | if (val.type != ValueType::String && val.type != ValueType::Word && 192 | val.type != ValueType::WordAssign && 193 | val.type != ValueType::WordDeclare) { 194 | return TypeError(); 195 | } 196 | if (val.type == ValueType::String) { 197 | if (val.str->get().empty()) { 198 | interp.evalStack.pushBack(Value()); 199 | return Success(); 200 | } 201 | for (size_t i = 0; i < val.str->get().size(); ++i) { 202 | char32_t c = val.str->get()[i]; 203 | if (i == 0) { 204 | if (!isValidIdentStart(c)) { 205 | interp.evalStack.pushBack(Value()); 206 | return Success(); 207 | } 208 | } else if (!isValidIdentChar(c)) { 209 | interp.evalStack.pushBack(Value()); 210 | return Success(); 211 | } 212 | } 213 | } 214 | val.type = ValueType::Word; 215 | interp.evalStack.pushBack(val); 216 | return Success(); 217 | } 218 | 219 | ExecutionResult toWordAssignOp(Interpreter& interp) { 220 | if (!interp.evalStack.assertDepth(1)) { 221 | return EvalStackUnderflow(); 222 | } 223 | Value val = interp.evalStack.popBack().value(); 224 | if (val.type != ValueType::String && val.type != ValueType::Word && 225 | val.type != ValueType::WordAssign && 226 | val.type != ValueType::WordDeclare) { 227 | return TypeError(); 228 | } 229 | if (val.type == ValueType::String) { 230 | if (val.str->get().empty()) { 231 | interp.evalStack.pushBack(Value()); 232 | return Success(); 233 | } 234 | for (size_t i = 0; i < val.str->get().size(); ++i) { 235 | char32_t c = val.str->get()[i]; 236 | if (i == 0) { 237 | if (!isValidIdentStart(c)) { 238 | interp.evalStack.pushBack(Value()); 239 | return Success(); 240 | } 241 | } else if (!isValidIdentChar(c)) { 242 | interp.evalStack.pushBack(Value()); 243 | return Success(); 244 | } 245 | } 246 | } 247 | val.type = ValueType::WordAssign; 248 | interp.evalStack.pushBack(val); 249 | return Success(); 250 | } 251 | 252 | ExecutionResult toWordDeclareOp(Interpreter& interp) { 253 | if (!interp.evalStack.assertDepth(1)) { 254 | return EvalStackUnderflow(); 255 | } 256 | Value val = interp.evalStack.popBack().value(); 257 | if (val.type != ValueType::String && val.type != ValueType::Word && 258 | val.type != ValueType::WordAssign && 259 | val.type != ValueType::WordDeclare) { 260 | return TypeError(); 261 | } 262 | if (val.type == ValueType::String) { 263 | if (val.str->get().empty()) { 264 | interp.evalStack.pushBack(Value()); 265 | return Success(); 266 | } 267 | for (size_t i = 1; i < val.str->get().size(); ++i) { 268 | char32_t c = val.str->get()[i]; 269 | if (i == 0) { 270 | if (!isValidIdentStart(c)) { 271 | interp.evalStack.pushBack(Value()); 272 | return Success(); 273 | } 274 | } else if (!isValidIdentChar(c)) { 275 | interp.evalStack.pushBack(Value()); 276 | return Success(); 277 | } 278 | } 279 | } 280 | val.type = ValueType::WordDeclare; 281 | interp.evalStack.pushBack(val); 282 | return Success(); 283 | } 284 | 285 | ExecutionResult toStringOp(Interpreter& interp) { 286 | if (!interp.evalStack.assertDepth(1)) { 287 | return EvalStackUnderflow(); 288 | } 289 | Value val = interp.evalStack.popBack().value(); 290 | if (val.type == ValueType::String || val.type == ValueType::Word || 291 | val.type == ValueType::WordAssign || 292 | val.type == ValueType::WordDeclare) { 293 | val.type = ValueType::String; 294 | } else { 295 | String* str = interp.heap.makeStringObject(prettyprint(val, interp)); 296 | val.type = ValueType::String; 297 | val.str = str; 298 | } 299 | interp.evalStack.pushBack(val); 300 | return Success(); 301 | } 302 | 303 | ExecutionResult toNativeWordOp(Interpreter& interp) { 304 | if (!interp.evalStack.assertDepth(1)) { 305 | return EvalStackUnderflow(); 306 | } 307 | Value val = interp.evalStack.popBack().value(); 308 | if (val.type == ValueType::String || val.type == ValueType::Word || 309 | val.type == ValueType::WordAssign || 310 | val.type == ValueType::WordDeclare) { 311 | NativeWord word = interp.queryNativeWord(val.str); 312 | if (word == nullptr) { 313 | interp.evalStack.pushBack(Value()); 314 | return Success(); 315 | } else { 316 | val.type = ValueType::NativeWord; 317 | val.word = word; 318 | interp.evalStack.pushBack(val); 319 | return Success(); 320 | } 321 | } 322 | return TypeError(); 323 | } 324 | 325 | MAKE_FROM_UNARY_OPERATOR(typeNativeWord, getTypeOp) 326 | 327 | void addTypingNativeWords(Interpreter& interp) { 328 | interp.defineNativeWord(U"type", typeNativeWord); 329 | interp.defineNativeWord(U"chr", intToCharOp); 330 | interp.defineNativeWord(U"ord", charToIntOp); 331 | interp.defineNativeWord(U"to_numeric", toIntegerOp); 332 | interp.defineNativeWord(U"to_array", toArrayOp); 333 | interp.defineNativeWord(U"to_placeholder", toPlaceholderOp); 334 | interp.defineNativeWord(U"to_slice_placeholder", toSplicePlaceholderOp); 335 | interp.defineNativeWord(U"to_word", toWordOp); 336 | interp.defineNativeWord(U"to_word_assign", toWordAssignOp); 337 | interp.defineNativeWord(U"to_word_declare", toWordDeclareOp); 338 | interp.defineNativeWord(U"to_string", toStringOp); 339 | interp.defineNativeWord(U"to_native_word", toNativeWordOp); 340 | } -------------------------------------------------------------------------------- /test_root: -------------------------------------------------------------------------------- 1 | SbQVHcuecUS-lv9xehGSGg 2 | -------------------------------------------------------------------------------- /tests/cases/abs/expectedOutput.txt: -------------------------------------------------------------------------------- 1 | 5 -------------------------------------------------------------------------------- /tests/cases/abs/main.fscript: -------------------------------------------------------------------------------- 1 | #function to calculate absolute value 2 | [$n 3 | n 0 < [ 4 | 0 n - return 5 | ] [ 6 | n return 7 | ] if_else 8 | ] $abs 9 | -5 abs! serialize write -------------------------------------------------------------------------------- /tests/cases/brainfuck/expectedOutput.txt: -------------------------------------------------------------------------------- 1 | hello world -------------------------------------------------------------------------------- /tests/cases/brainfuck/main.fscript: -------------------------------------------------------------------------------- 1 | # data tape 2 | 30000 0 alloc $DM 3 | "+[-[<<[+[--->]-[<<<]]]>>>-]>-.---.>..>.<<<<-.<+.>>>>>.>.<<.<-." $CM 4 | 5 | # data pointer 6 | 0 $DP 7 | # code pointer 8 | 0 $CP 9 | 10 | # increment/decrement helpers 11 | [ 1 + dup {} == [drop 0] if] $increment_template 12 | [ 1 - dup 0 1 - == [drop {} 1 -] if] $decrement_template 13 | 14 | # increment/decrement functions for tape values 15 | 256 increment_template@ $val_increment 16 | 256 decrement_template@ $val_decrement 17 | 18 | # increment/decrement functions for memory register 19 | 30000 increment_template@ $mem_increment 20 | 30000 decrement_template@ $mem_decrement 21 | 22 | # caching len 23 | CM len $CL 24 | 25 | # memory helpers 26 | [DM DP peek] $mem_load 27 | [$val DM DP val poke] $mem_store 28 | 29 | # action handlers 30 | 256 Nil alloc $handlers 31 | 32 | # handling + 33 | handlers "+" ord [ 34 | mem_load! val_increment! mem_store! 35 | ] poke 36 | 37 | # handling - 38 | handlers "-" ord [ 39 | mem_load! val_decrement! mem_store! 40 | ] poke 41 | 42 | # handling > 43 | handlers ">" ord [ 44 | DP 1 + =DP 45 | ] poke 46 | 47 | # handling < 48 | handlers "<" ord [ 49 | DP 1 - =DP 50 | ] poke 51 | 52 | # handling . 53 | handlers "." ord [ 54 | mem_load! chr write 55 | ] poke 56 | 57 | # handling [ 58 | handlers "[" ord [ 59 | mem_load! 0 == [ 60 | 1 $brc 61 | [brc 0 !=] [ 62 | CP 1 + =CP 63 | CM CP peek "[" == [ 64 | brc 1 + =brc 65 | ] if 66 | CM CP peek "]" == [ 67 | brc 1 - =brc 68 | ] if 69 | ] while 70 | ] if 71 | ] poke 72 | 73 | # handling [ 74 | handlers "]" ord [ 75 | mem_load! 0 != [ 76 | 1 $brc 77 | [brc 0 !=] [ 78 | CP 1 - =CP 79 | CM CP peek "[" == [ 80 | brc 1 - =brc 81 | ] if 82 | CM CP peek "]" == [ 83 | brc 1 + =brc 84 | ] if 85 | ] while 86 | CP 1 - =CP 87 | ] if 88 | ] poke 89 | 90 | # while in bounds 91 | [CP CL <] [ 92 | 93 | # fetch current opcode 94 | CM CP peek $opcode 95 | 96 | # get and execute handler 97 | handlers opcode ord peek ! 98 | 99 | # move to the next instruction 100 | CP 1 + =CP 101 | 102 | ] while -------------------------------------------------------------------------------- /tests/cases/is_prime/expectedOutput.txt: -------------------------------------------------------------------------------- 1 | False 2 | True 3 | True 4 | False 5 | True 6 | False 7 | True 8 | False 9 | False 10 | False 11 | -------------------------------------------------------------------------------- /tests/cases/is_prime/main.fscript: -------------------------------------------------------------------------------- 1 | [ $n 2 | n 2 < [ 3 | False return 4 | ] if 5 | [2 $i] [i i * n <=] [i 1 + =i] [ 6 | n i % 0 == [ 7 | False return 8 | ] if 9 | ] for 10 | True 11 | ] $is_prime 12 | 13 | [1 $i] [i 10 <=] [i 1 + =i] [ 14 | i is_prime! serialize "\n" + write 15 | ] for -------------------------------------------------------------------------------- /tests/cases/pow_func_gen/expectedOutput.txt: -------------------------------------------------------------------------------- 1 | 4 2 | 25 3 | 216 4 | -------------------------------------------------------------------------------- /tests/cases/pow_func_gen/main.fscript: -------------------------------------------------------------------------------- 1 | [$n [$n] [n] n * [*] n 1 - * + +] $gen_pow_func 2 | 3 gen_pow_func! $cube 3 | 2 gen_pow_func! $sqr 4 | 1 gen_pow_func! $id 5 | 6 | 4 id! serialize "\n" + write 7 | 5 sqr! serialize "\n" + write 8 | 6 cube! serialize "\n" + write -------------------------------------------------------------------------------- /tests/cases/quicksort/main.fscript: -------------------------------------------------------------------------------- 1 | [ $hi $lo $A 2 | lo hi < [ 3 | A lo hi partition! $p 4 | A lo p quicksort_rec! 5 | A p 1 + hi quicksort_rec! 6 | ] if 7 | A 8 | ] $quicksort_rec 9 | 10 | [ $hi $lo $A 11 | hi lo + 2 / $mid 12 | A mid peek $pivot 13 | lo $i 14 | hi $j 15 | [i j <=] [ 16 | 17 | [A i peek pivot <] [i 1 + =i] while 18 | 19 | [A j peek pivot >] [j 1 - =j] while 20 | 21 | i j >= [ 22 | break 23 | ] if 24 | 25 | A i peek $tmp 26 | A i A j peek poke 27 | A j tmp poke 28 | 29 | i 1 + =i 30 | j 1 - =j 31 | 32 | ] while 33 | j 34 | ] $partition 35 | 36 | [ $A A 0 A len 1 - quicksort_rec!] $quicksort 37 | 38 | "cases/quicksort/unsorted.fscript" readfile parse, 39 | arr quicksort! serialize write 40 | -------------------------------------------------------------------------------- /tests/cases/rot13/expectedOutput.txt: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /tests/cases/rot13/main.fscript: -------------------------------------------------------------------------------- 1 | "test" $str 2 | 3 | [ $n $str 4 | "" $result 5 | [0 $i] [i str len <] [i 1 + =i] [ 6 | str i peek $char 7 | char ord "a" ord - $ind 8 | ind n + 26 % =ind 9 | ind "a" ord + chr $char 10 | result char + =result 11 | ] for 12 | result 13 | ] $rot_n 14 | 15 | [13 rot_n!] $rot_13 16 | 17 | str rot_13! rot_13! write -------------------------------------------------------------------------------- /tests/helper.py: -------------------------------------------------------------------------------- 1 | import json 2 | import glob 3 | 4 | complexCasesPath = "cases/*" 5 | simpleCasesPath = "simpleCases/*" 6 | 7 | def create_tests(func_name, file): 8 | def decorator(cls): 9 | func = getattr(cls, func_name) 10 | # Set new funcs to class: 11 | with open(file, 'r') as fh: 12 | data = json.loads(fh.read())['data'] 13 | for i, params in enumerate(data): 14 | def tmp(params=params): # need for http://stackoverflow.com/q/7546285/1113207 15 | def wrapper(self, *args, **kwargs): 16 | return func(self, **params) 17 | return wrapper 18 | setattr(cls, func_name + '_' + params["name"], tmp()) 19 | # Remove func from class: 20 | setattr(cls, func_name, None) 21 | return cls 22 | return decorator 23 | 24 | def cases_from_files(func_name): 25 | def decorator(cls): 26 | func = getattr(cls, func_name) 27 | # Set new funcs to class: 28 | # print("cases: ", glob.glob("cases/*")) 29 | casesFolders = glob.glob(complexCasesPath) 30 | for i, params in enumerate(casesFolders): 31 | def tmp(params=params): # need for http://stackoverflow.com/q/7546285/1113207 32 | def wrapper(self, *args, **kwargs): 33 | return func(self, params) 34 | return wrapper 35 | setattr(cls, func_name + '_' + str(params.replace('\\','/').split('/')[1]), tmp()) 36 | # Remove func from class: 37 | setattr(cls, func_name, None) 38 | return cls 39 | return decorator 40 | 41 | def create_simple_cases(func_name): 42 | def decorator(cls): 43 | func = getattr(cls, func_name) 44 | casesFiles = glob.glob(simpleCasesPath) 45 | # Set new funcs to class: 46 | for file in casesFiles: 47 | with open(file, 'r') as fh: 48 | data = json.loads(fh.read())['cases'] 49 | for i, params in enumerate(data): 50 | def tmp(params=params): # need for http://stackoverflow.com/q/7546285/1113207 51 | def wrapper(self, *args, **kwargs): 52 | return func(self, **params) 53 | return wrapper 54 | setattr(cls, func_name + '_' + file.replace('\\','/').split('/')[1].split('.')[0] + '_' + params["name"], tmp()) 55 | # Remove func from class: 56 | setattr(cls, func_name, None) 57 | return cls 58 | return decorator -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ForthScriptLang/forthscript/9dbeed123fbe277e08f7e45ff022a12f8abc4ce7/tests/requirements.txt -------------------------------------------------------------------------------- /tests/simpleCases.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "name": "0", 5 | "input": "[dup 0 != [swap over % gcd!] [drop] if_else] $gcd 6 9 gcd! serialize write", 6 | "expectedOutput": "3" 7 | }, 8 | { 9 | "name": "1", 10 | "input": "3 $a [5 $a]! a serialize write", 11 | "expectedOutput": "3" 12 | }, 13 | { 14 | "name": "2", 15 | "input": "3 $a [5 $a], a serialize write", 16 | "expectedOutput": "5" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /tests/simpleCases/arithmetic.json: -------------------------------------------------------------------------------- 1 | { 2 | "cases": [ 3 | { 4 | "name": "0", 5 | "input": "1 1 + to_string write", 6 | "expectedOutput": "2" 7 | }, 8 | { 9 | "name": "1", 10 | "input": "1 1 - to_string write", 11 | "expectedOutput": "0" 12 | }, 13 | { 14 | "name": "2", 15 | "input": "99999999 99999999 + to_string write", 16 | "expectedOutput": "199999998" 17 | }, 18 | { 19 | "name": "3", 20 | "input": "4 8 * to_string write", 21 | "expectedOutput": "32" 22 | }, 23 | { 24 | "name": "4", 25 | "input": "34 2 / to_string write", 26 | "expectedOutput": "17" 27 | }, 28 | { 29 | "name": "5", 30 | "input": "34 0 / to_string write", 31 | "expectedOutput": "Runtime error: Division by zero\n" 32 | }, 33 | { 34 | "name": "6", 35 | "input": "4 3 / to_string write", 36 | "expectedOutput": "1" 37 | }, 38 | { 39 | "name": "7", 40 | "input": "7 3 % to_string write", 41 | "expectedOutput": "1" 42 | }, 43 | { 44 | "name": "8", 45 | "input": "7 0 % to_string write", 46 | "expectedOutput": "Runtime error: Division by zero\n" 47 | }, 48 | { 49 | "name": "9", 50 | "input": "7 12 % to_string write", 51 | "expectedOutput": "7" 52 | }, 53 | { 54 | "name": "10", 55 | "input": "100 10 % to_string write", 56 | "expectedOutput": "0" 57 | }, 58 | { 59 | "name": "11", 60 | "input": "9 109 - to_string write", 61 | "expectedOutput": "-100" 62 | }, 63 | { 64 | "name": "12", 65 | "input": "9 109 - 10 * to_string write", 66 | "expectedOutput": "-1000" 67 | }, 68 | { 69 | "name": "13", 70 | "input": "3 -2 + to_string write", 71 | "expectedOutput": "1" 72 | }, 73 | { 74 | "name": "14", 75 | "input": "3 -3 + to_string write", 76 | "expectedOutput": "0" 77 | }, 78 | { 79 | "name": "15", 80 | "input": "3 -4 + to_string write", 81 | "expectedOutput": "-1" 82 | }, 83 | { 84 | "name": "16", 85 | "input": "-3 -4 + to_string write", 86 | "expectedOutput": "-7" 87 | }, 88 | { 89 | "name": "17", 90 | "input": "3 -4 * to_string write", 91 | "expectedOutput": "-12" 92 | }, 93 | { 94 | "name": "18", 95 | "input": "-3 -4 * to_string write", 96 | "expectedOutput": "12" 97 | }, 98 | { 99 | "name": "19", 100 | "input": "3 -4 -8 + * to_string write", 101 | "expectedOutput": "-36" 102 | }, 103 | { 104 | "name": "20", 105 | "input": "3 -4 -8 + * to_string write", 106 | "expectedOutput": "-36" 107 | }, 108 | { 109 | "name": "21", 110 | "input": "-3 4 + to_string write", 111 | "expectedOutput": "1" 112 | }, 113 | { 114 | "name": "22", 115 | "input": "-3 4 + to_string write", 116 | "expectedOutput": "1" 117 | }, 118 | { 119 | "name": "23", 120 | "input": "-48 48 + to_string write", 121 | "expectedOutput": "0" 122 | }, 123 | { 124 | "name": "24", 125 | "input": "-48 48 + to_string write", 126 | "expectedOutput": "0" 127 | }, 128 | { 129 | "name": "25", 130 | "input": "-48 48 % to_string write", 131 | "expectedOutput": "0" 132 | }, 133 | { 134 | "name": "26", 135 | "input": "-48 23 % to_string write", 136 | "expectedOutput": "-2" 137 | }, 138 | { 139 | "name": "27", 140 | "input": "-48 -0 + to_string write", 141 | "expectedOutput": "-48" 142 | }, 143 | { 144 | "name": "27", 145 | "input": "-48 -0 / to_string write", 146 | "expectedOutput": "Runtime error: Division by zero\n" 147 | } 148 | ] 149 | } -------------------------------------------------------------------------------- /tests/simpleCases/comparisons.json: -------------------------------------------------------------------------------- 1 | { 2 | "cases": [ 3 | { 4 | "name": "0", 5 | "input": "33 5 > to_string write", 6 | "expectedOutput": "True" 7 | }, 8 | { 9 | "name": "1", 10 | "input": "33 5 < to_string write", 11 | "expectedOutput": "False" 12 | }, 13 | { 14 | "name": "2", 15 | "input": "-33 5 < to_string write", 16 | "expectedOutput": "True" 17 | }, 18 | { 19 | "name": "3", 20 | "input": "-33 5 > to_string write", 21 | "expectedOutput": "False" 22 | }, 23 | { 24 | "name": "4", 25 | "input": "-33 -5 > to_string write", 26 | "expectedOutput": "False" 27 | }, 28 | { 29 | "name": "5", 30 | "input": "-33 -5 < to_string write", 31 | "expectedOutput": "True" 32 | }, 33 | { 34 | "name": "6", 35 | "input": "-33 -59 < to_string write", 36 | "expectedOutput": "False" 37 | }, 38 | { 39 | "name": "7", 40 | "input": "0 0 < to_string write", 41 | "expectedOutput": "False" 42 | }, 43 | { 44 | "name": "8", 45 | "input": "0 0 > to_string write", 46 | "expectedOutput": "False" 47 | }, 48 | { 49 | "name": "9", 50 | "input": "0 0 == to_string write", 51 | "expectedOutput": "True" 52 | }, 53 | { 54 | "name": "10", 55 | "input": "0 0 >= to_string write", 56 | "expectedOutput": "True" 57 | }, 58 | { 59 | "name": "11", 60 | "input": "0 0 <= to_string write", 61 | "expectedOutput": "True" 62 | }, 63 | { 64 | "name": "12", 65 | "input": "199 -15 >= to_string write", 66 | "expectedOutput": "True" 67 | }, 68 | { 69 | "name": "13", 70 | "input": "-1000 999 <= to_string write", 71 | "expectedOutput": "True" 72 | }, 73 | { 74 | "name": "14", 75 | "input": "-1000 -1000 == to_string write", 76 | "expectedOutput": "True" 77 | }, 78 | { 79 | "name": "15", 80 | "input": "-1000 31000 == to_string write", 81 | "expectedOutput": "False" 82 | }, 83 | { 84 | "name": "16", 85 | "input": "0 -0 == to_string write", 86 | "expectedOutput": "True" 87 | } 88 | ] 89 | } -------------------------------------------------------------------------------- /tests/simpleCases/func_chr.json: -------------------------------------------------------------------------------- 1 | { 2 | "cases": [ 3 | { 4 | "name": "0", 5 | "input": "97 chr write", 6 | "expectedOutput": "a" 7 | }, 8 | { 9 | "name": "1", 10 | "input": "89 chr write", 11 | "expectedOutput": "Y" 12 | }, 13 | { 14 | "name": "2", 15 | "input": "126 chr write", 16 | "expectedOutput": "~" 17 | }, 18 | { 19 | "name": "3", 20 | "input": "32 chr write", 21 | "expectedOutput": " " 22 | }, 23 | { 24 | "name": "4", 25 | "input": "47 chr write", 26 | "expectedOutput": "/" 27 | }, 28 | { 29 | "name": "5", 30 | "input": "\"1\" chr to_string write", 31 | "expectedOutput": "Runtime error: Type error\n" 32 | }, 33 | { 34 | "name": "6", 35 | "input": "F chr to_string write", 36 | "expectedOutput": "Runtime error: Type error\n" 37 | }, 38 | { 39 | "name": "7", 40 | "input": "\"notchar\" chr to_string write", 41 | "expectedOutput": "Runtime error: Type error\n" 42 | }, 43 | { 44 | "name": "8", 45 | "input": "\"NUL\" chr to_string write", 46 | "expectedOutput": "Runtime error: Type error\n" 47 | }, 48 | { 49 | "name": "9", 50 | "input": "0 chr write", 51 | "expectedOutput": "" 52 | }, 53 | { 54 | "name": "11", 55 | "input": "-10 chr write", 56 | "expectedOutput": "Runtime error: Unknown unicode character\n" 57 | }, 58 | { 59 | "name": "12", 60 | "input": "1118208 chr write", 61 | "expectedOutput": "Runtime error: Unknown unicode character\n" 62 | }, 63 | { 64 | "name": "12", 65 | "input": "1114112 chr write", 66 | "expectedOutput": "Runtime error: Unknown unicode character\n" 67 | }, 68 | { 69 | "name": "13", 70 | "input": "chr", 71 | "expectedOutput": "Runtime error: Evaluation stack underflow\n" 72 | } 73 | ] 74 | } -------------------------------------------------------------------------------- /tests/simpleCases/func_dup.json: -------------------------------------------------------------------------------- 1 | { 2 | "cases": [ 3 | { 4 | "name": "0", 5 | "input": "1 dup to_string write to_string write", 6 | "expectedOutput": "11" 7 | }, 8 | { 9 | "name": "1", 10 | "input": "\"1\" dup write write", 11 | "expectedOutput": "11" 12 | }, 13 | { 14 | "name": "2", 15 | "input": "x dup to_string write to_string write", 16 | "expectedOutput": "NilNil" 17 | }, 18 | { 19 | "name": "3", 20 | "input": "[] dup to_string write to_string write", 21 | "expectedOutput": "[][]" 22 | }, 23 | { 24 | "name": "4", 25 | "input": "[dup] dup to_string write to_string write", 26 | "expectedOutput": "[dup][dup]" 27 | }, 28 | { 29 | "name": "5", 30 | "input": "[[1] [2]] dup to_string write to_string write", 31 | "expectedOutput": "[[1] [2]][[1] [2]]" 32 | }, 33 | { 34 | "name": "6", 35 | "input": "dup", 36 | "expectedOutput": "Runtime error: Evaluation stack underflow\n" 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /tests/simpleCases/func_len.json: -------------------------------------------------------------------------------- 1 | { 2 | "cases": [ 3 | { 4 | "name": "0", 5 | "input": "[2 3 4] len to_string write", 6 | "expectedOutput": "3" 7 | }, 8 | { 9 | "name": "1", 10 | "input": "[2] len to_string write", 11 | "expectedOutput": "1" 12 | }, 13 | { 14 | "name": "2", 15 | "input": "[] len to_string write", 16 | "expectedOutput": "0" 17 | }, 18 | { 19 | "name": "3", 20 | "input": "\"lorem\" len to_string write", 21 | "expectedOutput": "5" 22 | }, 23 | { 24 | "name": "4", 25 | "input": "1 len to_string write", 26 | "expectedOutput": "Runtime error: Type error\n" 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /tests/simpleCases/func_ord.json: -------------------------------------------------------------------------------- 1 | { 2 | "cases": [ 3 | { 4 | "name": "0", 5 | "input": "\"a\" ord to_string write", 6 | "expectedOutput": "97" 7 | }, 8 | { 9 | "name": "1", 10 | "input": "\"Y\" ord to_string write", 11 | "expectedOutput": "89" 12 | }, 13 | { 14 | "name": "2", 15 | "input": "\"~\" ord to_string write", 16 | "expectedOutput": "126" 17 | }, 18 | { 19 | "name": "3", 20 | "input": "\" \" ord to_string write", 21 | "expectedOutput": "32" 22 | }, 23 | { 24 | "name": "4", 25 | "input": "\"/\" ord to_string write", 26 | "expectedOutput": "47" 27 | }, 28 | { 29 | "name": "5", 30 | "input": "1 ord to_string write", 31 | "expectedOutput": "Runtime error: Type error\n" 32 | }, 33 | { 34 | "name": "6", 35 | "input": "F ord to_string write", 36 | "expectedOutput": "Runtime error: Type error\n" 37 | }, 38 | { 39 | "name": "7", 40 | "input": "\"notchar\" ord to_string write", 41 | "expectedOutput": "Runtime error: Can't convert not one character string to char\n" 42 | }, 43 | { 44 | "name": "8", 45 | "input": "\"NUL\" ord to_string write", 46 | "expectedOutput": "Runtime error: Can't convert not one character string to char\n" 47 | }, 48 | { 49 | "name": "9", 50 | "input": "ord", 51 | "expectedOutput": "Runtime error: Evaluation stack underflow\n" 52 | } 53 | ] 54 | } -------------------------------------------------------------------------------- /tests/simpleCases/func_peek.json: -------------------------------------------------------------------------------- 1 | { 2 | "cases": [ 3 | { 4 | "name": "0", 5 | "input": "[2 3 4] 0 peek to_string write", 6 | "expectedOutput": "2" 7 | }, 8 | { 9 | "name": "1", 10 | "input": "[2 3 4] 4 peek to_string write", 11 | "expectedOutput": "Runtime error: Out of bounds\n" 12 | }, 13 | { 14 | "name": "2", 15 | "input": "[2 3 4] -4 peek to_string write", 16 | "expectedOutput": "Runtime error: Out of bounds\n" 17 | }, 18 | { 19 | "name": "3", 20 | "input": "[2 3 4] -2 peek to_string write", 21 | "expectedOutput": "Runtime error: Out of bounds\n" 22 | }, 23 | { 24 | "name": "4", 25 | "input": "[2 3 4] 0 peek to_string write", 26 | "expectedOutput": "2" 27 | }, 28 | { 29 | "name": "5", 30 | "input": "[\"lorem\" \"ipsum\"] 0 peek write", 31 | "expectedOutput": "lorem" 32 | }, 33 | { 34 | "name": "6", 35 | "input": "[[2 3 4] [1 2 3]] 0 peek to_string write", 36 | "expectedOutput": "[2 3 4]" 37 | }, 38 | { 39 | "name": "7", 40 | "input": "\"lorem ipsum\" 5 peek write", 41 | "expectedOutput": " " 42 | }, 43 | { 44 | "name": "8", 45 | "input": "\"lorem ipsum\" 404 peek write", 46 | "expectedOutput": "Runtime error: Out of bounds\n" 47 | } 48 | ] 49 | } -------------------------------------------------------------------------------- /tests/simpleCases/func_poke.json: -------------------------------------------------------------------------------- 1 | { 2 | "cases": [ 3 | { 4 | "name": "0", 5 | "input": "[2 3 4] $arr arr 0 3 poke arr to_string write", 6 | "expectedOutput": "[3 3 4]" 7 | }, 8 | { 9 | "name": "1", 10 | "input": "[2 3 4] $arr arr 0 \"lorem\" poke arr to_string write", 11 | "expectedOutput": "[\"lorem\" 3 4]" 12 | }, 13 | { 14 | "name": "2", 15 | "input": "[2 3 4] $arr arr -10 \"lorem\" poke arr to_string write", 16 | "expectedOutput": "Runtime error: Out of bounds\n" 17 | }, 18 | { 19 | "name": "3", 20 | "input": "\"lorem imsum\" $arr arr 3 \"ne lorem\" poke arr to_string write", 21 | "expectedOutput": "Runtime error: Value is not indexable or immutable\n" 22 | }, 23 | { 24 | "name": "4", 25 | "input": "\"lorem imsum\" $arr arr -3 \"ne lorem\" poke arr to_string write", 26 | "expectedOutput": "Runtime error: Out of bounds\n" 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /tests/simpleCases/func_slice.json: -------------------------------------------------------------------------------- 1 | { 2 | "cases": [ 3 | { 4 | "name": "empty_string", 5 | "input": "\"\" 0 0 slice write", 6 | "expectedOutput": "" 7 | }, 8 | { 9 | "name": "empty_string_overrun", 10 | "input": "\"\" 1 2 slice write", 11 | "expectedOutput": "Runtime error: Out of bounds\n" 12 | }, 13 | { 14 | "name": "empty_array", 15 | "input": "[] 0 0 slice to_string write", 16 | "expectedOutput": "[]" 17 | }, 18 | { 19 | "name": "empty_array_overrun", 20 | "input": "[] 1 2 slice to_string write", 21 | "expectedOutput": "Runtime error: Out of bounds\n" 22 | }, 23 | { 24 | "name": "one_elem_array", 25 | "input": "[1] 0 1 slice to_string write", 26 | "expectedOutput": "[1]" 27 | }, 28 | { 29 | "name": "random_slice_array", 30 | "input": "[1 2 3 True \"heh\"] 1 4 slice to_string write", 31 | "expectedOutput": "[2 3 True]" 32 | }, 33 | { 34 | "name": "random_slice_array", 35 | "input": "[1 2 3 True \"heh\"] 1 4 slice to_string write", 36 | "expectedOutput": "[2 3 True]" 37 | } 38 | 39 | ] 40 | } -------------------------------------------------------------------------------- /tests/simpleCases/func_swap.json: -------------------------------------------------------------------------------- 1 | { 2 | "cases": [ 3 | { 4 | "name": "0", 5 | "input": "1 swap to_string write", 6 | "expectedOutput": "Runtime error: Evaluation stack underflow\n" 7 | }, 8 | { 9 | "name": "1", 10 | "input": "1 2 swap to_string write", 11 | "expectedOutput": "1" 12 | }, 13 | { 14 | "name": "2", 15 | "input": "\"pomello\" 2 swap write", 16 | "expectedOutput": "pomello" 17 | }, 18 | { 19 | "name": "3", 20 | "input": "1 \"pomello\" swap to_string write", 21 | "expectedOutput": "1" 22 | }, 23 | { 24 | "name": "3", 25 | "input": "swap", 26 | "expectedOutput": "Runtime error: Evaluation stack underflow\n" 27 | }, 28 | { 29 | "name": "4", 30 | "input": "\"1\" \"2\" swap swap swap swap swap swap swap swap write", 31 | "expectedOutput": "2" 32 | }, 33 | { 34 | "name": "5", 35 | "input": "\"1\" x swap write", 36 | "expectedOutput": "1" 37 | }, 38 | { 39 | "name": "6", 40 | "input": "x \"2\" swap to_string write", 41 | "expectedOutput": "Nil" 42 | } 43 | ] 44 | } -------------------------------------------------------------------------------- /tests/test.py: -------------------------------------------------------------------------------- 1 | import subprocess as sp 2 | import sys 3 | from getpass import getpass 4 | import telnetlib 5 | import multiprocessing as mp 6 | import os 7 | import threading 8 | import unittest 9 | import tempfile 10 | import glob 11 | from helper import create_tests, cases_from_files, create_simple_cases 12 | 13 | interpreterPath = "../build/forthscript" 14 | inputCasePath = "/main.fscript" 15 | outputCasePath = "/expectedOutput.txt" 16 | 17 | # remove this with simpleCases.json and create_tests 18 | @create_tests('test_old', 'simpleCases.json') 19 | class SimpleTestsOld(unittest.TestCase): 20 | def test_old(self, name, input, expectedOutput): 21 | f = tempfile.NamedTemporaryFile(delete=False) 22 | f.write(input.encode("utf-8")) 23 | f.close() 24 | inputFilePath = f.name 25 | proc = sp.Popen([interpreterPath, inputFilePath], stdout=sp.PIPE) 26 | (output, err) = proc.communicate() 27 | proc.wait() 28 | actualOutput = output.decode("utf-8").replace('\r', '') 29 | #print(f"[ {input} ] => {expectedOutput} ?") 30 | assert repr(actualOutput) == repr( 31 | expectedOutput), f"{repr(actualOutput)} != {repr(expectedOutput)}" 32 | os.unlink(f.name) 33 | 34 | 35 | @cases_from_files('test_files') 36 | class TestsFromFiles(unittest.TestCase): 37 | def test_files(self, case_folder): 38 | inputFilePath = case_folder + inputCasePath 39 | proc = sp.Popen([interpreterPath, inputFilePath], stdout=sp.PIPE) 40 | (output, err) = proc.communicate() 41 | proc.wait() 42 | actualOutput = output.decode("utf-8").replace('\r', '') 43 | expectedOutputFile = open(case_folder + outputCasePath, 'r') 44 | expectedOutput = expectedOutputFile.read() 45 | expectedOutputFile.close() 46 | assert repr(actualOutput) == repr( 47 | expectedOutput), f"{repr(actualOutput)} != {repr(expectedOutput)}" 48 | 49 | 50 | @create_simple_cases('test') 51 | class SimpleTests(unittest.TestCase): 52 | def test(self, name, input, expectedOutput): 53 | f = tempfile.NamedTemporaryFile(delete=False) 54 | f.write(input.encode("utf-8")) 55 | f.close() 56 | inputFilePath = f.name 57 | proc = sp.Popen([interpreterPath, inputFilePath], stdout=sp.PIPE) 58 | (output, err) = proc.communicate() 59 | proc.wait() 60 | actualOutput = output.decode("utf-8").replace('\r', '') 61 | #print(f"[ {input} ] => {expectedOutput} ?") 62 | assert repr(actualOutput) == repr( 63 | expectedOutput), f"{repr(actualOutput)} != {repr(expectedOutput)}" 64 | os.unlink(f.name) 65 | 66 | 67 | if __name__ == "__main__": 68 | unittest.main() 69 | --------------------------------------------------------------------------------