├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.yml │ └── config.yml └── workflows │ ├── backup-to-gitlab.yml │ ├── call-clacheck.yml │ └── call-license-check.yml ├── .gitignore ├── .gitmodules ├── .reuse └── dep5 ├── LICENSE.txt ├── LICENSES └── BSD-2-Clause-Patent.txt ├── README.md ├── README.zh-CN.md ├── ReleaseNotes.zh-CN.md ├── build.sh ├── demo ├── about-conditions.txt ├── about-lists.txt ├── about-loops.txt ├── about-strings.txt ├── base-language │ ├── about-conditions.txt │ ├── about-lists.txt │ ├── about-loops.txt │ ├── about-strings.txt │ ├── qml.txt │ ├── qt.txt │ ├── sort.txt │ └── sort2.txt ├── diagnostics │ ├── error-bad-identifier.txt │ └── error-inner.txt ├── features │ ├── bindings.txt │ ├── classes.txt │ ├── combiners.txt │ ├── conditions.txt │ ├── continuations.txt │ ├── define.txt │ ├── encapsulations.txt │ ├── environments.txt │ ├── equals.txt │ ├── exceptions.txt │ ├── expressions.txt │ ├── functions.txt │ ├── ignore.txt │ ├── infix-transformation.txt │ ├── integer-divisions.txt │ ├── list-algorithms.txt │ ├── lists.txt │ ├── logical-operations.txt │ ├── object-primitives.txt │ ├── set.txt │ ├── stdout.txt │ ├── strings.txt │ └── variables.txt ├── hello-world.txt ├── hello.qml ├── high-level-language │ ├── aliases.txt │ ├── bindings.txt │ ├── if.txt │ ├── lambda.txt │ ├── qt.txt │ └── swap.txt ├── introduction │ ├── advanced │ │ ├── comments.txt │ │ ├── core-library.txt │ │ └── enivornment-ownership.txt │ └── base │ │ ├── assignment.txt │ │ ├── bindings-and-environments.txt │ │ ├── controls.txt │ │ ├── def-syntax.txt │ │ ├── equals.txt │ │ ├── functions.txt │ │ ├── hello-world2.txt │ │ ├── hello-world3.txt │ │ └── lists.txt ├── qml.txt ├── qt.py ├── qt.txt ├── sort.txt └── sort2.txt ├── detect-llvm.sh ├── doc ├── Features.zh-CN.md ├── Interpreter.zh-CN.md ├── Introduction.zh-CN.md └── Language.zh-CN.md ├── include ├── BasicReduction.h ├── Context.h ├── Evaluation.h ├── Exception.h ├── Forms.h ├── Interpreter.h ├── JIT.h ├── Lexical.h ├── Math.h ├── Parser.h ├── Syntax.h ├── TCO.h ├── TermAccess.h ├── TermNode.h ├── Unilang.h ├── UnilangFFI.h └── UnilangQt.h ├── init.txt ├── install-sbuild.sh ├── sbuild.sh ├── src ├── BasicReduction.cpp ├── Context.cpp ├── Evaluation.cpp ├── Exception.cpp ├── Forms.cpp ├── Interpreter.cpp ├── JIT.cpp ├── Lexical.cpp ├── Main.cpp ├── Math.cpp ├── Parser.cpp ├── TCO.cpp ├── TermAccess.cpp ├── TermNode.cpp ├── UnilangFFI.cpp └── UnilangQt.cpp ├── std.txt ├── test.sh ├── test.txt ├── test └── nested.txt └── valgrind.sh /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: BUG Report | 缺陷报告 3 | description: General BUG report 4 | labels: [] 5 | assignees: [] 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | **Thanks for taking a minute to file a bug report! 感谢抽出时间报告 BUG 缺陷** 11 | 12 | A valid bug report should let someone other than you be able to reproduce the same issue, so please provide as much details as possible. 13 | 一个有效的缺陷报告应能够使一个你之外的人可以根据所提供的信息有效的复现问题,所以请提供尽可能详细的信息。 14 | 15 | ⚠ Read before creating issue 反馈之前请注意: 16 | Only report BUG here, please use the [Discussion board][discussion-board] for non-bug feature request, troubleshooting or general discusion. 17 | 非 BUG 的反馈(如特性请求、疑难解答和常规讨论)请转到 [Discussion 面板][discussion-board]。 18 | 19 | [discussion-board]: https://github.com/linuxdeepin/unilang/discussions/new 20 | 21 | - type: textarea 22 | attributes: 23 | label: SUMMARY | 概要 24 | description: | 25 | describe the bug you want to report 26 | 在这里描述你遇到的缺陷 27 | validations: 28 | required: true 29 | 30 | - type: textarea 31 | attributes: 32 | label: STEPS TO REPRODUCE | 重现步骤 33 | description: | 34 | describe the steps to reproduce the specific bug 35 | 在这里描述重现缺陷的步骤 36 | placeholder: | 37 | 1. ... 38 | 2. ... 39 | 3. ... 40 | validations: 41 | required: true 42 | 43 | - type: textarea 44 | attributes: 45 | label: OBSERVED BEHAVIORS | 观察到的行为 46 | description: | 47 | describe the observed behaviors 48 | 在这里描述观察到的结果 49 | 50 | - type: textarea 51 | attributes: 52 | label: EXPECTED BEHAVIORS | 预期行为 53 | description: | 54 | describe the expected behaviors 55 | 在这里描述你所预期的行为 56 | 57 | - type: textarea 58 | attributes: 59 | label: ENVIRONMENT INFORMATION | 环境信息 60 | description: | 61 | describe the related information of software version and environment configurations 62 | 在这里描述相关的软件版本信息和环境配置 63 | placeholder: | 64 | Example: 65 | Distribution: deepin v20.7 66 | Version: V0.12 67 | Environment variables: ECHO=y 68 | validations: 69 | required: true 70 | 71 | - type: textarea 72 | attributes: 73 | label: ADDITIONAL INFORMATION | 附加信息 74 | description: | 75 | any additional information you may want to provide so we can offer better help 76 | 任何想要的附加信息可以补充到这里,以便我们能提供更好的帮助 77 | 78 | ... 79 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Feature Request | 特性请求 4 | url: https://github.com/linuxdeepin/unilang/discussions/new?category=features-request-ideas-%E7%89%B9%E6%80%A7%E8%AF%B7%E6%B1%82-%E5%A4%B4%E8%84%91%E9%A3%8E%E6%9A%B4 5 | about: Please create feature requests to the discussion board in our unilang repo. 6 | - name: General Discussion & Questions | 常规讨论与问答 7 | url: https://github.com/linuxdeepin/unilang/discussions/categories/q-a-%E9%97%AE%E7%AD%94%E6%9D%BF%E5%9D%97 8 | about: Please use the discussion board in our unilang repo. 9 | -------------------------------------------------------------------------------- /.github/workflows/backup-to-gitlab.yml: -------------------------------------------------------------------------------- 1 | name: backup to gitlab 2 | on: [push] 3 | 4 | concurrency: 5 | group: ${{ github.workflow }} 6 | cancel-in-progress: true 7 | 8 | jobs: 9 | backup-to-gitlabwh: 10 | uses: linuxdeepin/.github/.github/workflows/backup-to-gitlabwh.yml@master 11 | secrets: 12 | BRIDGETOKEN: ${{ secrets.BRIDGETOKEN }} 13 | 14 | backup-to-gitee: 15 | uses: linuxdeepin/.github/.github/workflows/backup-to-gitee.yml@master 16 | secrets: 17 | GITEE_SYNC_TOKEN: ${{ secrets.GITEE_SYNC_TOKEN }} 18 | -------------------------------------------------------------------------------- /.github/workflows/call-clacheck.yml: -------------------------------------------------------------------------------- 1 | name: Call CLA check 2 | on: 3 | issue_comment: 4 | types: [created] 5 | pull_request_target: 6 | types: [opened, closed, synchronize] 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-pull/${{ github.event.number }} 10 | cancel-in-progress: true 11 | 12 | jobs: 13 | clacheck: 14 | uses: linuxdeepin/.github/.github/workflows/cla-check.yml@master 15 | secrets: 16 | APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} 17 | -------------------------------------------------------------------------------- /.github/workflows/call-license-check.yml: -------------------------------------------------------------------------------- 1 | name: Call License and README Check 2 | on: 3 | pull_request_target: 4 | types: [opened, synchronize, reopened] 5 | 6 | permissions: 7 | pull-requests: write 8 | contents: read 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-pull/${{ github.event.number }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | license-check: 16 | uses: linuxdeepin/.github/.github/workflows/license-check.yml@master 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.d 3 | 3rdparty/.patched 4 | build/* 5 | unilang* 6 | 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "3rdparty/YSLib"] 2 | path = 3rdparty/YSLib 3 | url = https://github.com/FrankHB/YSLib.git 4 | -------------------------------------------------------------------------------- /.reuse/dep5: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: Unilang 3 | Upstream-Contact: FrankHB 4 | Source: https://github.com/linuxdeepin/unilang 5 | 6 | Files: * 7 | Copyright: 2020-2022 UnionTech Software Technology Co., Ltd. 8 | License: BSD-2-Clause-Patent 9 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020-2022 UnionTech Software Technology Co.,Ltd. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | Subject to the terms and conditions of this license, each copyright holder and contributor hereby grants to those receiving rights under this license a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except for failure to satisfy the conditions of this license) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer this software, where such license applies only to those patent claims, already acquired or hereafter acquired, licensable by such copyright holder or contributor that are necessarily infringed by: 10 | 11 | (a) their Contribution(s) (the licensed copyrights of copyright holders and non-copyrightable additions of contributors, in source or binary form) alone; or 12 | 13 | (b) combination of their Contribution(s) with the work of authorship to which such Contribution(s) was added by such copyright holder or contributor, if, at the time the Contribution is added, such addition causes such combination to be necessarily infringed. The patent license shall not apply to any other combinations which include the Contribution. 14 | 15 | Except as expressly stated above, no rights or licenses from any copyright holder or contributor is granted under this license, whether expressly, by implication, estoppel or otherwise. 16 | 17 | DISCLAIMER 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | -------------------------------------------------------------------------------- /LICENSES/BSD-2-Clause-Patent.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020-2022 UnionTech Software Technology Co.,Ltd. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | Subject to the terms and conditions of this license, each copyright holder and contributor hereby grants to those receiving rights under this license a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except for failure to satisfy the conditions of this license) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer this software, where such license applies only to those patent claims, already acquired or hereafter acquired, licensable by such copyright holder or contributor that are necessarily infringed by: 10 | 11 | (a) their Contribution(s) (the licensed copyrights of copyright holders and non-copyrightable additions of contributors, in source or binary form) alone; or 12 | 13 | (b) combination of their Contribution(s) with the work of authorship to which such Contribution(s) was added by such copyright holder or contributor, if, at the time the Contribution is added, such addition causes such combination to be necessarily infringed. The patent license shall not apply to any other combinations which include the Contribution. 14 | 15 | Except as expressly stated above, no rights or licenses from any copyright holder or contributor is granted under this license, whether expressly, by implication, estoppel or otherwise. 16 | 17 | DISCLAIMER 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # SPDX-FileCopyrightText: 2020-2023 UnionTech Software Technology Co.,Ltd. 3 | 4 | set -e 5 | Unilang_BaseDir="$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd)" 6 | YSLib_BaseDir="$Unilang_BaseDir/3rdparty/YSLib" 7 | : "${Unilang_Output=unilang}" 8 | : "${Unilang_BuildDir="$(dirname "$Unilang_Output")"}" 9 | 10 | echo 'Configuring ...' 11 | 12 | echo "Output path is \"$Unilang_Output\"." 13 | echo "Build directory is \"$Unilang_BuildDir\"." 14 | 15 | : "${CXX:=g++}" 16 | if [[ "$Unilang_BuildDir" =~ .*debug.* ]]; then 17 | echo 'Debug mode turned on by configuration.' 18 | : "${CXXFLAGS=-std=c++11 -Wall -Wextra -g}" 19 | else 20 | : "${CXXFLAGS=-std=c++11 -Wall -Wextra -O3 -DNDEBUG}" 21 | fi 22 | 23 | . "$Unilang_BaseDir/detect-llvm.sh" 24 | 25 | CXXFLAGS_Qt="$(pkg-config --cflags Qt5Widgets Qt5Quick)" 26 | LIBS_Qt="$(pkg-config --libs Qt5Widgets Qt5Quick)" 27 | # NOTE: These are known redundant or false positives. 28 | if echo "$CXX" --version | grep -q clang; then 29 | CXX_Name_=Clang++ 30 | CXXFLAGS_WKRD_='-Wno-ignored-attributes -Wno-mismatched-tags' 31 | else 32 | CXX_Name_=G++ 33 | CXXFLAGS_WKRD_=\ 34 | '-Wno-dangling-reference -Wno-ignored-attributes -Wno-uninitialized' 35 | fi 36 | 37 | INCLUDES=(-Iinclude "-I$YSLib_BaseDir/YBase/include" \ 38 | "-I$YSLib_BaseDir/YFramework/include") 39 | 40 | SRCS=("$YSLib_BaseDir/YBase/source/ystdex/any.cpp" \ 41 | "$YSLib_BaseDir/YBase/source/ystdex/cassert.cpp" \ 42 | "$YSLib_BaseDir/YBase/source/ystdex/concurrency.cpp" \ 43 | "$YSLib_BaseDir/YBase/source/ystdex/cstdio.cpp" \ 44 | "$YSLib_BaseDir/YBase/source/ystdex/exception.cpp" \ 45 | "$YSLib_BaseDir/YBase/source/ystdex/memory_resource.cpp" \ 46 | "$YSLib_BaseDir/YBase/source/ystdex/node_base.cpp" \ 47 | "$YSLib_BaseDir/YBase/source/ystdex/tree.cpp" \ 48 | "$YSLib_BaseDir/YBase/source/ystdex/hash_table.cpp" \ 49 | "$YSLib_BaseDir/YFramework/source/CHRLib/CharacterProcessing.cpp" \ 50 | "$YSLib_BaseDir/YFramework/source/CHRLib/MappingEx.cpp" \ 51 | "$YSLib_BaseDir/YFramework/source/CHRLib/chrmap.cpp" \ 52 | "$YSLib_BaseDir/YFramework/source/YCLib/Debug.cpp" \ 53 | "$YSLib_BaseDir/YFramework/source/YCLib/FileIO.cpp" \ 54 | "$YSLib_BaseDir/YFramework/source/YCLib/FileSystem.cpp" \ 55 | "$YSLib_BaseDir/YFramework/source/YCLib/MemoryMapping.cpp" \ 56 | "$YSLib_BaseDir/YFramework/source/YCLib/NativeAPI.cpp" \ 57 | "$YSLib_BaseDir/YFramework/source/YCLib/YCommon.cpp" \ 58 | "$YSLib_BaseDir/YFramework/source/YSLib/Core/YCoreUtilities.cpp" \ 59 | "$YSLib_BaseDir/YFramework/source/YSLib/Core/YException.cpp" \ 60 | "$YSLib_BaseDir/YFramework/source/YSLib/Core/YObject.cpp" \ 61 | "$YSLib_BaseDir/YFramework/source/YSLib/Service/FileSystem.cpp" \ 62 | "$YSLib_BaseDir/YFramework/source/YSLib/Service/File.cpp" \ 63 | "$YSLib_BaseDir/YFramework/source/YSLib/Service/TextFile.cpp") 64 | 65 | case $(uname) in 66 | *MSYS* | *MINGW*) 67 | INCLUDES=("${INCLUDES[@]}" "-I$YSLib_BaseDir/YFramework/Win32/include") 68 | SRCS=("${SRCS[@]}" "$YSLib_BaseDir/YFramework/source/YCLib/Host.cpp" \ 69 | "$YSLib_BaseDir/YFramework/Win32/source/YCLib/MinGW32.cpp" \ 70 | "$YSLib_BaseDir/YFramework/Win32/source/YCLib/NLS.cpp" \ 71 | "$YSLib_BaseDir/YFramework/Win32/source/YCLib/Registry.cpp" \ 72 | "$YSLib_BaseDir/YFramework/Win32/source/YCLib/Consoles.cpp" 73 | "$YSLib_BaseDir/YFramework/source/YSLib/Core/YConsole.cpp") 74 | if test "$CXX_Name_" = G++; then 75 | CXXFLAGS_EXTRA="$CXXFLAGS_EXTRA -mthreads" 76 | else 77 | CXXFLAGS_EXTRA="$CXXFLAGS_EXTRA -D_MT" 78 | fi 79 | LIB_EXTRA="$LIB_EXTRA -mthreads" 80 | ;; 81 | *) 82 | CXXFLAGS_EXTRA="$CXXFLAGS_EXTRA -fPIC -pthread" 83 | LIB_EXTRA="$LIB_EXTRA -pthread -ldl" 84 | esac 85 | 86 | SRCS=("$Unilang_BaseDir/src"/*.cpp "${SRCS[@]}") 87 | 88 | echo 'Configuring Done.' 89 | 90 | echo 'Building ...' 91 | 92 | mkdir -p "$Unilang_BuildDir" 93 | mkdir -p "$(dirname "$Unilang_Output")" 94 | 95 | Call_() 96 | { 97 | # XXX: %Verbose is external. 98 | # shellcheck disable=2154 99 | if test -n "$Verbose"; then 100 | # shellcheck disable=2086 101 | echo "${@@Q}" 102 | fi 103 | "$@" 104 | } 105 | 106 | # XXX: Assume GNU parallel, not parallel from moreutils, if '--version' is 107 | # supported. 108 | # XXX: %NoParallel is external. 109 | # shellcheck disable=2154 110 | if test -z "$NoParallel" && hash parallel 2> /dev/null && hash grep 2> \ 111 | /dev/null && parallel --will-cite --version > /dev/null 2> /dev/null; \ 112 | then 113 | echo 'Using parallel.' 114 | if test -n "$Verbose"; then 115 | Verbose_t_=-t 116 | fi 117 | 118 | # XXX: Value of several variables may contain whitespaces. 119 | # shellcheck disable=2086 120 | parallel --will-cite -q $Verbose_t_ "$CXX" -c {} \ 121 | -o"$Unilang_BuildDir/{#}.o" $CXXFLAGS $CXXFLAGS_EXTRA $CXXFLAGS_WKRD_ \ 122 | $CXXFLAGS_Qt "${INCLUDES[@]}" ::: "${SRCS[@]}" 123 | 124 | OFILES_=("$Unilang_BuildDir"/*.o) 125 | 126 | # shellcheck disable=2086 127 | Call_ "$CXX" -o"$Unilang_Output" "${OFILES_[@]}" $LDFLAGS $LIBS $LIBS_Qt \ 128 | $LIBS_EXTRA 129 | else 130 | echo 'Not using parallel.' 131 | 132 | # shellcheck disable=2086 133 | Call_ "$CXX" -o"$Unilang_Output" $CXXFLAGS $CXXFLAGS_EXTRA $CXXFLAGS_WKRD_ \ 134 | $CXXFLAGS_Qt $LDFLAGS $LIBS_Qt "${INCLUDES[@]}" "${SRCS[@]}" $LIBS_EXTRA 135 | fi 136 | 137 | echo 'Done.' 138 | 139 | -------------------------------------------------------------------------------- /demo/about-conditions.txt: -------------------------------------------------------------------------------- 1 | "About conditions."; 2 | 3 | info "if expressions"; 4 | expect "T" if #t "T"; 5 | expect "T" if "" "T"; 6 | expect (list "a" "b") if #t (list "a" "b"); 7 | expect #inert if #f "T"; 8 | expect "T" if #t "T" else "F"; 9 | expect "T" if "" "T" else {"F"}; 10 | expect "F" if #f "T" else "F"; 11 | 12 | info "when: one-arm 'if' with implicit sequence evaluation"; 13 | expect "T" when #t "T"; 14 | expect "b" when #t list "a" "b"; 15 | expect (list "a" "b") when #t (list "a" "b"); 16 | expect "T" when "" "T"; 17 | expect #inert when #f "T"; 18 | 19 | info "unless: one arm negated 'if' with implict sequence evaluation"; 20 | expect #inert unless #t "T"; 21 | expect #inert unless "" "T"; 22 | expect "T" unless #f "T"; 23 | expect "b" unless #f list "a" "b"; 24 | expect (list "a" "b") unless #f (list "a" "b"); 25 | 26 | -------------------------------------------------------------------------------- /demo/about-lists.txt: -------------------------------------------------------------------------------- 1 | "About lists."; 2 | 3 | def a "a"; 4 | def b "b"; 5 | def c "c"; 6 | def l list a b c; 7 | 8 | expect (list "a" "b" "c") l; 9 | 10 | info "empty test"; 11 | expect #t null? (); 12 | expect #f null? l; 13 | 14 | info "list element extraction"; 15 | expect "a" first l; 16 | expect (list "b" "c") rest l; 17 | 18 | info "list concatenation"; 19 | expect (list "a" "b" "c" "d") list-concat l (list "d"); 20 | expect (list "a" "b" "c" "d" "e") append l (list "d") (list "e"); 21 | 22 | -------------------------------------------------------------------------------- /demo/about-loops.txt: -------------------------------------------------------------------------------- 1 | "About loops."; 2 | 3 | def l list "a" "b" "c" "d"; 4 | 5 | info "while loops: test a list element in a list"; 6 | defn list-contains? (l x) 7 | { 8 | def not-found #t; 9 | while (and not-found (not (null? l))) 10 | { 11 | if (eqv? (first l) x) 12 | { 13 | def not-found #f; 14 | } 15 | else 16 | { 17 | def l rest l; 18 | } 19 | }; 20 | not not-found 21 | }; 22 | $check list-contains? l "b"; 23 | $check-not list-contains? l "e"; 24 | 25 | -------------------------------------------------------------------------------- /demo/about-strings.txt: -------------------------------------------------------------------------------- 1 | "About strings."; 2 | import std.strings 3 | ++ string-empty? string=? string<- string-split string-contains?; 4 | 5 | def (a b c) list "a" "b" "c"; 6 | 7 | info "object comparison"; 8 | expect #t eqv? a a; 9 | expect #t eqv? a "a"; 10 | expect #f eqv? a "b"; 11 | 12 | info "string concatenation"; 13 | expect "abc" ++ "a" b c; 14 | expect "abc" ++ a "b" c; 15 | expect "abc" ++ a b "c"; 16 | 17 | info "string empty test"; 18 | expect #t string-empty? ""; 19 | expect #f string-empty? "a"; 20 | expect #f string-empty? a; 21 | 22 | info "string equality: similar to eqv?," 23 | " but enforcing the type of the operands as strings"; 24 | expect #t string=? a a; 25 | expect #t string=? a "a"; 26 | expect #f string=? a "b"; 27 | 28 | info 29 | "typed assignment: requires the 1st operand as a modifiable string lvalue"; 30 | string<- a "b"; 31 | expect #t string=? a "b"; 32 | 33 | info "string split"; 34 | expect (list "1" "2" "3") string-split "1,2,3" ","; 35 | 36 | info "string element tests"; 37 | expect #t string-contains? "123" "1"; 38 | expect #t string-contains? "123" "2"; 39 | expect #t string-contains? "123" "3"; 40 | expect #f string-contains? "123" "4"; 41 | 42 | "TODO", 43 | "type predicate", 44 | "more element access", 45 | "character extraction", 46 | "trimming", 47 | "lexicographical compare", 48 | "..."; 49 | 50 | -------------------------------------------------------------------------------- /demo/base-language/about-conditions.txt: -------------------------------------------------------------------------------- 1 | "About conditions."; 2 | 3 | info "if expressions"; 4 | $expect "T" $if #t "T"; 5 | $expect "T" $if "" "T"; 6 | $expect (list "a" "b") $if #t (list "a" "b"); 7 | $expect #inert $if #f "T"; 8 | $expect "T" $if #t "T" "F"; 9 | $expect "T" $if "" "T" "F"; 10 | $expect "F" $if #f "T" "F"; 11 | 12 | info "when: one-arm 'if' with implicit sequence evaluation"; 13 | $expect "T" $when #t "T"; 14 | $expect "b" $when #t list "a" "b"; 15 | $expect (list "a" "b") $when #t (list "a" "b"); 16 | $expect "T" $when "" "T"; 17 | $expect #inert $when #f "T"; 18 | 19 | info "unless: one arm negated 'if' with implict sequence evaluation"; 20 | $expect #inert $unless #t "T"; 21 | $expect #inert $unless "" "T"; 22 | $expect "T" $unless #f "T"; 23 | $expect "b" $unless #f list "a" "b"; 24 | $expect (list "a" "b") $unless #f (list "a" "b"); 25 | 26 | -------------------------------------------------------------------------------- /demo/base-language/about-lists.txt: -------------------------------------------------------------------------------- 1 | "About lists."; 2 | 3 | $def! a "a"; 4 | $def! b "b"; 5 | $def! c "c"; 6 | $def! l list a b c; 7 | 8 | $expect (list "a" "b" "c") l; 9 | 10 | info "empty test"; 11 | $check null? (); 12 | $check-not null? l; 13 | 14 | info "list element extraction"; 15 | $expect "a" first l; 16 | $expect (list "b" "c") rest l; 17 | 18 | info "list concatenation"; 19 | $expect (list "a" "b" "c" "d") list-concat l (list "d"); 20 | $expect (list "a" "b" "c" "d" "e") append l (list "d") (list "e"); 21 | 22 | -------------------------------------------------------------------------------- /demo/base-language/about-loops.txt: -------------------------------------------------------------------------------- 1 | "About loops."; 2 | 3 | $def! l list "a" "b" "c" "d"; 4 | 5 | info "while loops: test a list element in a list"; 6 | $defl! list-contains? (l x) 7 | ( 8 | $def! not-found #t; 9 | $while ($and not-found (not? (null? l))) 10 | ( 11 | $if (eqv? (first l) x) 12 | ( 13 | $def! not-found #f; 14 | ) 15 | ( 16 | $def! l rest l; 17 | ) 18 | ); 19 | not? not-found 20 | ); 21 | $check list-contains? l "b"; 22 | $check-not list-contains? l "e"; 23 | 24 | -------------------------------------------------------------------------------- /demo/base-language/about-strings.txt: -------------------------------------------------------------------------------- 1 | "About strings."; 2 | $import! std.strings 3 | ++ string-empty? string=? string<- string-split string-contains?; 4 | 5 | $def! (a b c) list "a" "b" "c"; 6 | 7 | info "object comparison"; 8 | $check eqv? a a; 9 | $check eqv? a "a"; 10 | $check-not eqv? a "b"; 11 | 12 | info "string concatenation"; 13 | $expect "abc" ++ "a" b c; 14 | $expect "abc" ++ a "b" c; 15 | $expect "abc" ++ a b "c"; 16 | 17 | info "string empty test"; 18 | $check string-empty? ""; 19 | $check-not string-empty? "a"; 20 | $check-not string-empty? a; 21 | 22 | info "string equality: similar to eqv?," 23 | " but enforcing the type of the operands as strings"; 24 | $check string=? a a; 25 | $check string=? a "a"; 26 | $check-not string=? a "b"; 27 | 28 | info 29 | "typed assignment: requires the 1st operand as a modifiable string lvalue"; 30 | string<- a "b"; 31 | $check string=? a "b"; 32 | 33 | info "string split"; 34 | $expect (list "1" "2" "3") string-split "1,2,3" ","; 35 | 36 | info "string element tests"; 37 | $check string-contains? "123" "1"; 38 | $check string-contains? "123" "2"; 39 | $check string-contains? "123" "3"; 40 | $check-not string-contains? "123" "4"; 41 | 42 | "TODO", 43 | "type predicate", 44 | "more element access", 45 | "character extraction", 46 | "trimming", 47 | "lexicographical compare", 48 | "..."; 49 | 50 | -------------------------------------------------------------------------------- /demo/base-language/qml.txt: -------------------------------------------------------------------------------- 1 | $import! UnilangQt QCoreApplication-setAttribute Qt.AA_EnableHighDpiScaling 2 | make-QApplication make-QQuickView QQuickView_set-source 3 | QQuickView_set-transparent QQuickView-show QApplication-exec; 4 | 5 | () newline; 6 | 7 | QCoreApplication-setAttribute Qt.AA_EnableHighDpiScaling; 8 | $def! app () make-QApplication; 9 | $def! window () make-QQuickView; 10 | QQuickView_set-transparent window; 11 | QQuickView_set-source window "demo/hello.qml"; 12 | QQuickView-show window; 13 | () QApplication-exec; 14 | -------------------------------------------------------------------------------- /demo/base-language/qt.txt: -------------------------------------------------------------------------------- 1 | $import! std.classes make-class make-object $access; 2 | $import! UnilangQt QWidget make-QPushButton make-QLabel Qt.AlignCenter 3 | make-QVBoxLayout QLayout-addWidget QObject-connect QLabel-setText 4 | make-QApplication QApplication-exec; 5 | 6 | () newline; 7 | 8 | $defl! log (x) (display x; () newline); 9 | $defl! logd (x) (log "DEBUG:"; log x); 10 | 11 | logd "Loading script ..."; 12 | 13 | $def! global () get-current-environment; 14 | $def! MyWidget make-class QWidget ($lambda (self) 15 | ( 16 | logd "[ctor] MyWidget"; 17 | $set! self hello list "Hallo Welt" "Hei maailma" "Hola Mundo" "Привет мир"; 18 | $set! self button make-QPushButton "Click me!"; 19 | $set! self text make-QLabel "Hello World!" Qt.AlignCenter; 20 | $set! self layout () make-QVBoxLayout; 21 | $import! self text button layout; 22 | QLayout-addWidget layout text; 23 | QLayout-addWidget layout button; 24 | ($remote-eval setLayout self) layout; 25 | QObject-connect button "clicked(bool)" ($remote-eval _dynamic self) 26 | ($lambda/e (make-environment self global) () ( 27 | logd "[slot] Callback called."; 28 | QLabel-setText text (random.choice hello) 29 | )) 30 | )); 31 | 32 | $def! app () make-QApplication; 33 | $def! wgt make-object MyWidget; 34 | ($access wgt resize) 800 600; 35 | () ($access wgt show); 36 | sys.exit (() QApplication-exec); 37 | 38 | -------------------------------------------------------------------------------- /demo/base-language/sort.txt: -------------------------------------------------------------------------------- 1 | "QuickSort variant, leftmost pivot, non-in-place."; 2 | 3 | $defl! log (&title &r) 4 | ( 5 | display title; 6 | display r; 7 | () newline; 8 | forward! r 9 | ); 10 | 11 | $defl! sort (&lst) 12 | ( 13 | $import! std.math =?; 14 | $if ($or (null? lst) (null? (rest& lst))) (forward! lst) 15 | ( 16 | log "partial result: " 17 | ( 18 | $let (((pivot .rlst) expire lst)) 19 | ( 20 | log "selected pivot: " pivot; 21 | append 22 | (log "sorted left: " 23 | (sort (log "filtered less than pivot: " 24 | (filter ($lambda (x) =? x pivot) rlst)))) 29 | ) 30 | ) 31 | ) 32 | ); 33 | 34 | $def! data (list 2 3 13 11 5 1 14 6 16 8 10 9 15 7 12 4); 35 | log "original: " data; 36 | log "sorted: " (sort data); 37 | () newline; 38 | 39 | -------------------------------------------------------------------------------- /demo/base-language/sort2.txt: -------------------------------------------------------------------------------- 1 | "QuickSort variant, leftmost pivot, in-place."; 2 | 3 | $import! std.math <=? >? ? (- hi lo) 1) 54 | ( 55 | $let ((m partition! lst (+ lo 1) hi (list-at& lst lo))) 56 | ( 57 | swap! (list-at& lst lo) (list-at& lst (- m 1)); 58 | log "partitioned: " lst; 59 | sort-range! lst lo (- m 1); 60 | log "left sorted: " lst; 61 | sort-range! lst m hi; 62 | log "right sorted: " lst; 63 | ) 64 | ); 65 | #inert 66 | ); 67 | 68 | $defl! sort! (&lst) sort-range! lst 0 (list-length lst); 69 | 70 | $def! data (list 2 3 13 11 5 1 14 6 16 8 10 9 15 7 12 4); 71 | log "original: " data; 72 | log "sorted: " (sort! data; data); 73 | () newline; 74 | 75 | -------------------------------------------------------------------------------- /demo/diagnostics/error-bad-identifier.txt: -------------------------------------------------------------------------------- 1 | "Error: bad identifier."; 2 | 3 | foo; 4 | 5 | -------------------------------------------------------------------------------- /demo/diagnostics/error-inner.txt: -------------------------------------------------------------------------------- 1 | "Error: inner calls."; 2 | 3 | $defl! foo (x) id (x x); 4 | 5 | foo 42; 6 | 7 | -------------------------------------------------------------------------------- /demo/features/bindings.txt: -------------------------------------------------------------------------------- 1 | "Binding demos."; 2 | 3 | $import! std.strings ++; 4 | $expect "ab" $let ((x "a") (y "b")) ++ x y; 5 | 6 | $def! new-env $bindings->environment (x "x") (y "y"); 7 | 8 | $def! my-module $provide! (exported-symbol-x exported-symbol-y) 9 | ( 10 | $import! std.strings ++; 11 | $def! internal-symbol "FOO"; 12 | $def! exported-symbol-x internal-symbol; 13 | $def! exported-symbol-y ++ internal-symbol "BAR"; 14 | ); 15 | 16 | $import! my-module exported-symbol-x; 17 | 18 | $expect "FOO" exported-symbol-x; 19 | $expect "FOOBAR" exported-symbol-y; 20 | 21 | -------------------------------------------------------------------------------- /demo/features/classes.txt: -------------------------------------------------------------------------------- 1 | "Classes demos."; 2 | 3 | $import! std.classes class? make-class object? make-object $access; 4 | 5 | $def! Shape make-class () id; 6 | 7 | $def! Rectangle make-class Shape ($lambda (self x y) 8 | ( 9 | $def! mods make-environment self std.math; 10 | $set! self X x; 11 | $set! self Y y; 12 | $set! self Area $lambda/e mods () (* X Y); 13 | )); 14 | 15 | $def! Circle make-class Shape ($lambda (self r) 16 | ( 17 | $def! mods make-environment self std.math; 18 | $set! self R r; 19 | $set! self Area $lambda/e mods () (* (* R R) 3.14159); 20 | )); 21 | 22 | $def! rect make-object Rectangle 4 2; 23 | 24 | display "rect.X = "; 25 | display ($access rect X); 26 | () newline; 27 | 28 | display "rect.Y = "; 29 | display ($access rect Y); 30 | () newline; 31 | 32 | display "rect.Area() = "; 33 | display ($access rect (() Area)); 34 | () newline; 35 | 36 | $def! circle make-object Circle 3; 37 | 38 | display "circle.R = "; 39 | display ($access circle R); 40 | () newline; 41 | 42 | display "circle.Area() = "; 43 | display ($access circle (() Area)); 44 | () newline; 45 | 46 | -------------------------------------------------------------------------------- /demo/features/combiners.txt: -------------------------------------------------------------------------------- 1 | "Combiner operation and definition demos."; 2 | 3 | $def! $f $vau/e (() get-current-environment) (body) d eval body d; 4 | $def! $f2 $vau (body) d eval body d; 5 | 6 | $def! id $lambda (x) x; 7 | 8 | $def! $lambda $vau (formals .body) d wrap 9 | (eval (cons $vau (cons formals (cons ignore body))) d); 10 | 11 | $defl! id (x) x; 12 | 13 | $def! id $lambda (x) x; 14 | 15 | 16 | -------------------------------------------------------------------------------- /demo/features/conditions.txt: -------------------------------------------------------------------------------- 1 | "Conditional operative demos."; 2 | 3 | $def! x (); 4 | display "x is "; 5 | display ($if (null? x) "empty" "not empty"); 6 | () newline; 7 | 8 | $defl! cond-test (x) $cond 9 | ((eqv? x "a") "x is a") 10 | ((eqv? x "b") "x is b") 11 | ((eqv? x "c") "x is c") 12 | (#t "unknown"); 13 | 14 | $expect "x is a" cond-test "a"; 15 | $expect "x is b" cond-test "b"; 16 | $expect "x is c" cond-test "c"; 17 | $expect "unknown" cond-test "d"; 18 | 19 | $when (eqv? x "a") (display "x") (display " is a"); 20 | 21 | $unless (eqv? x "a") (display "x") (display " is not a"); 22 | 23 | -------------------------------------------------------------------------------- /demo/features/continuations.txt: -------------------------------------------------------------------------------- 1 | "Continuations demos."; 2 | 3 | $import! std.continuations call/1cc continuation->applicative; 4 | 5 | $expect 42 call/1cc ($lambda . 42); 6 | 7 | $expect 43 $let ((a 43)) call/1cc ($lambda . a); 8 | 9 | $expect (list 44) call/1cc 10 | ($lambda (&k) ((continuation->applicative (move! k)) 44; 11 | raise-error "Failed to skip.")); 12 | 13 | -------------------------------------------------------------------------------- /demo/features/define.txt: -------------------------------------------------------------------------------- 1 | "Variable definition demos."; 2 | 3 | $def! x "FOO"; 4 | $def! ((x #ignore .y) z) list (list "X" "NOT USED" "Y1" "Y2") "Z"; 5 | 6 | -------------------------------------------------------------------------------- /demo/features/encapsulations.txt: -------------------------------------------------------------------------------- 1 | "Encapsulations demos."; 2 | 3 | $def! (e p? d) () make-encapsulation-type; 4 | $def! x e (list ()); 5 | $check p? x; 6 | $check not? (p? (list ())); 7 | $expect (list ()) d x; 8 | 9 | -------------------------------------------------------------------------------- /demo/features/environments.txt: -------------------------------------------------------------------------------- 1 | "Environments demos."; 2 | 3 | "Environments can be referred like the following statements."; 4 | 5 | $def! current () get-current-environment; 6 | $def! new () make-environment; 7 | $def! locked lock-environment current; 8 | $def! locked-current () lock-current-environment; 9 | $def! base-env () make-standard-environment; 10 | 11 | "Evaluation uses environments."; 12 | 13 | $expect current eval ($quote current) current; 14 | $expect new eval ($quote new) current; 15 | 16 | "The following evaluations are equivalent."; 17 | eval (list display "Hello, world") (() get-current-environment); 18 | display "Hello, world"; 19 | 20 | "However, the ground environment is not available directly in user programs."; 21 | 22 | -------------------------------------------------------------------------------- /demo/features/equals.txt: -------------------------------------------------------------------------------- 1 | "Equality predicates demos."; 2 | 3 | $def! x "FOO"; 4 | $def! y "FOO"; 5 | 6 | $check-not eq? x y; 7 | $check eqv? x y; 8 | 9 | -------------------------------------------------------------------------------- /demo/features/exceptions.txt: -------------------------------------------------------------------------------- 1 | "Exceptions demos."; 2 | 3 | $expect 21 try/catch (17 + 4) idv; 4 | $expect 10 try/catch (17 + (throw (2 + 3))) (lambda (x) (x * 2)); 5 | $expect 30 6 | try/catch (try/catch (17 + (throw (2 + 4))) (lambda (x) (x * 5))) idv; 7 | $expect 30 8 | try/catch (try/catch (17 + (throw (2 + 4))) (lambda (x) throw (x * 5))) idv; 9 | 10 | -------------------------------------------------------------------------------- /demo/features/expressions.txt: -------------------------------------------------------------------------------- 1 | "Expression demos."; 2 | 3 | "Each expression in a statement shall be well-formed with appropriate bindings."; 4 | 5 | (); 6 | 7 | "hello"; 8 | 9 | (); 10 | 11 | $let ((x idv) (y 1)) 12 | (x y); 13 | 14 | -------------------------------------------------------------------------------- /demo/features/functions.txt: -------------------------------------------------------------------------------- 1 | "Function and calls demos."; 2 | 3 | $def! f $lambda (x) x; 4 | 5 | "The following expressions have the 1st subexpression as a function."; 6 | 7 | f 1; 8 | (id f) 1; 9 | 10 | "After the following definitions, expressions above are no longer functions"; 11 | $def! f 42; 12 | $def! id $lambda (x) 42; 13 | 14 | $def! x "x"; 15 | "The expressions in the following statements are simple calls."; 16 | $quote x; 17 | id x; 18 | 19 | -------------------------------------------------------------------------------- /demo/features/ignore.txt: -------------------------------------------------------------------------------- 1 | "The ignore object demo."; 2 | 3 | $def! $lambda $vau (formals .body) d wrap 4 | (eval (cons $vau (cons formals (cons ignore body))) d); 5 | 6 | ($lambda (x) x) 42; 7 | 8 | -------------------------------------------------------------------------------- /demo/features/infix-transformation.txt: -------------------------------------------------------------------------------- 1 | "Infix transformation demos."; 2 | 3 | $let ((A idv) (B 42)) 4 | ( 5 | "Each group of the following expressions in the are equivalent."; 6 | $expect 42 (A; B); 7 | $expect 42 ($sequence A B); 8 | 9 | $expect (list A B) (A, B); 10 | $expect (list A B) list% A B 11 | ); 12 | 13 | -------------------------------------------------------------------------------- /demo/features/integer-divisions.txt: -------------------------------------------------------------------------------- 1 | "Integer division demos."; 2 | 3 | $import! std.math floor/ floor-quotient floor-remainder truncate/ 4 | truncate-quotient truncate-remainder; 5 | 6 | $expect (list 2 1) floor/ 5 2; 7 | $expect 2 floor-quotient 5 2; 8 | $expect 1 floor-remainder 5 2; 9 | $expect (list -3 1) floor/ -5 2; 10 | $expect -3 floor-quotient -5 2; 11 | $expect 1 floor-remainder -5 2; 12 | $expect (list -3 -1) floor/ 5 -2; 13 | $expect -3 floor-quotient 5 -2; 14 | $expect -1 floor-remainder 5 -2; 15 | $expect (list 2 -1) floor/ -5 -2; 16 | $expect 2 floor-quotient -5 -2; 17 | $expect -1 floor-remainder -5 -2; 18 | $expect (list 2 1) truncate/ 5 2; 19 | $expect 2 truncate-quotient 5 2; 20 | $expect 1 truncate-remainder 5 2; 21 | $expect (list -2 -1) truncate/ -5 2; 22 | $expect -2 truncate-quotient -5 2; 23 | $expect -1 truncate-remainder -5 2; 24 | $expect (list -2 1) truncate/ 5 -2; 25 | $expect -2 truncate-quotient 5 -2; 26 | $expect 1 truncate-remainder 5 -2; 27 | $expect (list 2 -1) truncate/ -5 -2; 28 | $expect 2 truncate-quotient -5 -2; 29 | $expect -1 truncate-remainder -5 -2; 30 | $expect (list 2.0 -1.0) truncate/ -5.0 -2; 31 | $expect 2.0 truncate-quotient -5.0 -2; 32 | $expect -1.0 truncate-remainder -5.0 -2; 33 | 34 | -------------------------------------------------------------------------------- /demo/features/list-algorithms.txt: -------------------------------------------------------------------------------- 1 | "List algorithm demoes."; 2 | 3 | $import! std.strings ++; 4 | 5 | $expect (list "as" "bs" "cs") map1 ($lambda (x) ++ x "s") (list "a" "b" "c"); 6 | 7 | -------------------------------------------------------------------------------- /demo/features/lists.txt: -------------------------------------------------------------------------------- 1 | "List demos."; 2 | 3 | "List construction."; 4 | 5 | $expect (list "x") cons "x" (); 6 | $expect (list "y" "a" "b") cons "y" (list "a" "b"); 7 | $expect (list "0" "1" "2" "3" "4") list* "0" "1" "2" (list "3" "4"); 8 | 9 | "List type predicate."; 10 | $check not? (list? ""); 11 | $check list? (); 12 | 13 | "List accesses."; 14 | 15 | $def! li list "a" "b" "c"; 16 | $expect "a" first li; 17 | $expect (list "b" "c") rest% li; 18 | 19 | "Apply."; 20 | 21 | $def! e () get-current-environment; 22 | $def! foo list; 23 | 24 | apply foo (list "x" "y") e; 25 | eval (list () foo "x" "y") e; 26 | 27 | -------------------------------------------------------------------------------- /demo/features/logical-operations.txt: -------------------------------------------------------------------------------- 1 | "Logical operation demos."; 2 | 3 | $check-not (not? #t); 4 | $check-not (not? "x"); 5 | $check (not? #f); 6 | $expect "z" $and (eqv? "x" "x") () "z"; 7 | $check-not $or #f (eqv? "x" "y"); 8 | 9 | $and (eqv? "x" "y") (display "unexpected evaluated again\n"; eqv? "x" "y"); 10 | and (eqv? "x" "y") (display "expected evaluated again\n"; eqv? "x" "y"); 11 | $or (eqv? "x" "x") (display "unexpected evaluated again\n"; eqv? "x" "x"); 12 | or (eqv? "x" "x") (display "expected evaluated again\n"; eqv? "x" "x"); 13 | 14 | -------------------------------------------------------------------------------- /demo/features/object-primitives.txt: -------------------------------------------------------------------------------- 1 | "Object primitives demos."; 2 | 3 | $def! x ""; 4 | $check reference? x; 5 | $check unique? (expire x); 6 | $check not? (modifiable? (as-const x)); 7 | 8 | -------------------------------------------------------------------------------- /demo/features/set.txt: -------------------------------------------------------------------------------- 1 | "Variable setting demos."; 2 | 3 | $set! (() get-current-environment) x "FOO"; 4 | $expect "FOO" x; 5 | 6 | $def! x "FOO"; 7 | $expect "FOO" x; 8 | 9 | -------------------------------------------------------------------------------- /demo/features/stdout.txt: -------------------------------------------------------------------------------- 1 | "Standard output demos"; 2 | 3 | display "FOO"; 4 | () newline; 5 | 6 | -------------------------------------------------------------------------------- /demo/features/strings.txt: -------------------------------------------------------------------------------- 1 | "Strings demos."; 2 | 3 | $import! std.strings string? ++ string-empty? string<- string=? string-split 4 | string-contains? string-contains-ci? regex-match? string->regex 5 | regex-replace; 6 | 7 | "String type predicate."; 8 | $check not? (string? ()); 9 | $check string? ""; 10 | $check string? "x"; 11 | 12 | "String concatenation."; 13 | $expect "xyz" ++ "x" "y" "z"; 14 | 15 | $def! x "x"; 16 | 17 | "String access."; 18 | $check not? (string-empty? x); 19 | 20 | "String assignment."; 21 | string<- x "y"; 22 | 23 | "String comparison."; 24 | $check (string=? x "y"); 25 | 26 | "String split."; 27 | $expect (list "x" "y" "z") string-split "x y z" " "; 28 | 29 | "Substring test."; 30 | $check string-contains? "x y z" "y z"; 31 | $check string-contains-ci? "x y z" "Y Z"; 32 | 33 | "String conversion."; 34 | $check eqv? (string->symbol "x") ($quote x); 35 | $check string=? (symbol->string (string->symbol "x")); 36 | 37 | "Regular expressions."; 38 | $check regex-match? ((string->regex ".+x.+") "123xyz"); 39 | $expect "456xyz456" regex-replace "123xyz125" (string->regex "45."); 40 | 41 | -------------------------------------------------------------------------------- /demo/features/variables.txt: -------------------------------------------------------------------------------- 1 | "Well-formed variable name demos."; 2 | 3 | $def! x 1; 4 | $def! xY 1; 5 | $def! FOO 1; 6 | $def! Bar123 1; 7 | $def! __LINE__ 1; 8 | $def! *STDIN* 1; 9 | $def! 1; 10 | $def! var-name 1; 11 | $def! call/cc 1; 12 | $def! a+b 1; 13 | 14 | -------------------------------------------------------------------------------- /demo/hello-world.txt: -------------------------------------------------------------------------------- 1 | "Hello world demo."; 2 | 3 | puts "hello, world"; 4 | 5 | -------------------------------------------------------------------------------- /demo/hello.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Rectangle { 4 | id: page 5 | width: 320; height: 480 6 | color: "lightgray" 7 | 8 | Text { 9 | id: helloText 10 | text: "Hello world!" 11 | y: 30 12 | anchors.horizontalCenter: page.horizontalCenter 13 | font.pointSize: 24; font.bold: true 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /demo/high-level-language/aliases.txt: -------------------------------------------------------------------------------- 1 | "High-level language aliases demos."; 2 | 3 | def $expect expect; 4 | 5 | expect #f (not #t); 6 | expect #f (not "x"); 7 | expect #t (not #f); 8 | expect "z" and (eqv? "x" "x") () "z"; 9 | expect #f or #f (eqv? "x" "y"); 10 | 11 | "The following function definitions are equivalent."; 12 | def f 42; 13 | def id1 lambda (x) 42; 14 | defn id2 (x) 42; 15 | 16 | -------------------------------------------------------------------------------- /demo/high-level-language/bindings.txt: -------------------------------------------------------------------------------- 1 | "High-level language bindings demos."; 2 | 3 | def i 42; 4 | def (i : Number) 42; 5 | 6 | defn test (let) 7 | ( 8 | "Compability case to plain bindings without annotations."; 9 | expect 42 let ([x 42]) x; 10 | "Typed cases."; 11 | expect 42 let ([(x : Number) 42]) x; 12 | expect "foo" let ([(x : Number) 42][(y : string) "foo"]) y; 13 | ); 14 | 15 | test let; 16 | test let*; 17 | test letrec; 18 | 19 | -------------------------------------------------------------------------------- /demo/high-level-language/if.txt: -------------------------------------------------------------------------------- 1 | "Control operator demos using high-level language features."; 2 | 3 | def x (); 4 | display "x is "; 5 | display (if (null? x) "empty" else "not empty"); 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /demo/high-level-language/lambda.txt: -------------------------------------------------------------------------------- 1 | "High-level language lambda demos."; 2 | 3 | def fn lambda ((x : Number)) x; 4 | fn 2; 5 | 6 | def fn lambda (x : Number,) x; 7 | fn 2; 8 | 9 | defn fn ((x : List)) x; 10 | fn (list 1 2 3); 11 | 12 | defn fn (x : List,) x; 13 | fn (list 1 2 3); 14 | 15 | defn fn ((x : Number) (y : List)) x; 16 | fn 2 (list 3 4); 17 | 18 | defn fn (x : Number, y : List) x; 19 | fn 2 (list 3 4); 20 | 21 | -------------------------------------------------------------------------------- /demo/high-level-language/qt.txt: -------------------------------------------------------------------------------- 1 | "This is an functionality equivalent version of demo/qt.txt."; 2 | 3 | import std.classes make-class make-object $access; 4 | import UnilangQt QWidget 5 | make-QPushButton make-QLabel make-QPushButton make-QLabel Qt.AlignCenter 6 | make-QVBoxLayout QLayout-addWidget QObject-connect QLabel-setText 7 | make-QApplication QApplication-exec; 8 | 9 | () newline; 10 | 11 | defn log (x) (display x; () newline); 12 | defn logd (x) (log "DEBUG:"; log x); 13 | 14 | logd "Loading script ..."; 15 | 16 | def global () get-current-environment; 17 | def MyWidget make-class QWidget ($lambda (self) 18 | { 19 | logd "[ctor] MyWidget"; 20 | $set! self hello list "Hallo Welt" "Hei maailma" "Hola Mundo" "Привет мир"; 21 | $set! self button make-QPushButton "Click me!"; 22 | $set! self text make-QLabel "Hello World!" Qt.AlignCenter; 23 | $set! self layout () make-QVBoxLayout; 24 | import self text button layout; 25 | QLayout-addWidget layout text; 26 | QLayout-addWidget layout button; 27 | ($remote-eval setLayout self) layout; 28 | QObject-connect button "clicked(bool)" ($remote-eval _dynamic self) 29 | ($lambda/e (make-environment self global) () { 30 | logd "[slot] Callback called."; 31 | QLabel-setText text (random.choice hello) 32 | }) 33 | }); 34 | 35 | def app () make-QApplication; 36 | def wgt make-object MyWidget; 37 | ($access wgt resize) 800 600; 38 | () ($access wgt show); 39 | sys.exit (() QApplication-exec); 40 | 41 | -------------------------------------------------------------------------------- /demo/high-level-language/swap.txt: -------------------------------------------------------------------------------- 1 | "High-level language demo of swapping."; 2 | 3 | defn swap! (&x &y) 4 | ( 5 | def t move! x; 6 | x := move! y; 7 | y := move! t; 8 | #inert 9 | ); 10 | 11 | def expect $expect; 12 | def x 1; 13 | def y 2; 14 | expect (list 1 2) list x y; 15 | swap! x y; 16 | expect (list 2 1) list x y; 17 | 18 | -------------------------------------------------------------------------------- /demo/introduction/advanced/comments.txt: -------------------------------------------------------------------------------- 1 | "Comments demos."; 2 | 3 | "This is a single-line comment."; 4 | 5 | "This is a", 6 | "multi-line comment."; 7 | 8 | "The following case shows an example of custom comments operator.", 9 | "It does nothing, by ignoring all of its arguments"; 10 | 11 | $def! $document-block $vau . #ignore ""; 12 | 13 | $document-block 14 | ( 15 | "@brief brief xxx" 16 | "@param p param" 17 | ); 18 | 19 | 20 | -------------------------------------------------------------------------------- /demo/introduction/advanced/core-library.txt: -------------------------------------------------------------------------------- 1 | "Core library utility demos."; 2 | 3 | $def! x "a"; 4 | $cond (x) 5 | ((eqv? x "a") "x is a") 6 | ((eqv? x "b") "x is b") 7 | (#t "x is other"); 8 | 9 | $sequence ($def! l ()) ($when (null? l) "list is null" (display l)); 10 | $sequence ($def! l list "x") ($unless (null? l) "list is not null" (display l)); 11 | 12 | $def! l list "x" "y" "z"; 13 | display (first l); 14 | display (rest l); 15 | 16 | $defv! $defw! (f formals ef .body) d 17 | eval (list $set! d f wrap (list* $vau formals ef body)) d; 18 | 19 | $defw! foldr1 (kons knil l) d 20 | apply accr (list l null? knil first rest kons) d; 21 | 22 | $defw! map1 (appv l) d 23 | foldr1 ($lambda (x xs) cons% (apply appv (list x) d) xs) () (forward! l); 24 | 25 | $defv! $let (bindings .body) d 26 | eval (list* () (list* $lambda (map1 first bindings) 27 | (list body)) (map1 ($lambda (x) list (rest x)) bindings)) d; 28 | 29 | $let () 30 | $def! e $provide! (a b c) $def! (a b c) list "a" "b" "c"; 31 | 32 | $let () 33 | ( 34 | $def! library-module () make-environment; 35 | $set! library-module v 42; 36 | $set! library-module id $lambda (x) x; 37 | 38 | $import! library-module id v; 39 | display (id v); 40 | ); 41 | 42 | -------------------------------------------------------------------------------- /demo/introduction/advanced/enivornment-ownership.txt: -------------------------------------------------------------------------------- 1 | "Environment ownership demos."; 2 | 3 | $def! $rem $vau . #ignore ""; 4 | 5 | "The following program commented out is incorrect."; 6 | 7 | $rem $let () 8 | ( 9 | $defl! foo (x) $lambda () display x; 10 | () (foo 1); 11 | ); 12 | 13 | $let () 14 | ( 15 | $defl! foo (x d) 16 | ( 17 | $def! se make-environment d; 18 | $set! se x x; 19 | $lambda/e se () display x; 20 | ); 21 | () (foo 1 (() get-current-environment)); 22 | ); 23 | 24 | $let () 25 | ( 26 | $defl! foo (x) $lambda/e (() lock-current-environment) () display x; 27 | () (foo 1); 28 | ); 29 | 30 | -------------------------------------------------------------------------------- /demo/introduction/base/assignment.txt: -------------------------------------------------------------------------------- 1 | "Assignment demos."; 2 | 3 | $def! x "hello"; 4 | assign! x "world"; 5 | display x; 6 | 7 | -------------------------------------------------------------------------------- /demo/introduction/base/bindings-and-environments.txt: -------------------------------------------------------------------------------- 1 | "Binding and environment demos."; 2 | 3 | eval (list display "Hello, world!") (() get-current-environment); 4 | 5 | $def! (x y) list "hello" "world"; 6 | display x; 7 | display y; 8 | 9 | "The following 3 cases are equivalent when the order is not cared."; 10 | 11 | $def! (x y) list "hello" "world"; 12 | 13 | $sequence (list ($def! x "hello") ($def! y "world")) #inert; 14 | 15 | $def! x "hello"; 16 | $def! y "world"; 17 | 18 | let ([x "hello"] [y "world"]) display (list x y); 19 | 20 | $def! $lambda $vau (formals .body) d 21 | wrap (eval (cons $vau (cons formals (cons #ignore body))) d); 22 | 23 | $def! $set! $vau (e formals .expr) d 24 | eval (list $def! formals (unwrap eval) expr d) (eval e d); 25 | 26 | $def! $defv! $vau ($f formals ef .body) d 27 | eval (list $set! d $f $vau formals ef body) d; 28 | $defv! $defl! (f formals .body) d 29 | eval (list $set! d f $lambda formals body) d; 30 | 31 | $def! defn $defl!; 32 | 33 | -------------------------------------------------------------------------------- /demo/introduction/base/controls.txt: -------------------------------------------------------------------------------- 1 | "Control operator demos."; 2 | 3 | $def! x (); 4 | display "x is "; 5 | display ($if (null? x) "empty" "not empty"); 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /demo/introduction/base/def-syntax.txt: -------------------------------------------------------------------------------- 1 | "Variable and objects."; 2 | 3 | $def! x "hello"; 4 | 5 | $def! def $def!; 6 | 7 | "Simple syntax sugars."; 8 | 9 | "The following two cases are equivalent."; 10 | 11 | def x "hello"; 12 | 13 | $def! x "hello"; 14 | 15 | -------------------------------------------------------------------------------- /demo/introduction/base/equals.txt: -------------------------------------------------------------------------------- 1 | "Equality demos."; 2 | 3 | $def! x "x"; 4 | display (eq? x "x"); 5 | display (eqv? x "x"); 6 | display (equal? (list x) (list "x")); 7 | 8 | -------------------------------------------------------------------------------- /demo/introduction/base/functions.txt: -------------------------------------------------------------------------------- 1 | "Function demos."; 2 | 3 | $lambda (x) display x; 4 | 5 | "The following two cases are equivalent."; 6 | 7 | $defl! id (x) x; 8 | 9 | defn id (x) x; 10 | 11 | display (($lambda ((x y)) x) (list "hello" "world")); 12 | 13 | $def! car $lambda ((x .)) x; 14 | $def! cdr $lambda ((#ignore .x)) x; 15 | 16 | -------------------------------------------------------------------------------- /demo/introduction/base/hello-world2.txt: -------------------------------------------------------------------------------- 1 | "Hello world demo: nested display."; 2 | 3 | display (display "Hello, world!"); 4 | 5 | -------------------------------------------------------------------------------- /demo/introduction/base/hello-world3.txt: -------------------------------------------------------------------------------- 1 | "Hello world demo: alternatives."; 2 | 3 | (display "Hello, world!"); 4 | 5 | () newline; 6 | 7 | () display "Hello, world!"; 8 | 9 | "The following two cases are equivalent."; 10 | 11 | display "Hello, world!"; 12 | 13 | $sequence (display "Hello, world!"); 14 | 15 | "The following two cases are equivalent."; 16 | 17 | display "Hello, "; display "world!"; 18 | 19 | $sequence (display "Hello, ") (display "world!"); 20 | 21 | -------------------------------------------------------------------------------- /demo/introduction/base/lists.txt: -------------------------------------------------------------------------------- 1 | "List demos."; 2 | 3 | list "hello" "world"; 4 | 5 | (); 6 | 7 | () list; 8 | 9 | list "hello" (list "world"); 10 | 11 | list (); 12 | 13 | "The following two cases are equivalent."; 14 | 15 | cons "x" (); 16 | 17 | list "x"; 18 | 19 | -------------------------------------------------------------------------------- /demo/qml.txt: -------------------------------------------------------------------------------- 1 | import UnilangQt QCoreApplication-setAttribute Qt.AA_EnableHighDpiScaling 2 | make-QApplication make-QQuickView QQuickView_set-source 3 | QQuickView_set-transparent QQuickView-show QApplication-exec; 4 | 5 | () newline; 6 | 7 | QCoreApplication-setAttribute Qt.AA_EnableHighDpiScaling; 8 | def app () make-QApplication; 9 | def window () make-QQuickView; 10 | QQuickView_set-transparent window; 11 | QQuickView_set-source window "demo/hello.qml"; 12 | QQuickView-show window; 13 | () QApplication-exec; 14 | -------------------------------------------------------------------------------- /demo/qt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | import random 4 | from PySide2 import QtCore, QtWidgets 5 | 6 | class MyWidget(QtWidgets.QWidget): 7 | def __init__(self): 8 | super().__init__() 9 | 10 | self.hello = ["Hallo Welt", "Hei maailma", "Hola Mundo", "Привет мир"] 11 | 12 | self.button = QtWidgets.QPushButton("Click me!") 13 | self.text = QtWidgets.QLabel("Hello World", 14 | alignment=QtCore.Qt.AlignCenter) 15 | 16 | self.layout = QtWidgets.QVBoxLayout() 17 | self.layout.addWidget(self.text) 18 | self.layout.addWidget(self.button) 19 | self.setLayout(self.layout) 20 | 21 | self.button.clicked.connect(self.magic) 22 | 23 | @QtCore.Slot() 24 | def magic(self): 25 | self.text.setText(random.choice(self.hello)) 26 | 27 | if __name__ == "__main__": 28 | app = QtWidgets.QApplication([]) 29 | 30 | widget = MyWidget() 31 | widget.resize(800, 600) 32 | widget.show() 33 | 34 | sys.exit(app.exec_()) 35 | 36 | -------------------------------------------------------------------------------- /demo/qt.txt: -------------------------------------------------------------------------------- 1 | import std.classes make-class make-object $access; 2 | import UnilangQt QWidget make-QPushButton make-QLabel Qt.AlignCenter 3 | make-QVBoxLayout QLayout-addWidget QObject-connect QLabel-setText 4 | make-QApplication QApplication-exec; 5 | 6 | () newline; 7 | 8 | defn log (x) {display x; () newline}; 9 | defn logd (x) {log "DEBUG:"; log x}; 10 | 11 | logd "Loading script ..."; 12 | 13 | def global () get-current-environment; 14 | def MyWidget make-class QWidget (lambda (self) 15 | ( 16 | logd "[ctor] MyWidget"; 17 | $set! self hello list "Hallo Welt" "Hei maailma" "Hola Mundo" "Привет мир"; 18 | $set! self button make-QPushButton "Click me!"; 19 | $set! self text make-QLabel "Hello World!" Qt.AlignCenter; 20 | $set! self layout () make-QVBoxLayout; 21 | import self text button layout; 22 | QLayout-addWidget layout text; 23 | QLayout-addWidget layout button; 24 | ($remote-eval setLayout self) layout; 25 | QObject-connect button "clicked(bool)" ($remote-eval _dynamic self) 26 | ($lambda/e (make-environment self global) () ( 27 | logd "[slot] Callback called."; 28 | QLabel-setText text (random.choice hello) 29 | )) 30 | )); 31 | 32 | def app () make-QApplication; 33 | def wgt make-object MyWidget; 34 | ($access wgt resize) 800 600; 35 | () ($access wgt show); 36 | sys.exit (() QApplication-exec); 37 | 38 | -------------------------------------------------------------------------------- /demo/sort.txt: -------------------------------------------------------------------------------- 1 | "QuickSort variant, leftmost pivot, non-in-place."; 2 | 3 | defn log (&title &r) 4 | { 5 | display title; 6 | display r; 7 | () newline; 8 | forward! r 9 | }; 10 | 11 | defn sort (&lst) 12 | { 13 | if ($or (null? lst) (null? (rest& lst))) 14 | { 15 | forward! lst 16 | } 17 | else 18 | { 19 | log "partial result: " 20 | ( 21 | let ([(pivot .rlst) expire lst]) 22 | { 23 | log "selected pivot: " pivot; 24 | append 25 | (log "sorted left: " 26 | (sort (log "filtered less than pivot: " 27 | (filter (lambda (x) {x < pivot}) rlst)))) 28 | (list pivot) 29 | (log "sorted right: " 30 | (sort (log "filtered not less than pivot: " 31 | (filter (lambda (x) {x > pivot}) rlst)))) 32 | } 33 | ) 34 | } 35 | }; 36 | 37 | def data (list 2 3 13 11 5 1 14 6 16 8 10 9 15 7 12 4); 38 | log "original: " data; 39 | log "sorted: " (sort data); 40 | () newline; 41 | 42 | -------------------------------------------------------------------------------- /demo/sort2.txt: -------------------------------------------------------------------------------- 1 | "QuickSort variant, leftmost pivot, in-place."; 2 | 3 | defn list-length (&l) 4 | if (null? l) 0 else {1 + (list-length (rest& l))}; 5 | 6 | $defl%! list-at& (&l i) 7 | if (i <= 0) {first& l} else {list-at& (rest& l) (i - 1)}; 8 | 9 | defn swap! (&x &y) 10 | { 11 | def t move! x; 12 | x := move! y; 13 | y := move! t; 14 | #inert 15 | }; 16 | 17 | defn log (&title &r) 18 | { 19 | display title; 20 | display r; 21 | () newline; 22 | forward! r 23 | }; 24 | 25 | defn partition! (&lst i j pivot) 26 | { 27 | unless (i = j) 28 | { 29 | while ($and (not? (i = j)) ((list-at& lst i) < pivot)) 30 | (assign! i (+ i 1)); 31 | unless (i = j) 32 | { 33 | let ([k i]) 34 | ( 35 | until (k := k + 1; k = j) 36 | ( 37 | if ((list-at& lst k) < pivot) 38 | ( 39 | swap! (list-at& lst i) (list-at& lst k); 40 | i := i + 1; 41 | ) 42 | ) 43 | ) 44 | } 45 | }; 46 | i 47 | }; 48 | 49 | defn sort-range! (&lst lo hi) 50 | { 51 | if (hi - lo > 1) 52 | { 53 | let ([m partition! lst (lo + 1) hi (list-at& lst lo)]) 54 | { 55 | swap! (list-at& lst lo) (list-at& lst (m - 1)); 56 | log "partitioned: " lst; 57 | sort-range! lst lo (m - 1); 58 | log "left sorted: " lst; 59 | sort-range! lst m hi; 60 | log "right sorted: " lst; 61 | } 62 | }; 63 | #inert 64 | }; 65 | 66 | defn sort! (&lst) sort-range! lst 0 (list-length lst); 67 | 68 | def data (list 2 3 13 11 5 1 14 6 16 8 10 9 15 7 12 4); 69 | log "original: " data; 70 | log "sorted: " (sort! data; data); 71 | () newline; 72 | 73 | -------------------------------------------------------------------------------- /detect-llvm.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # SPDX-FileCopyrightText: 2021-2023 UnionTech Software Technology Co.,Ltd. 3 | # Requires: LLVM 7.0 installed in some known location. 4 | # Usage: ./detect-llvm.sh, or sourced. 5 | # shellcheck disable=2016 6 | 7 | set -e 8 | 9 | # XXX: Currently we stick to LLVM 7. 10 | 11 | if test -z "$UNILANG_NO_LLVM"; then 12 | 13 | test_llvm7_prefix() 14 | { 15 | for pfx in "$USE_LLVM_PREFIX" '/usr/lib/llvm-7' '/opt/llvm70' \ 16 | '/opt/llvm70' '/usr/local' '/usr'; do 17 | if test -x "$pfx/bin/llvm-config"; then 18 | if [[ $("$pfx/bin/llvm-config" --version) =~ 7\..+ ]]; then 19 | LLVM_PREFIX="$("$pfx/bin/llvm-config" --prefix)" 20 | LLVM_BINDIR="$("$pfx/bin/llvm-config" --bindir)" 21 | return 22 | fi 23 | fi 24 | done 25 | } 26 | 27 | test_llvm7_prefix 28 | if test -d "$LLVM_PREFIX"; then 29 | echo 'Found LLVM 7 prefix:' "$LLVM_PREFIX" 30 | else 31 | echo 'ERROR: Failed to find LLVM 7.' 32 | echo 'Please specify the installation prefix in $USE_LLVM_PREFIX.' 33 | echo 'Alternatively, consider disable LLVM by "UNILANG_NO_LLVM".' 34 | exit 1 35 | fi 36 | echo 'LLVM version:' "$("$LLVM_BINDIR/llvm-config" --version)" 37 | 38 | CXXFLAGS_EXTRA="$("$LLVM_BINDIR/llvm-config" --cxxflags | sed s/-DNDEBUG//g) \ 39 | -fexceptions -frtti -Wp,-U_FORTIFY_SOURCE -U_FORTIFY_SOURCE -Wno-date-time" 40 | if echo "$CXX" --version | grep -q clang; then 41 | echo 'Found $CXX: '"$CXX"', unsupported compiler options are removed.' 42 | CXXFLAGS_EXTRA="$(echo "$CXXFLAGS_EXTRA" \ 43 | | sed -E 's/-Wno-(class-memaccess|maybe-uninitialized)//g')" 44 | fi 45 | # NOTE: Loading local symbol in LLVM IR requires '--export-dynamic'. 46 | LIBS_EXTRA="$("$LLVM_BINDIR/llvm-config" --ldflags) \ 47 | $("$LLVM_BINDIR/llvm-config" --libs) -lffi -Wl,--export-dynamic" 48 | 49 | else 50 | 51 | echo 'LLVM support is disabled.' 52 | CXXFLAGS_EXTRA='-DUNILANG_NO_LLVM=1' 53 | # XXX: Some other dependencies are still needed. 54 | # shellcheck disable=2034 55 | LIBS_EXTRA='-lffi' 56 | 57 | fi 58 | 59 | -------------------------------------------------------------------------------- /include/BasicReduction.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020-2022 UnionTech Software Technology Co.,Ltd. 2 | 3 | #ifndef INC_Unilang_BasicReduction_h_ 4 | #define INC_Unilang_BasicReduction_h_ 1 5 | 6 | #include "TermNode.h" // for TermNode, ystdex::ref_eq, yforward; 7 | #include // for std::for_each; 8 | #include // for ystdex::exclude_self_t; 9 | #include YFM_YSLib_Core_YObject // for YSLib::EmplaceCallResult; 10 | 11 | namespace Unilang 12 | { 13 | 14 | enum class ReductionStatus : size_t 15 | { 16 | Partial = 0x00, 17 | Neutral = 0x01, 18 | Clean = 0x02, 19 | Retained = 0x03, 20 | Regular = Retained, 21 | Retrying = 0x10 22 | }; 23 | 24 | YB_ATTR_nodiscard YB_STATELESS constexpr bool 25 | CheckReducible(ReductionStatus status) noexcept 26 | { 27 | return status == ReductionStatus::Partial 28 | || status == ReductionStatus::Retrying; 29 | } 30 | 31 | 32 | ReductionStatus 33 | RegularizeTerm(TermNode&, ReductionStatus) noexcept; 34 | 35 | 36 | inline void 37 | LiftOther(TermNode& term, TermNode& tm) 38 | { 39 | term.MoveContent(std::move(tm)); 40 | } 41 | 42 | inline void 43 | LiftOtherValue(TermNode& term, TermNode& tm) 44 | { 45 | AssertValueTags(tm); 46 | LiftOther(term, tm); 47 | } 48 | 49 | inline void 50 | LiftTerm(TermNode& term, TermNode& tm) 51 | { 52 | if(!ystdex::ref_eq<>()(term, tm)) 53 | LiftOther(term, tm); 54 | } 55 | 56 | void 57 | LiftOtherOrCopy(TermNode&, TermNode&, bool); 58 | 59 | void 60 | LiftTermOrCopy(TermNode&, TermNode&, bool); 61 | 62 | 63 | inline void 64 | LiftTermRef(TermNode& term, const TermNode& tm) 65 | { 66 | Unilang::SetContentWith(term, tm, &ValueObject::MakeIndirect); 67 | } 68 | inline void 69 | LiftTermRef(ValueObject& term_v, const ValueObject& vo) 70 | { 71 | term_v = vo.MakeIndirect(); 72 | } 73 | inline void 74 | LiftTermRef(TermNode& term, const ValueObject& vo) 75 | { 76 | LiftTermRef(term.Value, vo); 77 | } 78 | 79 | 80 | void 81 | LiftToReturn(TermNode&); 82 | 83 | TNIter 84 | LiftPrefixToReturn(TermNode&, TNCIter); 85 | inline TNIter 86 | LiftPrefixToReturn(TermNode& term) 87 | { 88 | return LiftPrefixToReturn(term, term.begin()); 89 | } 90 | 91 | inline void 92 | LiftSubtermsToReturn(TermNode& term) 93 | { 94 | std::for_each(term.begin(), term.end(), LiftToReturn); 95 | } 96 | 97 | ReductionStatus 98 | ReduceBranchToList(TermNode&) noexcept; 99 | 100 | ReductionStatus 101 | ReduceBranchToListValue(TermNode& term) noexcept; 102 | 103 | inline ReductionStatus 104 | ReduceForLiftedResult(TermNode& term) 105 | { 106 | LiftToReturn(term); 107 | return ReductionStatus::Retained; 108 | } 109 | 110 | 111 | YB_ATTR_nodiscard YB_STATELESS constexpr ReductionStatus 112 | EmplaceCallResultOrReturn(TermNode&, ReductionStatus status) noexcept 113 | { 114 | return status; 115 | } 116 | template)> 118 | YB_ATTR_nodiscard inline ReductionStatus 119 | EmplaceCallResultOrReturn(TermNode& term, _tParam&& arg) 120 | { 121 | YSLib::EmplaceCallResult(term.Value, yforward(arg), term.get_allocator()); 122 | return ReductionStatus::Clean; 123 | } 124 | template 125 | YB_ATTR_nodiscard inline ReductionStatus 126 | EmplaceCallResultOrReturn(TermNode& term, array arg) 127 | { 128 | TermNode::Container con(term.get_allocator()); 129 | 130 | for(auto& vo : arg) 131 | con.emplace_back(NoContainer, std::move(vo)); 132 | con.swap(term.GetContainerRef()); 133 | return ReductionStatus::Retained; 134 | } 135 | 136 | } // namespace Unilang; 137 | 138 | #endif 139 | -------------------------------------------------------------------------------- /include/Exception.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020-2023 UnionTech Software Technology Co.,Ltd. 2 | 3 | #ifndef INC_Unilang_Exceptions_h_ 4 | #define INC_Unilang_Exceptions_h_ 1 5 | 6 | #include "Lexical.h" // for size_t, default_allocator, byte, std::string, 7 | // string_view, shared_ptr, SourceInformation, Unilang::Deref; 8 | #include // for std::runtime_error; 9 | #include // for ystdex::invoke; 10 | #include "TermNode.h" // for TermNode; 11 | 12 | namespace Unilang 13 | { 14 | 15 | class UnilangException : public std::runtime_error 16 | { 17 | public: 18 | using runtime_error::runtime_error; 19 | 20 | UnilangException(const UnilangException&) = default; 21 | ~UnilangException() override; 22 | 23 | virtual void 24 | ReplaceAllocator(default_allocator) 25 | {} 26 | }; 27 | 28 | 29 | class TypeError : public UnilangException 30 | { 31 | public: 32 | using UnilangException::UnilangException; 33 | 34 | TypeError(const TypeError&) = default; 35 | ~TypeError() override; 36 | }; 37 | 38 | 39 | class ValueCategoryMismatch : public TypeError 40 | { 41 | public: 42 | using TypeError::TypeError; 43 | 44 | ValueCategoryMismatch(const ValueCategoryMismatch&) = default; 45 | ~ValueCategoryMismatch() override; 46 | }; 47 | 48 | 49 | class ListTypeError : public TypeError 50 | { 51 | public: 52 | using TypeError::TypeError; 53 | 54 | ListTypeError(const ListTypeError&) = default; 55 | ~ListTypeError() override; 56 | }; 57 | 58 | 59 | class ListReductionFailure : public TypeError 60 | { 61 | public: 62 | using TypeError::TypeError; 63 | 64 | ListReductionFailure(const ListReductionFailure&) = default; 65 | ~ListReductionFailure() override; 66 | }; 67 | 68 | 69 | class InvalidSyntax : public UnilangException 70 | { 71 | public: 72 | using UnilangException::UnilangException; 73 | 74 | InvalidSyntax(const InvalidSyntax&) = default; 75 | ~InvalidSyntax() override; 76 | }; 77 | 78 | 79 | class ParameterMismatch : public ListTypeError 80 | { 81 | public: 82 | using ListTypeError::ListTypeError; 83 | 84 | ParameterMismatch(const ParameterMismatch&) = default; 85 | ~ParameterMismatch() override; 86 | }; 87 | 88 | 89 | class ArityMismatch : public ParameterMismatch 90 | { 91 | private: 92 | size_t expected; 93 | size_t received; 94 | 95 | public: 96 | ArityMismatch(size_t, size_t); 97 | ArityMismatch(const ArityMismatch&) = default; 98 | ~ArityMismatch() override; 99 | 100 | YB_ATTR_nodiscard YB_PURE size_t 101 | GetExpected() const noexcept 102 | { 103 | return expected; 104 | } 105 | 106 | YB_ATTR_nodiscard YB_PURE size_t 107 | GetReceived() const noexcept 108 | { 109 | return received; 110 | } 111 | }; 112 | 113 | 114 | class BadIdentifier : public InvalidSyntax 115 | { 116 | private: 117 | shared_ptr p_identifier; 118 | 119 | public: 120 | SourceInformation Source{}; 121 | 122 | YB_NONNULL(2) 123 | BadIdentifier(const char*, size_t = 0); 124 | BadIdentifier(string_view, size_t = 0); 125 | BadIdentifier(const BadIdentifier&) = default; 126 | ~BadIdentifier() override; 127 | 128 | YB_ATTR_nodiscard YB_PURE const std::string& 129 | GetIdentifier() const noexcept 130 | { 131 | return Unilang::Deref(p_identifier); 132 | } 133 | 134 | void 135 | ReplaceAllocator(default_allocator) override; 136 | }; 137 | 138 | 139 | class InvalidReference : public UnilangException 140 | { 141 | public: 142 | using UnilangException::UnilangException; 143 | 144 | InvalidReference(const InvalidReference&) = default; 145 | ~InvalidReference() override; 146 | }; 147 | 148 | 149 | template 150 | auto 151 | GuardExceptionsForAllocator(default_allocator a, _fCallable&& f, 152 | _tParams&&... args) 153 | -> decltype(ystdex::invoke(yforward(f), yforward(args)...)) 154 | { 155 | TryExpr(ystdex::invoke(yforward(f), yforward(args)...)) 156 | catch(UnilangException& e) 157 | { 158 | e.ReplaceAllocator(a); 159 | throw; 160 | } 161 | } 162 | 163 | 164 | YB_NORETURN void 165 | ThrowInsufficientTermsError(const TermNode&, bool, size_t = 0); 166 | 167 | YB_NORETURN void 168 | ThrowListTypeErrorForAtom(const TermNode&, bool, size_t = 0); 169 | 170 | YB_NORETURN YB_NONNULL(1) void 171 | ThrowListTypeErrorForInvalidType(const char*, const TermNode&, bool, 172 | size_t = 0); 173 | YB_NORETURN void 174 | ThrowListTypeErrorForInvalidType(const type_info&, const TermNode&, bool, 175 | size_t = 0); 176 | 177 | YB_NORETURN void 178 | ThrowListTypeErrorForNonList(const TermNode&, bool, size_t = 0); 179 | 180 | YB_NORETURN YB_NONNULL(1,2) void 181 | ThrowTypeErrorForInvalidType(const char*, const char*); 182 | YB_NORETURN YB_NONNULL(1) void 183 | ThrowTypeErrorForInvalidType(const char*, const TermNode&, bool, size_t = 0); 184 | YB_NORETURN void 185 | ThrowTypeErrorForInvalidType(const type_info&, const TermNode&, bool, 186 | size_t = 0); 187 | 188 | YB_NORETURN void 189 | ThrowValueCategoryError(const TermNode&); 190 | 191 | 192 | YB_NORETURN void 193 | ThrowNonmodifiableErrorForAssignee(); 194 | 195 | } // namespace Unilang; 196 | 197 | #endif 198 | 199 | -------------------------------------------------------------------------------- /include/Forms.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020-2023 UnionTech Software Technology Co.,Ltd. 2 | 3 | #ifndef INC_Unilang_Forms_h_ 4 | #define INC_Unilang_Forms_h_ 1 5 | 6 | #include "Evaluation.h" // for ReductionStatus, TermNode, ReferenceTerm, 7 | // YSLib::EmplaceCallResult, yforward, Unilang::Deref, 8 | // Unilang::EmplaceCallResultOrReturn, AccessTypedValue, Strict, 9 | // Unilang::RegisterFormHandler, Context; 10 | #include // for ystdex::expand_proxy, 11 | // ystdex::invoke_nonvoid, ystdex::make_expanded, ystdex::bind1, 12 | // std::placeholders::_2, std::ref; 13 | #include // for std::next; 14 | #include "TermAccess.h" // for ResolvedTermReferencePtr, 15 | // Unilang::ResolveRegular; 16 | #include // for ystdex::make_transform; 17 | #include // for std::accumulate; 18 | #include // for ystdex::equality_comparable; 19 | #include // for ystdex::examiners; 20 | #include // for ystdex::exclude_self_params_t; 21 | #include // for ystdex::size_t_; 22 | 23 | namespace Unilang 24 | { 25 | 26 | class EncapsulationBase 27 | { 28 | private: 29 | shared_ptr p_type; 30 | 31 | public: 32 | EncapsulationBase(shared_ptr p) noexcept 33 | : p_type(std::move(p)) 34 | {} 35 | EncapsulationBase(const EncapsulationBase&) = default; 36 | EncapsulationBase(EncapsulationBase&&) = default; 37 | 38 | EncapsulationBase& 39 | operator=(const EncapsulationBase&) = default; 40 | EncapsulationBase& 41 | operator=(EncapsulationBase&&) = default; 42 | 43 | YB_ATTR_nodiscard YB_PURE friend bool 44 | operator==(const EncapsulationBase& x, const EncapsulationBase& y) noexcept 45 | { 46 | return x.p_type == y.p_type; 47 | } 48 | 49 | const EncapsulationBase& 50 | Get() const noexcept 51 | { 52 | return *this; 53 | } 54 | EncapsulationBase& 55 | GetEncapsulationBaseRef() noexcept 56 | { 57 | return *this; 58 | } 59 | const shared_ptr& 60 | GetType() const noexcept 61 | { 62 | return p_type; 63 | } 64 | }; 65 | 66 | 67 | class Encapsulation final : private EncapsulationBase 68 | { 69 | public: 70 | mutable TermNode TermRef; 71 | 72 | Encapsulation(shared_ptr p, TermNode term) 73 | : EncapsulationBase(std::move(p)), TermRef(std::move(term)) 74 | {} 75 | Encapsulation(const Encapsulation&) = default; 76 | Encapsulation(Encapsulation&&) = default; 77 | 78 | Encapsulation& 79 | operator=(const Encapsulation&) = default; 80 | Encapsulation& 81 | operator=(Encapsulation&&) = default; 82 | 83 | YB_ATTR_nodiscard YB_PURE friend bool 84 | operator==(const Encapsulation& x, const Encapsulation& y) noexcept 85 | { 86 | return x.Get() == y.Get() 87 | && Equal(ReferenceTerm(x.TermRef), ReferenceTerm(y.TermRef)); 88 | } 89 | 90 | using EncapsulationBase::Get; 91 | using EncapsulationBase::GetType; 92 | 93 | static bool 94 | Equal(const TermNode&, const TermNode&); 95 | }; 96 | 97 | 98 | class Encapsulate final : private EncapsulationBase 99 | { 100 | public: 101 | Encapsulate(shared_ptr p) 102 | : EncapsulationBase(std::move(p)) 103 | {} 104 | Encapsulate(const Encapsulate&) = default; 105 | Encapsulate(Encapsulate&&) = default; 106 | 107 | Encapsulate& 108 | operator=(const Encapsulate&) = default; 109 | Encapsulate& 110 | operator=(Encapsulate&&) = default; 111 | 112 | YB_ATTR_nodiscard YB_PURE friend bool 113 | operator==(const Encapsulate& x, const Encapsulate& y) noexcept 114 | { 115 | return x.Get() == y.Get(); 116 | } 117 | 118 | ReductionStatus 119 | operator()(TermNode&) const; 120 | }; 121 | 122 | 123 | class Encapsulated final : private EncapsulationBase 124 | { 125 | public: 126 | Encapsulated(shared_ptr p) 127 | : EncapsulationBase(std::move(p)) 128 | {} 129 | Encapsulated(const Encapsulated&) = default; 130 | Encapsulated(Encapsulated&&) = default; 131 | 132 | Encapsulated& 133 | operator=(const Encapsulated&) = default; 134 | Encapsulated& 135 | operator=(Encapsulated&&) = default; 136 | 137 | YB_ATTR_nodiscard YB_PURE friend bool 138 | operator==(const Encapsulated& x, const Encapsulated& y) noexcept 139 | { 140 | return x.Get() == y.Get(); 141 | } 142 | 143 | ReductionStatus 144 | operator()(TermNode&) const; 145 | }; 146 | 147 | 148 | class Decapsulate final : private EncapsulationBase 149 | { 150 | public: 151 | Decapsulate(shared_ptr p) 152 | : EncapsulationBase(std::move(p)) 153 | {} 154 | Decapsulate(const Decapsulate&) = default; 155 | Decapsulate(Decapsulate&&) = default; 156 | 157 | Decapsulate& 158 | operator=(const Decapsulate&) = default; 159 | Decapsulate& 160 | operator=(Decapsulate&&) = default; 161 | 162 | YB_ATTR_nodiscard YB_PURE friend bool 163 | operator==(const Decapsulate& x, const Decapsulate& y) noexcept 164 | { 165 | return x.Get() == y.Get(); 166 | } 167 | 168 | ReductionStatus 169 | operator()(TermNode&, Context&) const; 170 | }; 171 | 172 | namespace Forms 173 | { 174 | 175 | template 176 | inline auto 177 | CallRawUnary(_func&& f, TermNode& term, _tParams&&... args) 178 | -> yimpl(decltype(ystdex::expand_proxy 179 | ::call(f, Unilang::Deref(std::next(term.begin())), yforward(args)...))) 180 | { 181 | RetainN(term); 182 | return ystdex::expand_proxy::call(f, 183 | Unilang::Deref(std::next(term.begin())), yforward(args)...); 184 | } 185 | 186 | template 187 | inline auto 188 | CallResolvedUnary(_func&& f, TermNode& term) 189 | -> yimpl(decltype(Unilang::ResolveTerm(yforward(f), term))) 190 | { 191 | return Forms::CallRawUnary([&](TermNode& tm) 192 | -> yimpl(decltype(Unilang::ResolveTerm(yforward(f), term))){ 193 | return Unilang::ResolveTerm(yforward(f), tm); 194 | }, term); 195 | } 196 | 197 | template 198 | inline auto 199 | CallRegularUnaryAs(_func&& f, TermNode& term, _tParams&&... args) 200 | -> yimpl(decltype(ystdex::expand_proxy::call(f, Access<_type>( 202 | term), ResolvedTermReferencePtr(), std::forward<_tParams>(args)...))) 203 | { 204 | using handler_t 205 | = yimpl(void)(_type&, const ResolvedTermReferencePtr&, _tParams&&...); 206 | 207 | return Forms::CallResolvedUnary( 208 | [&](TermNode& nd, ResolvedTermReferencePtr p_ref) 209 | -> decltype(ystdex::expand_proxy::call(f, 210 | std::declval<_type&>(), p_ref)){ 211 | return ystdex::expand_proxy::call(f, AccessRegular< 212 | _type>(nd, p_ref), p_ref, std::forward<_tParams>(args)...); 213 | }, term); 214 | } 215 | 216 | template 217 | ReductionStatus 218 | CallUnary(_func&& f, TermNode& term, _tParams&&... args) 219 | { 220 | return Forms::CallRawUnary([&](TermNode& tm){ 221 | return Unilang::EmplaceCallResultOrReturn(term, ystdex::invoke_nonvoid( 222 | ystdex::make_expanded(std::ref(f)), 223 | tm, yforward(args)...)); 224 | }, term); 225 | } 226 | 227 | template 228 | ReductionStatus 229 | CallUnaryAs(_func&& f, TermNode& term, _tParams&&... args) 230 | { 231 | return Forms::CallUnary([&](TermNode& tm){ 232 | return ystdex::make_expanded(tm)), 233 | _tParams&&...)>(std::ref(f))(AccessTypedValue<_type>(tm), 234 | yforward(args)...); 235 | }, term); 236 | } 237 | 238 | template 239 | ReductionStatus 240 | CallBinary(_func&& f, TermNode& term, _tParams&&... args) 241 | { 242 | RetainN(term, 2); 243 | 244 | auto i(term.begin()); 245 | auto& x(Unilang::Deref(++i)); 246 | 247 | return Unilang::EmplaceCallResultOrReturn(term, ystdex::invoke_nonvoid( 248 | ystdex::make_expanded( 249 | std::ref(f)), x, Unilang::Deref(++i), yforward(args)...)); 250 | } 251 | 252 | template 253 | ReductionStatus 254 | CallBinaryAs(_func&& f, TermNode& term, _tParams&&... args) 255 | { 256 | RetainN(term, 2); 257 | 258 | auto i(term.begin()); 259 | auto&& x(AccessTypedValue<_type>(Unilang::Deref(++i))); 260 | 261 | return Unilang::EmplaceCallResultOrReturn(term, ystdex::invoke_nonvoid( 262 | ystdex::make_expanded(*i)), _tParams&&...)>(std::ref(f)), 264 | yforward(x), AccessTypedValue<_type2>(Unilang::Deref(++i)), 265 | yforward(args)...)); 266 | } 267 | 268 | template 269 | ReductionStatus 270 | CallBinaryFold(_func f, _type val, TermNode& term, _tParams&&... args) 271 | { 272 | const auto n(term.size() - 1); 273 | auto i(term.begin()); 274 | const auto j(ystdex::make_transform(++i, [](TNIter it){ 275 | return AccessTypedValue<_type>(Unilang::Deref(it)); 276 | })); 277 | 278 | return Unilang::EmplaceCallResultOrReturn(term, std::accumulate(j, std::next( 279 | j, typename std::iterator_traits::difference_type(n)), val, 280 | ystdex::bind1(f, std::placeholders::_2, yforward(args)...))); 281 | } 282 | 283 | 284 | template 285 | struct UnaryExpansion 286 | : private ystdex::equality_comparable> 287 | { 288 | _func Function; 289 | 290 | template)> 292 | UnaryExpansion(_tParams&&... args) 293 | : Function(yforward(args)...) 294 | {} 295 | 296 | YB_ATTR_nodiscard YB_PURE friend bool 297 | operator==(const UnaryExpansion& x, const UnaryExpansion& y) 298 | { 299 | return ystdex::examiners::equal_examiner::are_equal(x.Function, 300 | y.Function); 301 | } 302 | 303 | template 304 | inline ReductionStatus 305 | operator()(_tParams&&... args) const 306 | { 307 | return Forms::CallUnary(Function, yforward(args)...); 308 | } 309 | }; 310 | 311 | 312 | template 313 | struct UnaryAsExpansion 314 | : private ystdex::equality_comparable> 315 | { 316 | _func Function; 317 | 318 | template)> 320 | UnaryAsExpansion(_tParams&&... args) 321 | : Function(yforward(args)...) 322 | {} 323 | 324 | YB_ATTR_nodiscard YB_PURE friend bool 325 | operator==(const UnaryAsExpansion& x, const UnaryAsExpansion& y) 326 | { 327 | return ystdex::examiners::equal_examiner::are_equal(x.Function, 328 | y.Function); 329 | } 330 | 331 | template 332 | inline ReductionStatus 333 | operator()(_tParams&&... args) const 334 | { 335 | return Forms::CallUnaryAs<_type>(Function, yforward(args)...); 336 | } 337 | }; 338 | 339 | 340 | template 341 | struct BinaryExpansion 342 | : private ystdex::equality_comparable> 343 | { 344 | _func Function; 345 | 346 | template)> 348 | BinaryExpansion(_tParams&&... args) 349 | : Function(yforward(args)...) 350 | {} 351 | 352 | YB_ATTR_nodiscard YB_PURE friend bool 353 | operator==(const BinaryExpansion& x, const BinaryExpansion& y) 354 | { 355 | return ystdex::examiners::equal_examiner::are_equal(x.Function, 356 | y.Function); 357 | } 358 | 359 | template 360 | inline ReductionStatus 361 | operator()(_tParams&&... args) const 362 | { 363 | return Forms::CallBinary(Function, yforward(args)...); 364 | } 365 | }; 366 | 367 | 368 | template 369 | struct BinaryAsExpansion : private 370 | ystdex::equality_comparable> 371 | { 372 | _func Function; 373 | 374 | template)> 376 | BinaryAsExpansion(_tParams&&... args) 377 | : Function(yforward(args)...) 378 | {} 379 | 380 | YB_ATTR_nodiscard YB_PURE friend bool 381 | operator==(const BinaryAsExpansion& x, const BinaryAsExpansion& y) 382 | { 383 | return ystdex::examiners::equal_examiner::are_equal(x.Function, 384 | y.Function); 385 | } 386 | 387 | template 388 | inline ReductionStatus 389 | operator()(_tParams&&... args) const 390 | { 391 | return Forms::CallBinaryAs<_type, _type2>(Function, yforward(args)...); 392 | } 393 | }; 394 | 395 | 396 | template 397 | inline void 398 | RegisterUnary(_tTarget& target, string_view name, _func f) 399 | { 400 | Unilang::RegisterFormHandler(target, name, 401 | UnaryExpansion<_func>(std::move(f)), ystdex::size_t_<_vWrapping>()); 402 | } 403 | template 405 | inline void 406 | RegisterUnary(_tTarget& target, string_view name, _func f) 407 | { 408 | Unilang::RegisterFormHandler(target, name, UnaryAsExpansion<_type, _func>( 409 | std::move(f)), ystdex::size_t_<_vWrapping>()); 410 | } 411 | 412 | template 413 | inline void 414 | RegisterBinary(_tTarget& target, string_view name, _func f) 415 | { 416 | Unilang::RegisterFormHandler(target, name, 417 | BinaryExpansion<_func>(std::move(f)), ystdex::size_t_<_vWrapping>()); 418 | } 419 | template 421 | inline void 422 | RegisterBinary(_tTarget& target, string_view name, _func f) 423 | { 424 | Unilang::RegisterFormHandler(target, name, BinaryAsExpansion<_type, _type2, 425 | _func>(std::move(f)), ystdex::size_t_<_vWrapping>()); 426 | } 427 | 428 | 429 | void 430 | Eq(TermNode&); 431 | 432 | void 433 | EqLeaf(TermNode&); 434 | 435 | void 436 | EqValue(TermNode&); 437 | 438 | 439 | ReductionStatus 440 | If(TermNode&, Context&); 441 | 442 | 443 | ReductionStatus 444 | Cons(TermNode&); 445 | 446 | ReductionStatus 447 | ConsRef(TermNode&); 448 | 449 | void 450 | SetRest(TermNode&); 451 | 452 | void 453 | SetRestRef(TermNode&); 454 | 455 | 456 | ReductionStatus 457 | Eval(TermNode&, Context&); 458 | 459 | ReductionStatus 460 | EvalAt(TermNode&, Context&); 461 | 462 | ReductionStatus 463 | EvalRef(TermNode&, Context&); 464 | 465 | ReductionStatus 466 | EvalString(TermNode&, Context&); 467 | 468 | ReductionStatus 469 | EvalStringRef(TermNode&, Context&); 470 | 471 | 472 | void 473 | MakeEnvironment(TermNode&); 474 | 475 | void 476 | GetCurrentEnvironment(TermNode&, Context&); 477 | 478 | 479 | ReductionStatus 480 | Define(TermNode&, Context&); 481 | 482 | 483 | ReductionStatus 484 | Vau(TermNode&, Context&); 485 | 486 | ReductionStatus 487 | VauRef(TermNode&, Context&); 488 | 489 | ReductionStatus 490 | VauWithEnvironment(TermNode&, Context&); 491 | 492 | ReductionStatus 493 | VauWithEnvironmentRef(TermNode&, Context&); 494 | 495 | 496 | ReductionStatus 497 | Wrap(TermNode&); 498 | 499 | ReductionStatus 500 | WrapRef(TermNode&); 501 | 502 | ReductionStatus 503 | Unwrap(TermNode&); 504 | 505 | 506 | ReductionStatus 507 | CheckListReference(TermNode&); 508 | 509 | ReductionStatus 510 | CheckPairReference(TermNode&); 511 | 512 | 513 | ReductionStatus 514 | MakeEncapsulationType(TermNode& term); 515 | 516 | 517 | ReductionStatus 518 | Sequence(TermNode&, Context&); 519 | 520 | 521 | ReductionStatus 522 | Call1CC(TermNode&, Context&); 523 | 524 | ReductionStatus 525 | ContinuationToApplicative(TermNode&); 526 | 527 | } // namespace Forms; 528 | 529 | } // namespace Unilang; 530 | 531 | #endif 532 | 533 | -------------------------------------------------------------------------------- /include/Interpreter.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020-2021 UnionTech Software Technology Co.,Ltd. 2 | 3 | #ifndef INC_Unilang_Interpreter_h_ 4 | #define INC_Unilang_Interpreter_h_ 1 5 | 6 | #include "Context.h" // for pair, lref, stack, vector, GlobalState, string, 7 | // shared_ptr, Environment, Context, TermNode, 8 | // YSLib::Logger, YSLib::unique_ptr, std::istream; 9 | #include // for std::getenv; 10 | #include // for std::ostream; 11 | 12 | namespace Unilang 13 | { 14 | 15 | class Interpreter final 16 | { 17 | public: 18 | bool Echo = std::getenv("ECHO"); 19 | bool UseSourceLocation = !std::getenv("UNILANG_NO_SRCINFO"); 20 | 21 | private: 22 | string line{}; 23 | shared_ptr p_ground{}; 24 | 25 | public: 26 | GlobalState Global{}; 27 | Context Main{Global}; 28 | TermNode Term{Global.Allocator}; 29 | 30 | Interpreter(); 31 | Interpreter(const Interpreter&) = delete; 32 | 33 | void 34 | Evaluate(TermNode&); 35 | 36 | private: 37 | ReductionStatus 38 | ExecuteOnce(Context&); 39 | 40 | ReductionStatus 41 | ExecuteString(string_view, Context&); 42 | 43 | public: 44 | ReductionStatus 45 | Exit(); 46 | 47 | private: 48 | void 49 | HandleWithTrace(std::exception_ptr, Context&, 50 | Context::ReducerSequence::const_iterator); 51 | 52 | public: 53 | static YSLib::unique_ptr 54 | OpenUnique(Context&, string); 55 | 56 | TermNode 57 | Perform(string_view); 58 | 59 | void 60 | PrepareExecution(Context&); 61 | 62 | static void 63 | Print(const TermNode&); 64 | 65 | YB_ATTR_nodiscard TermNode 66 | Read(string_view); 67 | 68 | void 69 | Run(); 70 | 71 | void 72 | RunLine(string_view); 73 | 74 | private: 75 | ReductionStatus 76 | RunLoop(Context&); 77 | 78 | public: 79 | void 80 | RunScript(string); 81 | 82 | bool 83 | SaveGround(); 84 | 85 | std::istream& 86 | WaitForLine(); 87 | }; 88 | 89 | 90 | void 91 | DisplayTermValue(std::ostream&, const TermNode&); 92 | 93 | void 94 | PrintTermNode(std::ostream&, const TermNode&); 95 | 96 | void 97 | WriteTermValue(std::ostream&, const TermNode&); 98 | 99 | } // namespace Unilang; 100 | 101 | #endif 102 | 103 | -------------------------------------------------------------------------------- /include/JIT.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021-2022 UnionTech Software Technology Co.,Ltd. 2 | 3 | #ifndef INC_Unilang_JIT_h_ 4 | #define INC_Unilang_JIT_h_ 1 5 | 6 | #include "Context.h" // for Context; 7 | #include YFM_YSLib_Adaptor_YAdaptor // for YSLib::make_unique, 8 | // YSLib::unique_ptr, YSLib::to_pmr_string, YSLib::to_std_string; 9 | #include // for std::int8_t; 10 | 11 | namespace Unilang 12 | { 13 | 14 | using YSLib::make_unique; 15 | using YSLib::unique_ptr; 16 | 17 | using YSLib::to_pmr_string; 18 | using YSLib::to_std_string; 19 | 20 | inline namespace JITTypes 21 | { 22 | // NOTE: As of LLVM 12, LLVM has not built-in 'void*' type, and opaque types 23 | // are not ready. The documentation suggests to use 'i8*' instead, see 24 | // https://llvm.org/docs/LangRef.html#pointer-type. It should be safe in 25 | // the sense of TBAA, while the type is unknown by the LLVM optimization 26 | // passes. 27 | using HostPtr = std::int8_t*; 28 | 29 | using InteropFuncPtr = ReductionStatus(*)(HostPtr); 30 | 31 | } // inline namespace JITTypes; 32 | 33 | void 34 | SetupJIT(Context&); 35 | 36 | void 37 | JITMain(); 38 | 39 | } // namespace Unilang; 40 | 41 | #endif 42 | 43 | -------------------------------------------------------------------------------- /include/Lexical.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020-2022 UnionTech Software Technology Co.,Ltd. 2 | 3 | #ifndef INC_Unilang_Lexical_h_ 4 | #define INC_Unilang_Lexical_h_ 1 5 | 6 | #include "Unilang.h" // for shared_ptr, pair, string_view; 7 | #include // for assert; 8 | #include // for ystdex::isspace; 9 | 10 | namespace Unilang 11 | { 12 | 13 | using SourceName = shared_ptr; 14 | 15 | 16 | struct SourceLocation final 17 | { 18 | size_t Line = 0; 19 | size_t Column = 0; 20 | 21 | SourceLocation() = default; 22 | SourceLocation(size_t line, size_t col) 23 | : Line(line), Column(col) 24 | {} 25 | 26 | void 27 | Newline() noexcept 28 | { 29 | yunseq(++Line, Column = 0); 30 | } 31 | 32 | void 33 | Step() noexcept 34 | { 35 | ++Column; 36 | } 37 | }; 38 | 39 | 40 | using SourceInformation = pair; 41 | 42 | 43 | class UnescapeContext final 44 | { 45 | public: 46 | size_t Start = size_t(-1); 47 | size_t Length = 0; 48 | 49 | YB_ATTR_nodiscard bool 50 | IsHandling() const noexcept 51 | { 52 | return Length != 0; 53 | } 54 | 55 | void 56 | Clear() noexcept 57 | { 58 | Length = 0; 59 | } 60 | 61 | void 62 | VerifyBufferLength(size_t len) const 63 | { 64 | yunused(len); 65 | assert((Length == 0 || Start + Length <= len) 66 | && "Invalid unescaping state found."); 67 | } 68 | }; 69 | 70 | 71 | YB_ATTR_nodiscard bool 72 | HandleBackslashPrefix(string_view, UnescapeContext&); 73 | 74 | YB_ATTR_nodiscard bool 75 | Unescape(string&, char, UnescapeContext&, char); 76 | 77 | 78 | class LexicalAnalyzer final 79 | { 80 | private: 81 | UnescapeContext unescape_context{}; 82 | char ld = {}; 83 | 84 | public: 85 | YB_ATTR_nodiscard char 86 | GetDelimiter() const noexcept 87 | { 88 | return ld; 89 | } 90 | YB_ATTR_nodiscard const UnescapeContext& 91 | GetUnescapeContext() const noexcept 92 | { 93 | return unescape_context; 94 | } 95 | 96 | template 99 | YB_ATTR_nodiscard bool 100 | FilterChar(char c, _tCharBuffer& cbuf, _fUnescape unescape = Unescape, 101 | _fPrefixHandler handle_prefix = HandleBackslashPrefix) 102 | { 103 | if(!unescape_context.IsHandling()) 104 | { 105 | cbuf += c; 106 | return !handle_prefix(cbuf, unescape_context); 107 | } 108 | unescape(cbuf, c, unescape_context, ld); 109 | return {}; 110 | } 111 | 112 | bool 113 | UpdateBack(char&, char); 114 | }; 115 | 116 | 117 | YB_ATTR_nodiscard YB_PURE char 118 | CheckLiteral(string_view) noexcept; 119 | 120 | YB_ATTR_nodiscard YB_PURE inline string_view 121 | DeliteralizeUnchecked(string_view sv) noexcept 122 | { 123 | assert(sv.data()); 124 | assert(!(sv.size() < 2)); 125 | return sv.substr(1, sv.size() - 2); 126 | } 127 | 128 | YB_ATTR_nodiscard YB_PURE inline string_view 129 | Deliteralize(string_view sv) noexcept 130 | { 131 | return CheckLiteral(sv) != char() ? DeliteralizeUnchecked(sv) : sv; 132 | } 133 | 134 | YB_ATTR_nodiscard YB_PURE string 135 | Escape(string_view); 136 | 137 | YB_ATTR_nodiscard YB_PURE string 138 | EscapeLiteral(string_view); 139 | 140 | YB_ATTR_nodiscard YB_STATELESS constexpr bool 141 | IsGraphicalDelimiter(char c) noexcept 142 | { 143 | return c == '(' || c == ')' || c == ',' || c == ';' 144 | || c == '[' || c == ']' || c == '{' || c == '}'; 145 | } 146 | 147 | YB_ATTR_nodiscard constexpr bool 148 | IsDelimiter(char c) noexcept 149 | { 150 | return ystdex::isspace(c) || IsGraphicalDelimiter(c); 151 | } 152 | 153 | 154 | enum class LexemeCategory 155 | { 156 | Symbol, 157 | Code, 158 | Data, 159 | Extended 160 | }; 161 | 162 | 163 | YB_ATTR_nodiscard YB_PURE LexemeCategory 164 | CategorizeBasicLexeme(string_view) noexcept; 165 | 166 | YB_ATTR_nodiscard YB_PURE inline bool 167 | IsUnilangSymbol(string_view id) noexcept 168 | { 169 | return CategorizeBasicLexeme(id) == LexemeCategory::Symbol; 170 | } 171 | 172 | 173 | YB_NORETURN void 174 | ThrowMismatchBoundaryToken(char, char); 175 | 176 | } // namespace Unilang; 177 | 178 | #endif 179 | 180 | -------------------------------------------------------------------------------- /include/Math.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021, 2023 UnionTech Software Technology Co.,Ltd. 2 | 3 | #ifndef INC_Unilang_Math_h_ 4 | #define INC_Unilang_Math_h_ 1 5 | 6 | #include "TermAccess.h" // for ValueObject, TypedValueAccessor, 7 | // Unilang::ResolveTerm, ThrowTypeErrorForInvalidType; 8 | #include "Context.h" // for ReductionStatus; 9 | 10 | namespace Unilang 11 | { 12 | 13 | inline namespace Math 14 | { 15 | 16 | struct NumberLeaf 17 | {}; 18 | 19 | struct NumberNode 20 | {}; 21 | 22 | 23 | YB_ATTR_nodiscard YB_PURE bool 24 | IsExactValue(const ValueObject&) noexcept; 25 | 26 | YB_ATTR_nodiscard YB_PURE bool 27 | IsInexactValue(const ValueObject&) noexcept; 28 | 29 | YB_ATTR_nodiscard YB_PURE inline bool 30 | IsNumberValue(const ValueObject& vo) noexcept 31 | { 32 | return IsExactValue(vo) || IsInexactValue(vo); 33 | } 34 | 35 | YB_ATTR_nodiscard YB_PURE bool 36 | IsRationalValue(const ValueObject&) noexcept; 37 | 38 | YB_ATTR_nodiscard YB_PURE bool 39 | IsIntegerValue(const ValueObject&) noexcept; 40 | 41 | 42 | YB_ATTR_nodiscard YB_PURE bool 43 | IsFinite(const ValueObject&) noexcept; 44 | 45 | YB_ATTR_nodiscard YB_PURE bool 46 | IsInfinite(const ValueObject&) noexcept; 47 | 48 | YB_ATTR_nodiscard YB_PURE bool 49 | IsNaN(const ValueObject&) noexcept; 50 | 51 | 52 | YB_ATTR_nodiscard YB_PURE bool 53 | Equal(const ValueObject&, const ValueObject&) noexcept; 54 | 55 | YB_ATTR_nodiscard YB_PURE bool 56 | Less(const ValueObject&, const ValueObject&) noexcept; 57 | 58 | YB_ATTR_nodiscard YB_PURE bool 59 | Greater(const ValueObject&, const ValueObject&) noexcept; 60 | 61 | YB_ATTR_nodiscard YB_PURE bool 62 | LessEqual(const ValueObject&, const ValueObject&) noexcept; 63 | 64 | YB_ATTR_nodiscard YB_PURE bool 65 | GreaterEqual(const ValueObject&, const ValueObject&) noexcept; 66 | 67 | 68 | YB_ATTR_nodiscard YB_PURE bool 69 | IsZero(const ValueObject&) noexcept; 70 | 71 | YB_ATTR_nodiscard YB_PURE bool 72 | IsPositive(const ValueObject&) noexcept; 73 | 74 | YB_ATTR_nodiscard YB_PURE bool 75 | IsNegative(const ValueObject&) noexcept; 76 | 77 | YB_ATTR_nodiscard YB_PURE bool 78 | IsOdd(const ValueObject&) noexcept; 79 | 80 | YB_ATTR_nodiscard YB_PURE bool 81 | IsEven(const ValueObject&) noexcept; 82 | 83 | 84 | YB_ATTR_nodiscard YB_PURE ValueObject 85 | Max(ResolvedArg<>&&, ResolvedArg<>&&); 86 | 87 | YB_ATTR_nodiscard YB_PURE ValueObject 88 | Min(ResolvedArg<>&&, ResolvedArg<>&&); 89 | 90 | YB_ATTR_nodiscard YB_PURE ValueObject 91 | Add1(ResolvedArg<>&&); 92 | 93 | YB_ATTR_nodiscard YB_PURE ValueObject 94 | Sub1(ResolvedArg<>&&); 95 | 96 | YB_ATTR_nodiscard YB_PURE ValueObject 97 | Plus(ResolvedArg<>&&, ResolvedArg<>&&); 98 | 99 | YB_ATTR_nodiscard YB_PURE ValueObject 100 | Minus(ResolvedArg<>&&, ResolvedArg<>&&); 101 | 102 | YB_ATTR_nodiscard YB_PURE ValueObject 103 | Multiplies(ResolvedArg<>&&, ResolvedArg<>&&); 104 | 105 | YB_ATTR_nodiscard YB_PURE ValueObject 106 | Divides(ResolvedArg<>&&, ResolvedArg<>&&); 107 | 108 | YB_ATTR_nodiscard YB_PURE ValueObject 109 | Abs(ResolvedArg<>&&); 110 | 111 | YB_ATTR_nodiscard YB_PURE array 112 | FloorDivides(ResolvedArg<>&&, ResolvedArg<>&&); 113 | 114 | YB_ATTR_nodiscard YB_PURE ValueObject 115 | FloorQuotient(ResolvedArg<>&&, ResolvedArg<>&&); 116 | 117 | YB_ATTR_nodiscard YB_PURE ValueObject 118 | FloorRemainder(ResolvedArg<>&&, ResolvedArg<>&&); 119 | 120 | YB_ATTR_nodiscard YB_PURE array 121 | TruncateDivides(ResolvedArg<>&&, ResolvedArg<>&&); 122 | 123 | YB_ATTR_nodiscard YB_PURE ValueObject 124 | TruncateQuotient(ResolvedArg<>&&, ResolvedArg<>&&); 125 | 126 | YB_ATTR_nodiscard YB_PURE ValueObject 127 | TruncateRemainder(ResolvedArg<>&&, ResolvedArg<>&&); 128 | 129 | YB_ATTR_nodiscard YB_PURE ValueObject 130 | Inexact(ResolvedArg<>&&); 131 | 132 | 133 | YB_ATTR_nodiscard YB_PURE ValueObject 134 | StringToNumber(const string&) noexcept; 135 | 136 | YB_ATTR_nodiscard YB_PURE string 137 | NumberToString(const ResolvedArg<>&); 138 | 139 | } // inline namespace Math; 140 | 141 | void 142 | ReadDecimal(ValueObject&, string_view, string_view::const_iterator); 143 | 144 | ReductionStatus 145 | ReadNumber(ValueObject&, string_view); 146 | 147 | 148 | // XXX: These functions are known problematic with non-default locales. 149 | YB_ATTR_nodiscard YB_PURE string 150 | FPToString(float, string::allocator_type = {}); 151 | YB_ATTR_nodiscard YB_PURE string 152 | FPToString(double, string::allocator_type = {}); 153 | YB_ATTR_nodiscard YB_PURE string 154 | FPToString(long double, string::allocator_type = {}); 155 | 156 | YB_ATTR_nodiscard YB_PURE string 157 | NumberValueToString(const ValueObject&, string::allocator_type = {}); 158 | 159 | 160 | template<> 161 | struct TypedValueAccessor 162 | { 163 | template 164 | YB_ATTR_nodiscard YB_PURE inline auto 165 | operator()(_tTerm& term) const -> yimpl(decltype((term.Value))) 166 | { 167 | return Unilang::ResolveTerm( 168 | [](_tTerm& nd, bool has_ref) -> yimpl(decltype((term.Value))){ 169 | if(IsLeaf(nd)) 170 | { 171 | if(IsNumberValue(nd.Value)) 172 | return nd.Value; 173 | ThrowTypeErrorForInvalidType("number", nd, has_ref); 174 | } 175 | ThrowListTypeErrorForInvalidType("number", nd, has_ref); 176 | }, term); 177 | } 178 | }; 179 | 180 | template<> 181 | struct TypedValueAccessor 182 | { 183 | template 184 | YB_ATTR_nodiscard YB_PURE inline ResolvedArg<> 185 | operator()(_tTerm& term) const 186 | { 187 | return Unilang::ResolveTerm( 188 | [](_tTerm& nd, ResolvedTermReferencePtr p_ref) -> ResolvedArg<>{ 189 | if(IsLeaf(nd)) 190 | { 191 | if(IsNumberValue(nd.Value)) 192 | return {nd, p_ref}; 193 | ThrowTypeErrorForInvalidType("number", nd, p_ref); 194 | } 195 | ThrowListTypeErrorForInvalidType("number", nd, p_ref); 196 | }, term); 197 | } 198 | }; 199 | 200 | } // namespace Unilang; 201 | 202 | #endif 203 | 204 | -------------------------------------------------------------------------------- /include/Parser.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020-2022 UnionTech Software Technology Co.,Ltd. 2 | 3 | #ifndef INC_Unilang_Parser_h_ 4 | #define INC_Unilang_Parser_h_ 1 5 | 6 | #include "Lexical.h" // for lref, LexicalAnalyzer, pmr::polymorphic_allocator, 7 | // string, pmr, std::swap, vector; 8 | #include // for ystdex::remove_reference_t; 9 | #include // for ystdex::unwrap_ref_decay_t; 10 | #include // for ystdex::detected_or_t, 11 | // ystdex::enable_if_convertible_t; 12 | 13 | namespace Unilang 14 | { 15 | 16 | template 17 | using MemberParseResult = typename _tParser::ParseResult; 18 | 19 | template 20 | using ParserClassOf 21 | = ystdex::remove_reference_t>; 22 | 23 | template 24 | using ParseResultOf = ystdex::detected_or_t, MemberParseResult, 25 | ParserClassOf<_fParse>>; 26 | 27 | template 28 | using GParserResult = const ParseResultOf<_fParse>&; 29 | 30 | 31 | class BufferedByteParserBase 32 | { 33 | private: 34 | lref lexer_ref; 35 | string buffer{}; 36 | 37 | public: 38 | BufferedByteParserBase(LexicalAnalyzer& lexer, 39 | pmr::polymorphic_allocator a = {}) 40 | : lexer_ref(lexer), buffer(a) 41 | {} 42 | 43 | YB_ATTR_nodiscard YB_PURE const string& 44 | GetBuffer() const noexcept 45 | { 46 | return buffer; 47 | } 48 | YB_ATTR_nodiscard YB_PURE string& 49 | GetBufferRef() noexcept 50 | { 51 | return buffer; 52 | } 53 | YB_ATTR_nodiscard YB_PURE LexicalAnalyzer& 54 | GetLexerRef() const noexcept 55 | { 56 | return lexer_ref; 57 | } 58 | YB_ATTR_nodiscard YB_PURE char& 59 | GetBackRef() noexcept 60 | { 61 | return buffer.back(); 62 | } 63 | 64 | size_t 65 | QueryLastDelimited(char ld) const noexcept 66 | { 67 | return buffer.length() - (ld != char() ? 1 : 0); 68 | } 69 | 70 | void 71 | reserve(size_t n) 72 | { 73 | buffer.reserve(n); 74 | } 75 | 76 | friend void 77 | swap(BufferedByteParserBase& x, BufferedByteParserBase& y) noexcept 78 | { 79 | std::swap(x.lexer_ref, y.lexer_ref); 80 | std::swap(x.buffer, y.buffer); 81 | } 82 | }; 83 | 84 | 85 | class ByteParser : private BufferedByteParserBase 86 | { 87 | public: 88 | using ParseResult = vector; 89 | 90 | private: 91 | mutable ParseResult lexemes{}; 92 | bool update_current = {}; 93 | 94 | public: 95 | ByteParser(LexicalAnalyzer& lexer, 96 | pmr::polymorphic_allocator a = {}) 97 | : BufferedByteParserBase(lexer, a), lexemes(a) 98 | {} 99 | ByteParser(const ByteParser& parse) 100 | : BufferedByteParserBase(parse), 101 | lexemes(parse.lexemes, parse.lexemes.get_allocator()), 102 | update_current(parse.update_current) 103 | {} 104 | ByteParser(ByteParser&&) = default; 105 | 106 | ByteParser& 107 | operator=(const ByteParser& parse) 108 | { 109 | return ystdex::copy_and_swap(*this, parse); 110 | } 111 | ByteParser& 112 | operator=(ByteParser&&) = default; 113 | 114 | template 115 | inline void 116 | operator()(char c, _tParams&&... args) 117 | { 118 | auto& lexer(GetLexerRef()); 119 | 120 | Update(lexer.FilterChar(c, GetBufferRef(), yforward(args)...) 121 | && lexer.UpdateBack(GetBackRef(), c)); 122 | } 123 | 124 | bool 125 | IsUpdating() const noexcept 126 | { 127 | return update_current; 128 | } 129 | 130 | using BufferedByteParserBase::GetBuffer; 131 | using BufferedByteParserBase::GetBufferRef; 132 | using BufferedByteParserBase::GetLexerRef; 133 | 134 | const ParseResult& 135 | GetResult() const noexcept 136 | { 137 | return lexemes; 138 | } 139 | 140 | private: 141 | void 142 | Update(bool); 143 | 144 | public: 145 | using BufferedByteParserBase::reserve; 146 | 147 | friend void 148 | swap(ByteParser& x, ByteParser& y) 149 | { 150 | swap(static_cast(x), 151 | static_cast(y)); 152 | swap(x.lexemes, y.lexemes); 153 | std::swap(x.update_current, y.update_current); 154 | } 155 | }; 156 | 157 | 158 | class SourcedByteParser : private BufferedByteParserBase 159 | { 160 | public: 161 | using ParseResult = vector>; 162 | 163 | private: 164 | mutable ParseResult lexemes{}; 165 | bool update_current = {}; 166 | SourceLocation source_location{0, 0}; 167 | 168 | public: 169 | SourcedByteParser(LexicalAnalyzer& lexer, 170 | pmr::polymorphic_allocator a = {}) 171 | : BufferedByteParserBase(lexer, a), lexemes(a) 172 | {} 173 | SourcedByteParser(const SourcedByteParser& parse) 174 | : BufferedByteParserBase(parse), 175 | lexemes(parse.lexemes, parse.lexemes.get_allocator()), 176 | update_current(parse.update_current), 177 | source_location(parse.source_location) 178 | {} 179 | SourcedByteParser(SourcedByteParser&&) = default; 180 | 181 | SourcedByteParser& 182 | operator=(const SourcedByteParser& parse) 183 | { 184 | return ystdex::copy_and_swap(*this, parse); 185 | } 186 | SourcedByteParser& 187 | operator=(SourcedByteParser&&) = default; 188 | 189 | template 190 | inline void 191 | operator()(char c, _tParams&&... args) 192 | { 193 | auto& lexer(GetLexerRef()); 194 | 195 | Update(lexer.FilterChar(c, GetBufferRef(), yforward(args)...) 196 | && lexer.UpdateBack(GetBackRef(), c)); 197 | if(c != '\n') 198 | source_location.Step(); 199 | else 200 | source_location.Newline(); 201 | } 202 | 203 | bool 204 | IsUpdating() const noexcept 205 | { 206 | return update_current; 207 | } 208 | 209 | using BufferedByteParserBase::GetBuffer; 210 | using BufferedByteParserBase::GetBufferRef; 211 | using BufferedByteParserBase::GetLexerRef; 212 | const ParseResult& 213 | GetResult() const noexcept 214 | { 215 | return lexemes; 216 | } 217 | const SourceLocation& 218 | GetSourceLocation() const noexcept 219 | { 220 | return source_location; 221 | } 222 | 223 | private: 224 | void 225 | Update(bool); 226 | 227 | public: 228 | using BufferedByteParserBase::reserve; 229 | 230 | friend void 231 | swap(SourcedByteParser& x, SourcedByteParser& y) 232 | { 233 | swap(static_cast(x), 234 | static_cast(y)); 235 | swap(x.lexemes, y.lexemes); 236 | std::swap(x.update_current, y.update_current); 237 | std::swap(x.source_location, y.source_location); 238 | } 239 | }; 240 | 241 | 242 | template)> 244 | YB_ATTR_nodiscard YB_STATELESS const _type& 245 | ToLexeme(const _type& val) noexcept 246 | { 247 | return val; 248 | } 249 | YB_ATTR_nodiscard YB_PURE inline const string& 250 | ToLexeme(const SourcedByteParser::ParseResult::value_type& val) noexcept 251 | { 252 | return val.second; 253 | } 254 | 255 | } // namespace Unilang; 256 | 257 | #endif 258 | 259 | -------------------------------------------------------------------------------- /include/Syntax.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020-2022 UnionTech Software Technology Co.,Ltd. 2 | 3 | #ifndef INC_Unilang_Syntax_h_ 4 | #define INC_Unilang_Syntax_h_ 1 5 | 6 | #include "Parser.h" // for ToLexeme, ThrowMismatchBoundaryToken; 7 | #include "TermNode.h" // for TermNode, stack; 8 | #include "Exception.h" // for UnilangException; 9 | #include // for assert; 10 | #include // for ystdex::remove_reference_t; 11 | 12 | namespace Unilang 13 | { 14 | 15 | struct LexemeTokenizer final 16 | { 17 | TermNode::allocator_type Allocator; 18 | 19 | LexemeTokenizer(TermNode::allocator_type a = {}) 20 | : Allocator(a) 21 | {} 22 | 23 | template 24 | YB_ATTR_nodiscard YB_PURE inline TermNode 25 | operator()(const _type& val) const 26 | { 27 | return Unilang::AsTermNode(Allocator, std::allocator_arg, Allocator, 28 | ToLexeme(val)); 29 | } 30 | }; 31 | 32 | 33 | template 34 | YB_ATTR_nodiscard inline _tIn 35 | ReduceSyntax(TermNode& term, _tIn first, _tIn last) 36 | { 37 | return ReduceSyntax(term, first, last, 38 | LexemeTokenizer{term.get_allocator()}); 39 | } 40 | template 41 | YB_ATTR_nodiscard _tIn 42 | ReduceSyntax(TermNode& term, _tIn first, _tIn last, _fTokenize tokenize) 43 | { 44 | const auto a(term.get_allocator()); 45 | stack tms(a); 46 | struct Guard final 47 | { 48 | TermNode& term; 49 | stack& tms; 50 | 51 | ~Guard() 52 | { 53 | assert(!tms.empty()); 54 | term = std::move(tms.top()); 55 | } 56 | } gd{term, tms}; 57 | 58 | tms.push(std::move(term)); 59 | for(; first != last; ++first) 60 | { 61 | const auto& val(*first); 62 | const auto& lexeme(ToLexeme(val)); 63 | 64 | if(lexeme == "(" || lexeme == "[" || lexeme == "{") 65 | { 66 | tms.push(AsTermNode(a)); 67 | tms.top().Value = lexeme; 68 | } 69 | else if(!(lexeme == ")" || lexeme == "]" || lexeme == "}")) 70 | { 71 | assert(!tms.empty()); 72 | tms.top().Add(tokenize(val)); 73 | } 74 | else if(tms.size() != 1) 75 | { 76 | auto tm(std::move(tms.top())); 77 | 78 | tms.pop(); 79 | 80 | const auto& ltok(tm.Value.GetObject< 81 | ystdex::remove_reference_t>()); 82 | 83 | if(lexeme == ")" && ltok != "(") 84 | ThrowMismatchBoundaryToken('(', ')'); 85 | if(lexeme == "]" && ltok != "[") 86 | ThrowMismatchBoundaryToken('[', ']'); 87 | if(lexeme == "}" && ltok != "{") 88 | ThrowMismatchBoundaryToken('{', '}'); 89 | tm.Value.Clear(); 90 | assert(!tms.empty()); 91 | tms.top().Add(std::move(tm)); 92 | } 93 | else 94 | return first; 95 | } 96 | if(tms.size() == 1) 97 | return first; 98 | throw UnilangException("Redundant '(', '[' or '{' found."); 99 | } 100 | 101 | } // namespace Unilang; 102 | 103 | #endif 104 | 105 | -------------------------------------------------------------------------------- /include/TCO.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021-2023 UnionTech Software Technology Co.,Ltd. 2 | 3 | #ifndef INC_Unilang_TCO_h_ 4 | #define INC_Unilang_TCO_h_ 1 5 | 6 | #include "Lexical.h" // for SourceName; 7 | #include "Evaluation.h" // for shared_ptr, Context, YSLib::allocate_shared, 8 | // Unilang::Deref, UnilangException, unordered_map, lref, Environment, size_t, set, 9 | // weak_ptr, EnvironmentParent, IsTyped, pair, list, TermNode, 10 | // EnvironmentGuard, ReductionStatus, NameTypedContextHandler; 11 | #include // for ystdex::get_hash; 12 | #include // for ystdex::bind1, std::ref, std::placeholder; 13 | #include // for std::tuple, std::get; 14 | #include // for ystdex::guard, 15 | // ystdex::make_unique_guard; 16 | #include // for ystdex::optional; 17 | #include // for assert; 18 | 19 | namespace Unilang 20 | { 21 | 22 | class SourceNameRecoverer final 23 | { 24 | private: 25 | SourceName* p_current{}; 26 | SourceName Saved{}; 27 | 28 | public: 29 | SourceNameRecoverer() = default; 30 | SourceNameRecoverer(SourceName& cur, SourceName name) noexcept 31 | : p_current(&cur), Saved(name) 32 | {} 33 | SourceNameRecoverer(const SourceNameRecoverer&) = default; 34 | SourceNameRecoverer(SourceNameRecoverer&&) = default; 35 | 36 | SourceNameRecoverer& 37 | operator=(const SourceNameRecoverer&) = default; 38 | SourceNameRecoverer& 39 | operator=(SourceNameRecoverer&&) = default; 40 | 41 | void 42 | operator()() const noexcept 43 | { 44 | if(p_current) 45 | *p_current = std::move(Saved); 46 | } 47 | }; 48 | 49 | 50 | class OneShotChecker final 51 | { 52 | private: 53 | shared_ptr p_shot; 54 | 55 | public: 56 | OneShotChecker(Context& ctx) 57 | : p_shot(YSLib::allocate_shared(ctx.get_allocator())) 58 | {} 59 | 60 | void 61 | operator()() const noexcept 62 | { 63 | if(p_shot) 64 | Unilang::Deref(p_shot) = true; 65 | } 66 | 67 | void 68 | Check() const 69 | { 70 | auto& shot(Unilang::Deref(p_shot)); 71 | 72 | if(!shot) 73 | shot = true; 74 | else 75 | throw UnilangException("One-shot continuation expired."); 76 | } 77 | }; 78 | 79 | 80 | struct RecordCompressor final 81 | { 82 | using RecordInfo = unordered_map, size_t, 83 | ystdex::get_hash<>, ystdex::get_equal_to<>>; 84 | using ReferenceSet = unordered_set, 85 | ystdex::get_hash<>, ystdex::get_equal_to<>>; 86 | 87 | weak_ptr RootPtr; 88 | ReferenceSet Reachable, NewlyReachable; 89 | RecordInfo Universe; 90 | 91 | RecordCompressor(const shared_ptr&); 92 | RecordCompressor(const shared_ptr&, 93 | Environment::allocator_type); 94 | 95 | void 96 | AddParents(Environment&); 97 | 98 | void 99 | Compress(); 100 | 101 | YB_ATTR_nodiscard YB_PURE static size_t 102 | CountReferences(const shared_ptr&) noexcept; 103 | 104 | YB_ATTR_nodiscard YB_PURE static size_t 105 | CountStrong(const shared_ptr&) noexcept; 106 | 107 | template 108 | static void 109 | Traverse(Environment& e, EnvironmentParent& parent, const _fTracer& trace) 110 | { 111 | const auto p_poly(&parent.GetObject()); 112 | 113 | if(const auto p_single_weak 114 | = dynamic_cast(p_poly)) 115 | { 116 | if(auto p = p_single_weak->Get().Lock()) 117 | TraverseForSharedPtr(e, parent, trace, p); 118 | } 119 | else if(const auto p_single_strong 120 | = dynamic_cast(p_poly)) 121 | { 122 | if(auto p = p_single_strong->Get()) 123 | TraverseForSharedPtr(e, parent, trace, p); 124 | } 125 | else if(const auto p_parent_list 126 | = dynamic_cast(p_poly)) 127 | { 128 | for(auto& vo : p_parent_list->GetRef()) 129 | Traverse(e, vo, trace); 130 | } 131 | } 132 | 133 | private: 134 | template 135 | static void 136 | TraverseForSharedPtr(Environment& e, EnvironmentParent& parent, 137 | const _fTracer& trace, shared_ptr& p) 138 | { 139 | if(ystdex::expand_proxy&, 140 | Environment&, EnvironmentParent&)>::call(trace, p, e, parent)) 141 | { 142 | auto& dst(Unilang::Deref(p)); 143 | 144 | p.reset(); 145 | Traverse(dst, dst.Parent, trace); 146 | } 147 | } 148 | }; 149 | 150 | 151 | 152 | enum RecordFrameIndex : size_t 153 | { 154 | ActiveEnvironmentPtr, 155 | ActiveCombiner 156 | }; 157 | 158 | using FrameRecord = pair, ValueObject>; 159 | 160 | 161 | class FrameRecordList : public yimpl(YSLib::forward_list) 162 | { 163 | private: 164 | using Base = yimpl(YSLib::forward_list); 165 | 166 | public: 167 | FrameRecordList() noexcept(noexcept(Base())) 168 | : Base() 169 | {} 170 | using Base::Base; 171 | FrameRecordList(const FrameRecordList& l) 172 | : Base(l, l.get_allocator()) 173 | {} 174 | FrameRecordList(FrameRecordList&&) = default; 175 | ~FrameRecordList() 176 | { 177 | clear(); 178 | } 179 | 180 | FrameRecordList& 181 | operator=(const FrameRecordList&) = default; 182 | FrameRecordList& 183 | operator=(FrameRecordList&&) = default; 184 | 185 | void 186 | clear() noexcept 187 | { 188 | while(!empty()) 189 | pop_front(); 190 | } 191 | }; 192 | 193 | 194 | class TCOAction final 195 | { 196 | private: 197 | struct GuardFunction final 198 | { 199 | lref TermRef; 200 | 201 | void 202 | operator()() const noexcept 203 | { 204 | TermRef.get().Clear(); 205 | } 206 | }; 207 | enum ExtraInfoIndex : size_t 208 | { 209 | RecoverSourceName, 210 | CheckOneShot 211 | }; 212 | 213 | using ExtraInfo = std::tuple, 214 | ystdex::optional>>; 215 | 216 | mutable size_t req_lift_result = 0; 217 | mutable FrameRecordList record_list; 218 | mutable EnvironmentGuard env_guard; 219 | mutable decltype(ystdex::make_unique_guard(std::declval())) 220 | term_guard; 221 | mutable ystdex::optional opt_extra_info{}; 222 | 223 | public: 224 | TCOAction(Context&, TermNode&, bool); 225 | TCOAction(const TCOAction&); 226 | TCOAction(TCOAction&&) = default; 227 | 228 | TCOAction& 229 | operator=(TCOAction&&) = default; 230 | 231 | ReductionStatus 232 | operator()(Context&) const; 233 | 234 | YB_ATTR_nodiscard YB_PURE Context& 235 | GetContextRef() const noexcept 236 | { 237 | return env_guard.func.ContextRef; 238 | } 239 | 240 | private: 241 | YB_ATTR_nodiscard ExtraInfo& 242 | GetExtraInfoRef() 243 | { 244 | if(!opt_extra_info) 245 | opt_extra_info.emplace(); 246 | return *opt_extra_info; 247 | } 248 | 249 | public: 250 | FrameRecordList& 251 | GetFrameRecordList() const noexcept 252 | { 253 | return record_list; 254 | } 255 | 256 | TermNode& 257 | GetTermRef() const noexcept 258 | { 259 | return term_guard.func.func.TermRef; 260 | } 261 | 262 | void 263 | AddOperator(ValueObject& op) const 264 | { 265 | record_list.emplace_front(nullptr, std::move(op)); 266 | } 267 | 268 | void 269 | AssertAttached() const noexcept 270 | { 271 | assert(!record_list.empty() && "No entry found in the record list"); 272 | assert(!std::get(record_list.front()) 273 | && "Missing the fresh frame in the record list."); 274 | } 275 | 276 | YB_ATTR_nodiscard ValueObject& 277 | Attach(ValueObject& op) const 278 | { 279 | AddOperator(op); 280 | return std::get(record_list.front()); 281 | } 282 | 283 | void 284 | CompressFrameList(); 285 | 286 | void 287 | CompressForContext(Context& ctx) 288 | { 289 | CompressFrameList(); 290 | RecordCompressor(ctx.GetRecordPtr()).Compress(); 291 | } 292 | 293 | void 294 | CompressForGuard(Context&, EnvironmentGuard&&); 295 | 296 | YB_ATTR_nodiscard OneShotChecker 297 | MakeOneShotChecker() 298 | { 299 | auto& one_shot_guard(std::get(GetExtraInfoRef())); 300 | 301 | if(!one_shot_guard) 302 | one_shot_guard.emplace(env_guard.func.ContextRef.get()); 303 | return one_shot_guard->func; 304 | } 305 | 306 | void 307 | PopTopFrame() const noexcept 308 | { 309 | AssertAttached(); 310 | record_list.pop_front(); 311 | } 312 | 313 | void 314 | ReleaseOneShotGuard() 315 | { 316 | auto& one_shot_guard(std::get(GetExtraInfoRef())); 317 | 318 | assert(one_shot_guard && "One-shot guard is not initialized properly."); 319 | return one_shot_guard.reset(); 320 | } 321 | 322 | void 323 | SaveTailSourceName(SourceName& cur, SourceName name) 324 | { 325 | std::get(GetExtraInfoRef()) 326 | = SourceNameRecoverer(cur, std::move(name)); 327 | } 328 | 329 | void 330 | SetupLift() const; 331 | void 332 | SetupLift(bool lift) const 333 | { 334 | if(lift) 335 | SetupLift(); 336 | } 337 | }; 338 | 339 | YB_ATTR_nodiscard YB_PURE inline TCOAction* 340 | AccessTCOAction(Context& ctx) noexcept 341 | { 342 | return ctx.AccessCurrentAs(); 343 | } 344 | 345 | YB_ATTR_nodiscard TCOAction& 346 | EnsureTCOAction(Context&, TermNode&); 347 | 348 | YB_ATTR_nodiscard inline TCOAction& 349 | RefTCOAction(Context& ctx) 350 | { 351 | return Unilang::Deref(AccessTCOAction(ctx)); 352 | } 353 | 354 | void 355 | SetupTailTCOAction(Context&, TermNode&, bool); 356 | 357 | 358 | TCOAction& 359 | PrepareTCOEvaluation(Context&, TermNode&, EnvironmentGuard&&); 360 | 361 | 362 | inline void 363 | AssertNextTerm(Context& ctx, TermNode& term) 364 | { 365 | yunused(ctx), 366 | yunused(term); 367 | assert(ystdex::ref_eq<>()(term, ctx.GetNextTermRef())); 368 | } 369 | 370 | 371 | template 372 | inline ReductionStatus 373 | RelayDirect(Context& ctx, _fCurrent&& cur) 374 | { 375 | return cur(ctx); 376 | } 377 | 378 | template 379 | inline auto 380 | RelayDirect(Context& ctx, _fCurrent&& cur, TermNode& term) 381 | -> decltype(cur(term, ctx)) 382 | { 383 | // XXX: See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93470. 384 | return ystdex::unref(cur)(term, ctx); 385 | } 386 | inline ReductionStatus 387 | RelayDirect(Context& ctx, const Continuation& cur, TermNode& term) 388 | { 389 | return cur.Handler(term, ctx); 390 | } 391 | 392 | template 393 | inline ReductionStatus 394 | RelayCurrentOrDirect(Context& ctx, _fCurrent&& cur, TermNode& term) 395 | { 396 | return Unilang::RelayDirect(ctx, yforward(cur), term); 397 | } 398 | 399 | template 400 | inline ReductionStatus 401 | RelayCurrentNext(Context& ctx, TermNode& term, _fCurrent&& cur, 402 | _fNext&& next) 403 | { 404 | RelaySwitched(ctx, yforward(next)); 405 | return Unilang::RelayDirect(ctx, yforward(cur), term); 406 | } 407 | 408 | template 409 | inline ReductionStatus 410 | ReduceCurrentNext(TermNode& term, Context& ctx, _fCurrent&& cur, 411 | _fNext&& next) 412 | { 413 | ctx.SetNextTermRef(term); 414 | return Unilang::RelayCurrentNext(ctx, term, yforward(cur), yforward(next)); 415 | } 416 | 417 | 418 | template 419 | inline ReductionStatus 420 | ReduceSubsequent(TermNode& term, Context& ctx, _fNext&& next) 421 | { 422 | return Unilang::ReduceCurrentNext(term, ctx, std::ref(ReduceOnce), 423 | yforward(next)); 424 | } 425 | 426 | 427 | struct NonTailCall final 428 | { 429 | template 430 | static inline ReductionStatus 431 | RelayNextGuarded(Context& ctx, TermNode& term, EnvironmentGuard&& gd, 432 | _fCurrent&& cur) 433 | { 434 | return Unilang::RelayCurrentNext(ctx, term, yforward(cur), 435 | MoveKeptGuard(gd)); 436 | } 437 | 438 | template 439 | static inline ReductionStatus 440 | RelayNextGuardedLifted(Context& ctx, TermNode& term, 441 | EnvironmentGuard&& gd, _fCurrent&& cur) 442 | { 443 | auto act(MoveKeptGuard(gd)); 444 | Continuation cont(NameTypedContextHandler([&]{ 445 | return ReduceForLiftedResult(term); 446 | }, "eval-lift-result"), ctx); 447 | 448 | RelaySwitched(ctx, std::move(act)); 449 | return Unilang::RelayCurrentNext(ctx, term, yforward(cur), 450 | std::move(cont)); 451 | } 452 | 453 | template 454 | static ReductionStatus 455 | RelayNextGuardedProbe(Context& ctx, TermNode& term, 456 | EnvironmentGuard&& gd, bool lift, _fCurrent&& cur) 457 | { 458 | auto act(MoveKeptGuard(gd)); 459 | 460 | if(lift) 461 | { 462 | Continuation cont(NameTypedContextHandler([&]{ 463 | return ReduceForLiftedResult(term); 464 | }, "eval-lift-result"), ctx); 465 | 466 | RelaySwitched(ctx, std::move(act)); 467 | return Unilang::RelayCurrentNext(ctx, term, yforward(cur), 468 | std::move(cont)); 469 | } 470 | return Unilang::RelayCurrentNext(ctx, term, yforward(cur), std::move(act)); 471 | } 472 | 473 | static void 474 | SetupForNonTail(Context& ctx, TermNode& term) 475 | { 476 | assert(!AccessTCOAction(ctx)); 477 | ctx.LastStatus = ReductionStatus::Neutral; 478 | SetupTailTCOAction(ctx, term, {}); 479 | } 480 | }; 481 | 482 | 483 | struct TailCall final 484 | { 485 | template 486 | static inline ReductionStatus 487 | RelayNextGuarded(Context& ctx, TermNode& term, EnvironmentGuard&& gd, 488 | _fCurrent&& cur) 489 | { 490 | PrepareTCOEvaluation(ctx, term, std::move(gd)); 491 | return Unilang::RelayCurrentOrDirect(ctx, yforward(cur), term); 492 | } 493 | 494 | template 495 | static inline ReductionStatus 496 | RelayNextGuardedLifted(Context& ctx, TermNode& term, 497 | EnvironmentGuard&& gd, _fCurrent&& cur) 498 | { 499 | PrepareTCOEvaluation(ctx, term, std::move(gd)).SetupLift(); 500 | return Unilang::RelayCurrentOrDirect(ctx, yforward(cur), term); 501 | } 502 | 503 | template 504 | static inline ReductionStatus 505 | RelayNextGuardedProbe(Context& ctx, TermNode& term, 506 | EnvironmentGuard&& gd, bool lift, _fCurrent&& cur) 507 | { 508 | PrepareTCOEvaluation(ctx, term, std::move(gd)).SetupLift(lift); 509 | return Unilang::RelayCurrentOrDirect(ctx, yforward(cur), term); 510 | } 511 | 512 | static void 513 | SetupForNonTail(Context&, TermNode&) noexcept 514 | {} 515 | }; 516 | 517 | 518 | template 519 | struct Combine final 520 | { 521 | static ReductionStatus 522 | RelayEnvSwitch(Context& ctx, TermNode& term, EnvironmentGuard gd) 523 | { 524 | return _tTraits::RelayNextGuarded(ctx, term, std::move(gd), 525 | std::ref(ReduceCombinedBranch)); 526 | } 527 | static ReductionStatus 528 | RelayEnvSwitch(Context& ctx, TermNode& term, 529 | shared_ptr p_env) 530 | { 531 | return RelayEnvSwitch(ctx, term, EnvironmentGuard(ctx, 532 | ctx.SwitchEnvironmentUnchecked(std::move(p_env)))); 533 | } 534 | 535 | template 536 | static inline ReductionStatus 537 | ReduceEnvSwitch(TermNode& term, Context& ctx, _tGuardOrEnv&& gd_or_env) 538 | { 539 | _tTraits::SetupForNonTail(ctx, term); 540 | return RelayEnvSwitch(ctx, term, yforward(gd_or_env)); 541 | } 542 | 543 | template 544 | static ReductionStatus 545 | ReduceCallSubsequent(TermNode& term, Context& ctx, 546 | _tGuardOrEnv&& gd_or_env, _fNext&& next) 547 | { 548 | return Unilang::ReduceCurrentNext(term, ctx, 549 | ystdex::bind1([](TermNode& t, Context& c, _tGuardOrEnv& g_e){ 550 | return ReduceEnvSwitch(t, c, std::move(g_e)); 551 | }, std::placeholders::_2, std::move(gd_or_env)), yforward(next)); 552 | } 553 | }; 554 | 555 | 556 | using Action = function; 557 | 558 | 559 | } // namespace Unilang; 560 | 561 | #endif 562 | 563 | -------------------------------------------------------------------------------- /include/Unilang.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020-2023 UnionTech Software Technology Co.,Ltd. 2 | 3 | #ifndef INC_Unilang_Unilang_h_ 4 | #define INC_Unilang_Unilang_h_ 1 5 | 6 | #include 7 | #include YFM_YSLib_Adaptor_YAdaptor // for YSLib::byte, YSLib::size_t, 8 | // YSLib::lref, YSLib::pair, YSLib::allocate_shared, YSLib::make_shared, 9 | // YSLib::shared_ptr, YSLib::weak_ptr, YSLib::string_view, YSLib::pmr, 10 | // YSLib::default_allocator, YSLib::basic_string, YSLib::string, YSLib::array, 11 | // YSLib::forward_list, YSLib::flat_map, YSLib::flat_set, YSLib::list, 12 | // YSLib::map, YSLib::set, YSLib::unordered_map, YSLib::unordered_set, 13 | // YSLib::vector, YSLib::deque, YSLib::stack, YSLib::ostringstream, 14 | // YSLib::sfmt, YSLib::make_observer, YSLib::observer_ptr, YSLib::Deref, 15 | // YSLib::Nonnull; 16 | #include YFM_YSLib_Core_YFunc // for YSLib::function; 17 | #include // for YSLib::share_move; 18 | #include YFM_YSLib_Core_YObject // for YSLib::type_id, YSLib::type_index, 19 | // YSLib::type_info, YSLib::any_ops, YSLib::any, YSLib::bad_any_cast, 20 | // YSLib::in_place_type, YSLib::ValueObject, YSLib::IsTyped; 21 | #include // for complete 22 | // ystdex::pmr::polymorphic_allocator; 23 | 24 | namespace Unilang 25 | { 26 | 27 | using YSLib::byte; 28 | using YSLib::size_t; 29 | 30 | using YSLib::lref; 31 | 32 | using YSLib::pair; 33 | 34 | using YSLib::function; 35 | 36 | using YSLib::allocate_shared; 37 | using YSLib::make_shared; 38 | using YSLib::share_move; 39 | using YSLib::shared_ptr; 40 | using YSLib::weak_ptr; 41 | 42 | using YSLib::string_view; 43 | 44 | namespace pmr = YSLib::pmr; 45 | using YSLib::default_allocator; 46 | 47 | using YSLib::basic_string; 48 | using YSLib::string; 49 | 50 | using YSLib::array; 51 | using YSLib::forward_list; 52 | using YSLib::flat_map; 53 | using YSLib::flat_set; 54 | using YSLib::list; 55 | using YSLib::map; 56 | using YSLib::set; 57 | using YSLib::unordered_map; 58 | using YSLib::unordered_set; 59 | using YSLib::vector; 60 | 61 | using YSLib::deque; 62 | using YSLib::stack; 63 | 64 | using YSLib::ostringstream; 65 | 66 | // NOTE: Only use the unqualified call for unqualified 'string' type. 67 | using YSLib::sfmt; 68 | 69 | using YSLib::type_id; 70 | using YSLib::type_index; 71 | using YSLib::type_info; 72 | 73 | using YSLib::make_observer; 74 | using YSLib::observer_ptr; 75 | 76 | namespace any_ops = YSLib::any_ops; 77 | using YSLib::any; 78 | using YSLib::bad_any_cast; 79 | using YSLib::in_place_type; 80 | 81 | using YSLib::ValueObject; 82 | 83 | using YSLib::Deref; 84 | using YSLib::Nonnull; 85 | 86 | } // namespace Unilang; 87 | 88 | #endif 89 | 90 | -------------------------------------------------------------------------------- /include/UnilangFFI.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 UnionTech Software Technology Co.,Ltd. 2 | 3 | #ifndef INC_Unilang_UnilangFFI_h_ 4 | #define INC_Unilang_UnilangFFI_h_ 1 5 | 6 | #include "Interpreter.h" // for Interpreter; 7 | 8 | namespace Unilang 9 | { 10 | 11 | void 12 | InitializeFFI(Interpreter&); 13 | 14 | } // namespace Unilang; 15 | 16 | #endif 17 | 18 | -------------------------------------------------------------------------------- /include/UnilangQt.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020-2021 UnionTech Software Technology Co.,Ltd. 2 | 3 | #ifndef INC_Unilang_UnilangQt_h_ 4 | #define INC_Unilang_UnilangQt_h_ 1 5 | 6 | #include "Interpreter.h" // for Interpreter; 7 | 8 | namespace Unilang 9 | { 10 | 11 | // NOTE: This function shall be called to use Qt bindings. The 'argc' argument 12 | // is explicitly kept of 'int' type in the caller. 13 | void 14 | InitializeQt(Interpreter&, int&, char*[]); 15 | 16 | } // namespace Unilang; 17 | 18 | #endif 19 | 20 | -------------------------------------------------------------------------------- /install-sbuild.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # SPDX-FileCopyrightText: 2020-2023 UnionTech Software Technology Co.,Ltd. 3 | # Requires: wget, 7z. 4 | 5 | set -e 6 | 7 | Unilang_BaseDir="$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd)" 8 | YSLib_BaseDir="$Unilang_BaseDir/3rdparty/YSLib" 9 | 10 | case $(uname) in 11 | *MSYS* | *MINGW*) 12 | echo 'Windows platforms are not supported by this installer yet.' 13 | exit 1 14 | esac 15 | 16 | # NOTE: Prepare archives for YSLib build. 17 | 18 | LIB="$YSLib_BaseDir/YFramework/Linux/lib" 19 | 20 | if test -d "$LIB" && test -r "$LIB/libFreeImage.a" \ 21 | && test -r "$LIB/libFreeImaged.a"; \ 22 | then 23 | echo 'Archive files for YSLib are detected. Skip.' 24 | else 25 | echo 'Archive files for YSLib are not ready. Try to download them...' 26 | 27 | if ! hash wget 2> /dev/null; then 28 | echo "Missing tool: wget. Install wget first." 29 | exit 1 30 | fi 31 | if ! hash 7za 2> /dev/null; then 32 | echo "Missing tool: 7za. Install p7zip first." 33 | exit 1 34 | fi 35 | 36 | echo 'Getting archive files online ...' 37 | 38 | mkdir -p "$LIB" 39 | 40 | # XXX: Currently OSDN does not always successfully select the optimal 41 | # mirror. Use hard-coded TUNA mirror to get better performance. 42 | URL_Lib_Archive=\ 43 | 'https://osdn.net/frs/redir.php?m=tuna&f=yslib%2F73798%2FExternal-0.9-b916.7z' 44 | 45 | # XXX: Currently p7zip does not support '-si' for 7z archives. 46 | wget -O /tmp/External-0.9-b916.7z "$URL_Lib_Archive" 47 | 7za x -y -bsp0 -bso0 /tmp/External-0.9-b916.7z -o"$LIB" 48 | 49 | echo 'Archive files prepared.' 50 | fi 51 | 52 | # NOTE: Patch files. 53 | 54 | PATCHED_SIG="$Unilang_BaseDir/3rdparty/.patched" 55 | if test -f "$PATCHED_SIG"; then 56 | echo 'Patched source found. Skipped patching.' 57 | else 58 | echo 'Ready to patch files.' 59 | 60 | # NOTE: Workaround for compiler bugs. 61 | sed -i 's/is_nothrow_swappable()/true/' \ 62 | "$YSLib_BaseDir/YBase/include/ystdex/flat_map.hpp" 63 | sed -i 's/is_nothrow_swappable()/true/' \ 64 | "$YSLib_BaseDir/YBase/include/ystdex/flat_map.hpp" 65 | sed -i 's/is_nothrow_swappable()/true/' \ 66 | "$YSLib_BaseDir/YBase/include/ystdex/flat_set.hpp" 67 | # NOTE: Old GCC does not support LTO well. Disable it globally here. 68 | sed -i 's/-flto=jobserver//g' \ 69 | "$YSLib_BaseDir/Tools/Scripts/SHBuild-YSLib-common.txt" 70 | sed -i 's/-flto=auto//g' \ 71 | "$YSLib_BaseDir/Tools/Scripts/SHBuild-YSLib-common.txt" 72 | sed -i 's/-flto//g' \ 73 | "$YSLib_BaseDir/Tools/Scripts/SHBuild-YSLib-common.txt" 74 | # NOTE: Workaround for requiring LLD with Clang++. LLD may not work on 75 | # certain configurations on Linux. 76 | sed -i 's/use_lld_ \#t/use_lld_ host-win32/g' \ 77 | "$YSLib_BaseDir/Tools/Scripts/SHBuild-YSLib-common.txt" 78 | # NOTE: Use debug library to work around the bogus LTO information in the 79 | # release library. 80 | cp "$YSLib_BaseDir/YFramework/Linux/lib/libFreeImaged.a" \ 81 | "$YSLib_BaseDir/YFramework/Linux/lib/libFreeImage.a" 82 | 83 | touch "$PATCHED_SIG" 84 | echo 'Patched.' 85 | fi 86 | 87 | # NOTE: Build. 88 | 89 | : "${SHBuild_BuildOpt:="-xj,$(nproc)"}" 90 | : "${SHBuild_SysRoot:="$YSLib_BaseDir/sysroot"}" 91 | 92 | echo "Use option: SHBuild_BuildOpt=$SHBuild_BuildOpt" 93 | echo "Use option: SHBuild_SysRoot=$SHBuild_SysRoot" 94 | 95 | SHBuild_SysRoot="$SHBuild_SysRoot" SHBuild_UseDebug=true \ 96 | SHBuild_UseRelease=true SHBuild_NoDev=true \ 97 | "$YSLib_BaseDir/Tools/install-sysroot.sh" "$SHBuild_BuildOpt" "$@" 98 | 99 | echo 'To make the build environment work, ensure environment variables are' \ 100 | 'exported as following:' 101 | echo "export PATH=$SHBuild_SysRoot/usr/bin:\$PATH" 102 | echo "export LD_LIBRARY_PATH=$SHBuild_SysRoot/usr/lib:\$LD_LIBRARY_PATH" 103 | 104 | echo 'Done.' 105 | 106 | -------------------------------------------------------------------------------- /sbuild.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # SPDX-FileCopyrightText: 2020-2023 UnionTech Software Technology Co.,Ltd. 3 | # Requires: YSLib sysroot from up-to-date 3rdparty, having bin in $PATH. 4 | # Usage: sbuild.sh CONF [SHBOPT_BASE ...]. 5 | # See https://frankhb.github.io/YSLib-book/Tools/Scripts.zh-CN.html. 6 | 7 | set -e 8 | Unilang_BaseDir="$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd)" 9 | 10 | test -n "$1" \ 11 | || (echo "ERROR: The configuration name should not be empty." >& 2; exit 1) 12 | 13 | . "$Unilang_BaseDir/detect-llvm.sh" 14 | 15 | CXXFLAGS_Qt="$(pkg-config --cflags Qt5Widgets Qt5Quick)" 16 | LIBS_Qt="$(pkg-config --libs Qt5Widgets Qt5Quick)" 17 | 18 | case $(uname) in 19 | *MINGW64*) 20 | # XXX: Workaround for MinGW64 G++ + ld with ASLR enabled by default. Clang++ 21 | # + ld or G++ + lld do need the flag, but leaving it here as-is. 22 | # See https://www.msys2.org/news/#2021-01-31-aslr-enabled-by-default, 23 | # https://github.com/msys2/MINGW-packages/issues/6986, 24 | # https://github.com/msys2/MINGW-packages/issues/7023, 25 | # and https://sourceware.org/bugzilla/show_bug.cgi?id=26659. 26 | LDFLAGS_LOWBASE_=-Wl,--default-image-base-low 27 | ;; 28 | esac 29 | case $(uname) in 30 | *MSYS* | *MINGW*) 31 | LIBS_EXTRA="$LIBS_Qt $LIBS_EXTRA" 32 | ;; 33 | *) 34 | if echo "$CXX" | grep -q clang; then 35 | # XXX: Workaround for old versions of Clang++ not recognizing 36 | # '-fno-semantic-interposition'. 37 | export C_CXXFLAGS_PIC=-fPIC 38 | fi 39 | LIBS_EXTRA="$LIBS_Qt $LIBS_EXTRA -ldl" 40 | esac 41 | mkdir -p "$Unilang_BaseDir/build" 42 | (cd "$Unilang_BaseDir/build" && SHBuild_NoAdjustSubsystem=true \ 43 | SHBuild_CXXFLAGS="$CXXFLAGS_EXTRA" SHBuild_LDFLAGS="$LDFLAGS_LOWBASE_" \ 44 | SHBuild_LIBS="$LIBS_EXTRA" SHBuild-BuildPkg.sh "$@" \ 45 | -xn,unilang "$Unilang_BaseDir/src" -I\""$Unilang_BaseDir/include"\" \ 46 | "$CXXFLAGS_Qt") 47 | 48 | echo "Done." 49 | 50 | -------------------------------------------------------------------------------- /src/BasicReduction.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020-2022 UnionTech Software Technology Co.,Ltd. 2 | 3 | #include "BasicReduction.h" // for IsSticky; 4 | #include "TermAccess.h" // for ClearCombiningTags, EnsureValueTags, 5 | // TryAccessLeafAtom, TermReference; 6 | #include "TermNode.h" // for AssertValueTags; 7 | #include // for assert; 8 | #include // for std::distance; 9 | #include // for ystdex::as_const; 10 | #include // for ystdex::cast_mutable; 11 | 12 | namespace Unilang 13 | { 14 | 15 | ReductionStatus 16 | RegularizeTerm(TermNode& term, ReductionStatus status) noexcept 17 | { 18 | ClearCombiningTags(term); 19 | if(status == ReductionStatus::Clean) 20 | term.ClearContainer(); 21 | return status; 22 | } 23 | 24 | 25 | void 26 | LiftOtherOrCopy(TermNode& term, TermNode& tm, bool move) 27 | { 28 | if(move) 29 | LiftOther(term, tm); 30 | else 31 | term.CopyContent(tm); 32 | } 33 | 34 | void 35 | LiftTermOrCopy(TermNode& term, TermNode& tm, bool move) 36 | { 37 | if(move) 38 | LiftTerm(term, tm); 39 | else 40 | term.CopyContent(tm); 41 | } 42 | 43 | 44 | namespace 45 | { 46 | 47 | void 48 | LiftMovedOther(TermNode& term, const TermReference& ref, bool move) 49 | { 50 | LiftOtherOrCopy(term, ref.get(), move); 51 | EnsureValueTags(term.Tags); 52 | AssertValueTags(term); 53 | } 54 | 55 | } // unnamed namespace; 56 | 57 | void 58 | LiftToReturn(TermNode& term) 59 | { 60 | if(const auto p = TryAccessLeafAtom(term)) 61 | LiftMovedOther(term, *p, p->IsMovable()); 62 | AssertValueTags(term); 63 | } 64 | 65 | TNIter 66 | LiftPrefixToReturn(TermNode& term, TNCIter it) 67 | { 68 | assert(size_t(std::distance(ystdex::as_const(term).begin(), it)) 69 | <= CountPrefix(term) && "Invalid arguments found."); 70 | 71 | auto i(ystdex::cast_mutable(term.GetContainerRef(), it)); 72 | 73 | while(i != term.end() && !IsSticky(i->Tags)) 74 | { 75 | LiftToReturn(*i); 76 | ++i; 77 | } 78 | assert((term.Value || i == term.end()) && "Invalid representation found."); 79 | return i; 80 | } 81 | 82 | ReductionStatus 83 | ReduceBranchToList(TermNode& term) noexcept 84 | { 85 | RemoveHead(term); 86 | return ReductionStatus::Retained; 87 | } 88 | 89 | ReductionStatus 90 | ReduceBranchToListValue(TermNode& term) noexcept 91 | { 92 | RemoveHead(term); 93 | LiftSubtermsToReturn(term); 94 | return ReductionStatus::Retained; 95 | } 96 | 97 | } // namespace Unilang; 98 | 99 | -------------------------------------------------------------------------------- /src/Context.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020-2023 UnionTech Software Technology Co.,Ltd. 2 | 3 | #include "Context.h" // for string_view, Unilang::allocate_shared, 4 | // make_observer, lref, ystdex::retry_on_cond, YSLib::make_string_view; 5 | #include // for assert; 6 | #include "Exception.h" // for TypeError, BadIdentifier, UnilangException, 7 | // ListTypeError; 8 | #include // for ystdex::sfmt; 9 | #include "TermNode.h" // for Unilang::AddValueTo, AssertValueTags, IsAtom; 10 | #include // for std::throw_with_nested; 11 | #include // ystdex::exchange; 12 | #include "Evaluation.h" // for ReduceOnce; 13 | #include // for ystdex::make_guard; 14 | #include "TermAccess.h" // for Unilang::IsMovable; 15 | #include "Forms.h" // for Forms::Sequence, ReduceBranchToList; 16 | #include "Evaluation.h" // for Strict; 17 | #include // for ystdex::id; 18 | #include // for std::find_if; 19 | #include "Syntax.h" // for ReduceSyntax; 20 | 21 | namespace Unilang 22 | { 23 | 24 | EnvironmentReference::EnvironmentReference(const shared_ptr& p_env) 25 | noexcept 26 | : EnvironmentReference(p_env, p_env ? p_env->GetAnchorPtr() : nullptr) 27 | {} 28 | 29 | namespace 30 | { 31 | 32 | struct AnchorData final 33 | { 34 | public: 35 | AnchorData() = default; 36 | AnchorData(AnchorData&&) = default; 37 | 38 | AnchorData& 39 | operator=(AnchorData&&) = default; 40 | }; 41 | 42 | 43 | shared_ptr 44 | RedirectToShared(shared_ptr p_env) 45 | { 46 | #if Unilang_CheckParentEnvironment 47 | if(p_env) 48 | #else 49 | assert(p_env); 50 | #endif 51 | return p_env; 52 | #if Unilang_CheckParentEnvironment 53 | throw InvalidReference(ystdex::sfmt("Invalid reference found for name," 54 | " probably due to invalid context access by a dangling reference.")); 55 | #endif 56 | } 57 | 58 | 59 | using Redirector = IParent::Redirector; 60 | 61 | void 62 | RedirectEnvironmentList(Environment::allocator_type a, Redirector& cont, 63 | EnvironmentList::const_iterator first, EnvironmentList::const_iterator last) 64 | { 65 | cont = ystdex::make_obj_using_allocator(a, 66 | std::bind([=, &cont](Redirector& c) -> observer_ptr{ 67 | cont = std::move(c); 68 | if(first != last) 69 | { 70 | RedirectEnvironmentList(a, cont, std::next(first), last); 71 | return make_observer(&first->GetObject()); 72 | } 73 | return {}; 74 | }, std::move(cont))); 75 | } 76 | 77 | YB_NORETURN void 78 | ThrowResolveEnvironmentFailure(const TermNode& term, bool has_ref) 79 | { 80 | throw ListTypeError(ystdex::sfmt("Invalid environment formed from list '%s'" 81 | " found.", TermToStringWithReferenceMark(term, has_ref).c_str())); 82 | } 83 | 84 | } // unnamed namespace; 85 | 86 | 87 | IParent::~IParent() = default; 88 | 89 | 90 | EmptyParent::~EmptyParent() = default; 91 | 92 | 93 | shared_ptr 94 | SingleWeakParent::TryRedirect(IParent::Redirector&) const 95 | { 96 | return RedirectToShared(env_ref.Lock()); 97 | } 98 | 99 | 100 | shared_ptr 101 | SingleStrongParent::TryRedirect(IParent::Redirector&) const 102 | { 103 | return RedirectToShared(env_ptr); 104 | } 105 | 106 | 107 | const EmptyParent EnvironmentParent::DefaultEmptyParent; 108 | 109 | 110 | shared_ptr 111 | ParentList::TryRedirect(IParent::Redirector& cont) const 112 | { 113 | RedirectEnvironmentList(envs.get_allocator(), cont, envs.cbegin(), 114 | envs.cend()); 115 | return {}; 116 | } 117 | 118 | 119 | BindingMap& 120 | Environment::GetMapCheckedRef() 121 | { 122 | if(!IsFrozen()) 123 | return GetMapRef(); 124 | throw TypeError("Frozen environment found."); 125 | } 126 | 127 | void 128 | Environment::DefineChecked(BindingMap& m, string_view id, ValueObject&& vo) 129 | { 130 | assert(id.data()); 131 | if(!Unilang::AddValueTo(m, id, std::move(vo))) 132 | throw BadIdentifier(id, 2); 133 | } 134 | 135 | Environment& 136 | Environment::EnsureValid(const shared_ptr& p_env) 137 | { 138 | if(p_env) 139 | return *p_env; 140 | throw std::invalid_argument("Invalid environment found."); 141 | } 142 | 143 | AnchorPtr 144 | Environment::InitAnchor(allocator_type a) const 145 | { 146 | return Unilang::allocate_shared(a); 147 | } 148 | 149 | NameResolution::first_type 150 | Environment::LookupName(string_view id) const 151 | { 152 | assert(id.data()); 153 | 154 | const auto i(bindings.find(id)); 155 | 156 | return make_observer(i != bindings.cend() ? &i->second : nullptr); 157 | } 158 | 159 | void 160 | Environment::ThrowForInvalidType(const type_info& tp) 161 | { 162 | throw TypeError( 163 | ystdex::sfmt("Invalid environment type '%s' found.", tp.name())); 164 | } 165 | 166 | 167 | Context::Context(const GlobalState& g) 168 | : memory_rsrc(*g.Allocator.resource()), Global(g) 169 | {} 170 | 171 | TermNode& 172 | Context::GetNextTermRef() const 173 | { 174 | if(const auto p = next_term_ptr) 175 | return *p; 176 | throw UnilangException("No next term found to evaluation."); 177 | } 178 | 179 | ReductionStatus 180 | Context::ApplyTail() 181 | { 182 | assert(IsAlive() && "No tail action found."); 183 | TailAction = std::move(current.front()); 184 | current.pop_front(); 185 | try 186 | { 187 | LastStatus = TailAction(*this); 188 | } 189 | catch(...) 190 | { 191 | HandleException(std::current_exception()); 192 | } 193 | return LastStatus; 194 | } 195 | 196 | void 197 | Context::DefaultHandleException(std::exception_ptr p) 198 | { 199 | assert(bool(p)); 200 | try 201 | { 202 | std::rethrow_exception(std::move(p)); 203 | } 204 | catch(bad_any_cast& ex) 205 | { 206 | std::throw_with_nested(TypeError(ystdex::sfmt("Mismatched types ('%s'," 207 | " '%s') found.", ex.from(), ex.to()).c_str())); 208 | } 209 | } 210 | 211 | NameResolution 212 | Context::Resolve(shared_ptr p_env, string_view id) const 213 | { 214 | assert(bool(p_env)); 215 | 216 | Redirector cont; 217 | NameResolution::first_type p_obj; 218 | 219 | do 220 | { 221 | p_obj = p_env->LookupName(id); 222 | }while([&]() -> bool{ 223 | if(!p_obj) 224 | { 225 | observer_ptr p_next(&p_env->Parent.GetObject()); 226 | 227 | do 228 | { 229 | auto& parent(*p_next); 230 | 231 | p_next = {}; 232 | if(auto p_redirected = parent.TryRedirect(cont)) 233 | { 234 | p_env.swap(p_redirected); 235 | return true; 236 | } 237 | while(!p_next && bool(cont)) 238 | p_next = ystdex::exchange(cont, Redirector())(); 239 | assert(p_next.get() != &parent && "Cyclic parent found."); 240 | }while(p_next); 241 | } 242 | return false; 243 | }()); 244 | return {p_obj, std::move(p_env)}; 245 | } 246 | 247 | ReductionStatus 248 | Context::RewriteGuarded(TermNode&, Reducer reduce) 249 | { 250 | const auto i(GetCurrent().cbegin()); 251 | const auto unwind(ystdex::make_guard([i, this]() noexcept{ 252 | TailAction = nullptr; 253 | UnwindCurrentUntil(i); 254 | })); 255 | 256 | return RewriteUntil(std::move(reduce), i); 257 | } 258 | 259 | ReductionStatus 260 | Context::RewriteLoop() 261 | { 262 | // NOTE: Rewrite until no actions remain. 263 | do 264 | { 265 | ApplyTail(); 266 | }while(IsAlive()); 267 | return LastStatus; 268 | } 269 | 270 | ReductionStatus 271 | Context::RewriteLoopUntil(ReducerSequence::const_iterator i) 272 | { 273 | assert(IsAliveBefore(i) && "No action to reduce."); 274 | // NOTE: Rewrite until no actions before the parameter pointed to remain. 275 | return ystdex::retry_on_cond( 276 | std::bind(&Context::IsAliveBefore, this, i), [&]{ 277 | return ApplyTail(); 278 | }); 279 | } 280 | 281 | ReductionStatus 282 | Context::RewriteTerm(TermNode& term) 283 | { 284 | AssertValueTags(term); 285 | next_term_ptr = &term; 286 | return Rewrite(Unilang::ToReducer(get_allocator(), std::ref(ReduceOnce))); 287 | } 288 | 289 | ReductionStatus 290 | Context::RewriteTermGuarded(TermNode& term) 291 | { 292 | AssertValueTags(term); 293 | next_term_ptr = &term; 294 | return RewriteGuarded(term, 295 | Unilang::ToReducer(get_allocator(), std::ref(ReduceOnce))); 296 | } 297 | 298 | shared_ptr 299 | Context::ShareRecord() const noexcept 300 | { 301 | return p_record; 302 | } 303 | 304 | shared_ptr 305 | Context::SwitchEnvironment(shared_ptr p_env) 306 | { 307 | if(p_env) 308 | return SwitchEnvironmentUnchecked(p_env); 309 | throw std::invalid_argument("Invalid environment record pointer found."); 310 | } 311 | 312 | shared_ptr 313 | Context::SwitchEnvironmentUnchecked(shared_ptr p_env) noexcept 314 | { 315 | assert(p_env); 316 | return ystdex::exchange(p_record, std::move(p_env)); 317 | } 318 | 319 | bool 320 | Context::TrySetTailOperatorName(TermNode& term) const noexcept 321 | { 322 | if(combining_term_ptr) 323 | { 324 | auto& comb(*combining_term_ptr); 325 | 326 | if(IsBranch(comb) && ystdex::ref_eq<>()(AccessFirstSubterm(comb), term)) 327 | { 328 | OperatorName = std::move(term.Value); 329 | return true; 330 | } 331 | } 332 | return {}; 333 | } 334 | 335 | void 336 | Context::UnwindCurrent() noexcept 337 | { 338 | // XXX: The order is significant. 339 | while(!current.empty()) 340 | current.pop_front(); 341 | } 342 | 343 | EnvironmentReference 344 | Context::WeakenRecord() const noexcept 345 | { 346 | return p_record; 347 | } 348 | 349 | 350 | TermNode 351 | MoveResolved(const Context& ctx, string_view id) 352 | { 353 | auto pr(ResolveName(ctx, id)); 354 | 355 | if(const auto p = pr.first) 356 | { 357 | if(Unilang::Deref(pr.second).IsFrozen()) 358 | return *p; 359 | return std::move(*p); 360 | } 361 | throw BadIdentifier(id); 362 | } 363 | 364 | TermNode 365 | ResolveIdentifier(const Context& ctx, string_view id) 366 | { 367 | auto pr(ResolveName(ctx, id)); 368 | 369 | if(pr.first) 370 | return PrepareCollapse(*pr.first, pr.second); 371 | throw BadIdentifier(id); 372 | } 373 | 374 | pair, bool> 375 | ResolveEnvironment(const TermNode& term) 376 | { 377 | return ResolveTerm(static_cast, bool>(&)( 378 | const TermNode&, bool)>(ResolveEnvironmentReferent), term); 379 | } 380 | pair, bool> 381 | ResolveEnvironment(TermNode& term) 382 | { 383 | return ResolveTerm(static_cast, bool>(&)( 384 | TermNode&, ResolvedTermReferencePtr)>(ResolveEnvironmentReferent), 385 | term); 386 | } 387 | 388 | pair, bool> 389 | ResolveEnvironmentReferent(const TermNode& nd, bool has_ref) 390 | { 391 | if(IsAtom(nd)) 392 | return ResolveEnvironmentValue(nd.Value); 393 | ThrowResolveEnvironmentFailure(nd, has_ref); 394 | } 395 | pair, bool> 396 | ResolveEnvironmentReferent(TermNode& nd, ResolvedTermReferencePtr p_ref) 397 | { 398 | if(IsAtom(nd)) 399 | return ResolveEnvironmentValue(nd.Value, Unilang::IsMovable(p_ref)); 400 | ThrowResolveEnvironmentFailure(nd, p_ref); 401 | } 402 | 403 | pair, bool> 404 | ResolveEnvironmentValue(const ValueObject& vo) 405 | { 406 | if(const auto p = vo.AccessPtr()) 407 | return {p->Lock(), {}}; 408 | if(const auto p = vo.AccessPtr>()) 409 | return {*p, true}; 410 | Environment::ThrowForInvalidType(vo.type()); 411 | } 412 | pair, bool> 413 | ResolveEnvironmentValue(ValueObject& vo, bool move) 414 | { 415 | if(const auto p = vo.AccessPtr()) 416 | return {p->Lock(), {}}; 417 | if(const auto p = vo.AccessPtr>()) 418 | return {move ? std::move(*p) : *p, true}; 419 | Environment::ThrowForInvalidType(vo.type()); 420 | } 421 | 422 | 423 | struct SeparatorPass::TransformationSpec final 424 | { 425 | enum SeparatorKind 426 | { 427 | NAry, 428 | BinaryAssocLeft, 429 | BinaryAssocRight 430 | }; 431 | 432 | function Filter; 433 | function MakePrefix; 434 | SeparatorKind Kind; 435 | 436 | TransformationSpec(decltype(Filter), decltype(MakePrefix), 437 | SeparatorKind = NAry); 438 | TransformationSpec(TokenValue, ValueObject, SeparatorKind = NAry); 439 | }; 440 | 441 | SeparatorPass::TransformationSpec::TransformationSpec( 442 | decltype(Filter) filter, decltype(MakePrefix) make_pfx, SeparatorKind kind) 443 | : Filter(std::move(filter)), MakePrefix(std::move(make_pfx)), Kind(kind) 444 | {} 445 | SeparatorPass::TransformationSpec::TransformationSpec(TokenValue delim, 446 | ValueObject pfx, SeparatorKind kind) 447 | : TransformationSpec(ystdex::bind1(HasValue, delim), 448 | [=](const ValueObject&){ 449 | return pfx; 450 | }, kind) 451 | {} 452 | 453 | SeparatorPass::SeparatorPass(TermNode::allocator_type a) 454 | : allocator(a), transformations({{";", ContextHandler(Forms::Sequence)}, 455 | {",", ContextHandler(FormContextHandler(ReduceBranchToList, Strict))}, 456 | {[](const TermNode& nd) noexcept{ 457 | return HasValue(nd, ":="); 458 | }, [](const ValueObject&){ 459 | return TokenValue("assign!"); 460 | }, TransformationSpec::BinaryAssocRight}, 461 | {[](const TermNode& nd) noexcept{ 462 | return HasValue(nd, "=") || HasValue(nd, "!="); 463 | }, ystdex::id<>(), TransformationSpec::BinaryAssocLeft}, 464 | {[](const TermNode& nd) noexcept{ 465 | return HasValue(nd, "<") || HasValue(nd, ">") 466 | || HasValue(nd, "<=") || HasValue(nd, ">="); 467 | }, ystdex::id<>(), TransformationSpec::BinaryAssocLeft}, 468 | {[](const TermNode& nd) noexcept{ 469 | return HasValue(nd, "+") || HasValue(nd, "-"); 470 | }, ystdex::id<>(), TransformationSpec::BinaryAssocLeft}, 471 | {[](const TermNode& nd) noexcept{ 472 | return HasValue(nd, "*") || HasValue(nd, "/"); 473 | }, ystdex::id<>(), TransformationSpec::BinaryAssocLeft}}, a) 474 | {} 475 | SeparatorPass::~SeparatorPass() = default; 476 | 477 | ReductionStatus 478 | SeparatorPass::operator()(TermNode& term) const 479 | { 480 | assert(remained.empty() && "Invalid state found."); 481 | Transform(term, {}, remained); 482 | while(!remained.empty()) 483 | { 484 | const auto entry(std::move(remained.top())); 485 | 486 | remained.pop(); 487 | for(auto& tm : entry.first.get()) 488 | Transform(tm, entry.second, remained); 489 | } 490 | return ReductionStatus::Clean; 491 | } 492 | 493 | void 494 | SeparatorPass::Transform(TermNode& term, bool skip_binary, 495 | SeparatorPass::TermStack& terms) const 496 | { 497 | if(IsBranch(term)) 498 | { 499 | if(IsEmpty(*term.begin())) 500 | skip_binary = true; 501 | terms.push({term, skip_binary}); 502 | for(const auto& trans : transformations) 503 | { 504 | const auto& filter(trans.Filter); 505 | auto i(std::find_if(term.begin(), term.end(), filter)); 506 | 507 | if(i != term.end()) 508 | switch(trans.Kind) 509 | { 510 | case TransformationSpec::NAry: 511 | term = SeparatorTransformer::Process(std::move(term), 512 | trans.MakePrefix(i->Value), filter); 513 | break; 514 | case TransformationSpec::BinaryAssocLeft: 515 | case TransformationSpec::BinaryAssocRight: 516 | if(skip_binary) 517 | break; 518 | if(trans.Kind == TransformationSpec::BinaryAssocLeft) 519 | { 520 | const auto ri(std::find_if(term.rbegin(), term.rend(), 521 | filter)); 522 | 523 | assert(ri != term.rend()); 524 | i = ri.base(); 525 | --i; 526 | } 527 | if(i != term.begin() && std::next(i) != term.end()) 528 | { 529 | const auto a(term.get_allocator()); 530 | auto res(Unilang::AsTermNode(a, yforward(term).Value)); 531 | auto im(std::make_move_iterator(i)); 532 | using it_t = decltype(im); 533 | const auto range_add([&](it_t b, it_t e){ 534 | const auto add([&](TermNode& node, it_t j){ 535 | node.Add(Unilang::Deref(j)); 536 | }); 537 | 538 | assert(b != e); 539 | if(std::next(b) == e) 540 | add(res, b); 541 | else 542 | { 543 | auto child(Unilang::AsTermNode(a)); 544 | 545 | do 546 | { 547 | add(child, b++); 548 | }while(b != e); 549 | res.Add(std::move(child)); 550 | } 551 | }); 552 | 553 | res.Add( 554 | Unilang::AsTermNode(a, trans.MakePrefix(i->Value))); 555 | range_add(std::make_move_iterator(term.begin()), im); 556 | range_add(++im, std::make_move_iterator(term.end())); 557 | term = std::move(res); 558 | } 559 | } 560 | } 561 | } 562 | } 563 | 564 | 565 | GlobalState::GlobalState(TermNode::allocator_type a) 566 | : Allocator(a), ConvertLeaf([this](const GParsedValue& str){ 567 | TermNode term(Allocator); 568 | const auto id(YSLib::make_string_view(str)); 569 | 570 | if(!id.empty()) 571 | ParseLeaf(term, id); 572 | return term; 573 | }), ConvertLeafSourced([this](const GParsedValue& val, 574 | const Context& ctx){ 575 | TermNode term(Allocator); 576 | const auto id(YSLib::make_string_view(val.second)); 577 | 578 | if(!id.empty()) 579 | ParseLeafWithSourceInformation(term, id, ctx.CurrentSource, 580 | val.first); 581 | return term; 582 | }) 583 | {} 584 | 585 | TermNode 586 | GlobalState::Read(string_view unit, Context& ctx) const 587 | { 588 | LexicalAnalyzer lexer; 589 | 590 | if(UseSourceLocation) 591 | { 592 | SourcedByteParser parse(lexer, ctx.get_allocator()); 593 | 594 | return Prepare(ctx, unit.begin(), unit.end(), ystdex::ref(parse)); 595 | } 596 | return Prepare(lexer, ctx, unit.begin(), unit.end()); 597 | } 598 | 599 | TermNode 600 | GlobalState::ReadFrom(std::streambuf& buf, Context& ctx) const 601 | { 602 | using s_it_t = std::istreambuf_iterator; 603 | LexicalAnalyzer lexer; 604 | 605 | if(UseSourceLocation) 606 | { 607 | SourcedByteParser parse(lexer, ctx.get_allocator()); 608 | 609 | return Prepare(ctx, s_it_t(&buf), s_it_t(), ystdex::ref(parse)); 610 | } 611 | return Prepare(lexer, ctx, s_it_t(&buf), s_it_t()); 612 | } 613 | TermNode 614 | GlobalState::ReadFrom(std::istream& is, Context& ctx) const 615 | { 616 | if(is) 617 | { 618 | if(const auto p = is.rdbuf()) 619 | return ReadFrom(*p, ctx); 620 | throw std::invalid_argument("Invalid stream buffer found."); 621 | } 622 | else 623 | throw std::invalid_argument("Invalid stream found."); 624 | } 625 | 626 | } // namespace Unilang; 627 | 628 | -------------------------------------------------------------------------------- /src/Exception.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020-2023 UnionTech Software Technology Co.,Ltd. 2 | 3 | #include "Exception.h" // for std::string, YSLib::to_std_string, ystdex::sfmt, 4 | // make_shared, YSLib::share_move; 5 | #include "Lexical.h" // for EscapeLiteral; 6 | #include "TermAccess.h" // for TermToStringWithReferenceMark; 7 | #include // for assert; 8 | 9 | namespace Unilang 10 | { 11 | 12 | UnilangException::~UnilangException() = default; 13 | 14 | 15 | TypeError::~TypeError() = default; 16 | 17 | 18 | ValueCategoryMismatch::~ValueCategoryMismatch() = default; 19 | 20 | 21 | ListTypeError::~ListTypeError() = default; 22 | 23 | 24 | ListReductionFailure::~ListReductionFailure() = default; 25 | 26 | 27 | InvalidSyntax::~InvalidSyntax() = default; 28 | 29 | 30 | ParameterMismatch::~ParameterMismatch() = default; 31 | 32 | 33 | ArityMismatch::~ArityMismatch() = default; 34 | 35 | 36 | BadIdentifier::~BadIdentifier() = default; 37 | 38 | 39 | InvalidReference::~InvalidReference() = default; 40 | 41 | namespace 42 | { 43 | 44 | std::string 45 | InitBadIdentifierExceptionString(string_view id, size_t n) 46 | { 47 | return YSLib::to_std_string((n != 0 48 | ? (n == 1 ? "Bad identifier: '" : "Duplicate identifier: '") 49 | : "Unknown identifier: '") + EscapeLiteral(id) + "'."); 50 | } 51 | 52 | } // unnamed namespace; 53 | 54 | ArityMismatch::ArityMismatch(size_t e, size_t r) 55 | : ParameterMismatch( 56 | ystdex::sfmt("Arity mismatch: expected %zu, received %zu.", e, r)), 57 | expected(e), received(r) 58 | {} 59 | 60 | 61 | BadIdentifier::BadIdentifier(const char* id, size_t n) 62 | : InvalidSyntax(InitBadIdentifierExceptionString(id, n)), 63 | p_identifier(make_shared(id)) 64 | {} 65 | BadIdentifier::BadIdentifier(string_view id, size_t n) 66 | : InvalidSyntax(InitBadIdentifierExceptionString(id, n)), 67 | p_identifier(make_shared(id.data(), id.size())) 68 | {} 69 | 70 | void 71 | BadIdentifier::ReplaceAllocator(default_allocator a) 72 | { 73 | auto& p_src(Source.first); 74 | 75 | if(p_src) 76 | p_src = YSLib::share_move(a, *p_src); 77 | } 78 | 79 | 80 | void 81 | ThrowInsufficientTermsError(const TermNode& term, bool has_ref, size_t n_skip) 82 | { 83 | throw ParameterMismatch(ystdex::sfmt( 84 | "Insufficient subterms found in '%s' for the list parameter.", 85 | TermToStringWithReferenceMark(term, has_ref, n_skip).c_str())); 86 | } 87 | 88 | void 89 | ThrowListTypeErrorForAtom(const TermNode& term, bool has_ref, size_t n_skip) 90 | { 91 | throw ListTypeError(ystdex::sfmt("Expected a pair, got '%s'.", 92 | TermToStringWithReferenceMark(term, has_ref, n_skip).c_str())); 93 | } 94 | 95 | void 96 | ThrowListTypeErrorForInvalidType(const char* name, const TermNode& term, 97 | bool has_ref, size_t n_skip) 98 | { 99 | throw ListTypeError(ystdex::sfmt("Expected a value of type '%s', got '%s'.", 100 | name, TermToStringWithReferenceMark(term, has_ref, n_skip).c_str())); 101 | } 102 | void 103 | ThrowListTypeErrorForInvalidType(const type_info& ti, 104 | const TermNode& term, bool has_ref, size_t n_skip) 105 | { 106 | ThrowListTypeErrorForInvalidType(ti.name(), term, has_ref, n_skip); 107 | } 108 | 109 | void 110 | ThrowListTypeErrorForNonList(const TermNode& term, bool has_ref, size_t n_skip) 111 | { 112 | throw ListTypeError(ystdex::sfmt("Expected a list, got '%s'.", 113 | TermToStringWithReferenceMark(term, has_ref, n_skip).c_str())); 114 | } 115 | 116 | void 117 | ThrowTypeErrorForInvalidType(const char* name, const char* rep) 118 | { 119 | throw TypeError(ystdex::sfmt("Expected a value of type '%s', got '%s'.", 120 | name, rep)); 121 | } 122 | void 123 | ThrowTypeErrorForInvalidType(const char* name, const TermNode& term, 124 | bool has_ref, size_t n_skip) 125 | { 126 | ThrowTypeErrorForInvalidType(name, 127 | TermToStringWithReferenceMark(term, has_ref, n_skip).c_str()); 128 | } 129 | void 130 | ThrowTypeErrorForInvalidType(const type_info& ti, const TermNode& term, 131 | bool has_ref, size_t n_skip) 132 | { 133 | ThrowTypeErrorForInvalidType(ti.name(), term, has_ref, n_skip); 134 | } 135 | 136 | void 137 | ThrowValueCategoryError(const TermNode& term) 138 | { 139 | throw ValueCategoryMismatch(ystdex::sfmt("Expected a reference for the 1st " 140 | "argument, got '%s'.", TermToString(term).c_str())); 141 | } 142 | 143 | 144 | void 145 | ThrowNonmodifiableErrorForAssignee() 146 | { 147 | throw TypeError("Destination operand of assignment shall be modifiable."); 148 | } 149 | 150 | } // namespace Unilang; 151 | 152 | -------------------------------------------------------------------------------- /src/Interpreter.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020-2023 UnionTech Software Technology Co.,Ltd. 2 | 3 | #include "Interpreter.h" // for TokenValue, ystdex::sfmt, HasValue, 4 | // string_view, std::bind, Unilang::SwitchToFreshEnvironment, 5 | // Unilang::ToParent, std::getline; 6 | #include // for std::ostream; 7 | #include "Math.h" // for FPToString; 8 | #include // for ystdex::bind1, std::placeholders::_1; 9 | #include "Evaluation.h" // for TraceBacktrace; 10 | #include 11 | #include YFM_YSLib_Core_YException // for YSLib, YSLib::ExtractException, 12 | // YSLib::Notice, YSLib::stringstream; 13 | #include YFM_YSLib_Service_TextFile // for Text::OpenSkippedBOMtream, 14 | // Text::BOM_UTF_8, YSLib::share_move; 15 | #include // for std::throw_with_nested, std::rethrow_exception; 16 | #include // for ystdex::make_guard; 17 | #include "Evaluation.h" // for Unilang::NameTypedReducerHandler; 18 | #include // for std::cout, std::endl, std::cin; 19 | 20 | namespace Unilang 21 | { 22 | 23 | namespace 24 | { 25 | 26 | template 27 | void 28 | PrintTermNode(std::ostream& os, const TermNode& term, _func f, size_t depth = 0, 29 | size_t idx = 0) 30 | { 31 | if(depth != 0 && idx != 0) 32 | os << ' '; 33 | ResolveTerm([&](const TermNode& tm){ 34 | try 35 | { 36 | f(os, tm); 37 | return; 38 | } 39 | catch(bad_any_cast&) 40 | {} 41 | os << '('; 42 | try 43 | { 44 | size_t i(0); 45 | 46 | for(const auto& nd : tm) 47 | { 48 | PrintTermNode(os, nd, f, depth + 1, i); 49 | ++i; 50 | } 51 | } 52 | catch(std::out_of_range&) 53 | {} 54 | os << ')'; 55 | }, term); 56 | } 57 | 58 | YB_ATTR_nodiscard YB_PURE string 59 | StringifyValueObject(const ValueObject& vo) 60 | { 61 | if(const auto p = vo.AccessPtr()) 62 | return ystdex::quote(*p); 63 | if(const auto p = vo.AccessPtr()) 64 | return *p; 65 | if(const auto p = vo.AccessPtr()) 66 | return *p ? "#t" : "#f"; 67 | if(const auto p = vo.AccessPtr()) 68 | return sfmt("%d", *p); 69 | if(const auto p = vo.AccessPtr()) 70 | return sfmt("%u", *p); 71 | if(const auto p = vo.AccessPtr()) 72 | return sfmt("%lld", *p); 73 | if(const auto p = vo.AccessPtr()) 74 | return sfmt("%llu", *p); 75 | if(const auto p = vo.AccessPtr()) 76 | return FPToString(*p); 77 | if(const auto p = vo.AccessPtr()) 78 | return sfmt("%ld", *p); 79 | if(const auto p = vo.AccessPtr()) 80 | return sfmt("%lu", *p); 81 | if(const auto p = vo.AccessPtr()) 82 | return sfmt("%hd", *p); 83 | if(const auto p = vo.AccessPtr()) 84 | return sfmt("%hu", *p); 85 | if(const auto p = vo.AccessPtr()) 86 | return sfmt("%d", int(*p)); 87 | if(const auto p = vo.AccessPtr()) 88 | return sfmt("%u", unsigned(*p)); 89 | if(const auto p = vo.AccessPtr()) 90 | return FPToString(*p); 91 | if(const auto p = vo.AccessPtr()) 92 | return FPToString(*p); 93 | if(const auto p = vo.AccessPtr()) 94 | return ystdex::sfmt("%d", *p); 95 | if(const auto p = vo.AccessPtr()) 96 | if(*p == ValueToken::Unspecified) 97 | return "#inert"; 98 | 99 | const auto& t(vo.type()); 100 | 101 | if(t != typeid(void)) 102 | return "#[" + string(t.name()) + ']'; 103 | throw bad_any_cast(); 104 | } 105 | 106 | YB_ATTR_nodiscard YB_PURE string 107 | StringifyValueObjectForDisplay(const ValueObject& vo) 108 | { 109 | if(const auto p = vo.AccessPtr()) 110 | return *p; 111 | return StringifyValueObject(vo); 112 | } 113 | 114 | YB_ATTR_nodiscard YB_PURE string 115 | StringifyValueObjectForWrite(const ValueObject& vo) 116 | { 117 | // XXX: At current, this is just same to the "print" routine. 118 | return StringifyValueObject(vo); 119 | } 120 | 121 | YB_ATTR_nodiscard YB_PURE std::string 122 | MismatchedTypesToString(const bad_any_cast& e) 123 | { 124 | return ystdex::sfmt("Mismatched types ('%s', '%s') found.", e.from(), 125 | e.to()); 126 | } 127 | 128 | void 129 | TraceException(const std::exception& e, YSLib::Logger& trace) 130 | { 131 | using namespace YSLib; 132 | 133 | ExtractException([&](const char* str, size_t level){ 134 | const auto print([&](RecordLevel lv, const char* name, const char* msg){ 135 | trace.TraceFormat(lv, "%*s%s<%u>: %s", int(level), "", name, 136 | unsigned(lv), msg); 137 | }); 138 | 139 | try 140 | { 141 | throw; 142 | } 143 | catch(BadIdentifier& ex) 144 | { 145 | print(Err, "BadIdentifier", str); 146 | const auto& si(ex.Source); 147 | 148 | if(ex.Source.first) 149 | trace.TraceFormat(Err, "%*sIdentifier '%s' is at line %zu," 150 | " column %zu in %s.", int(level + 1), "", 151 | ex.GetIdentifier().c_str(), si.second.Line + 1, 152 | si.second.Column + 1, si.first->c_str()); 153 | } 154 | catch(bad_any_cast& ex) 155 | { 156 | print(Warning, "TypeError", MismatchedTypesToString(ex).c_str()); 157 | } 158 | catch(LoggedEvent& ex) 159 | { 160 | print(Err, typeid(ex).name(), str); 161 | } 162 | catch(...) 163 | { 164 | print(Err, "Error", str); 165 | } 166 | }, e); 167 | } 168 | 169 | YSLib::unique_ptr 170 | OpenFile(const char* filename) 171 | { 172 | try 173 | { 174 | return YSLib::Text::OpenSkippedBOMtream< 175 | YSLib::IO::SharedInputMappedFileStream>(YSLib::Text::BOM_UTF_8, 176 | filename); 177 | } 178 | catch(...) 179 | { 180 | std::throw_with_nested(std::invalid_argument( 181 | ystdex::sfmt("Failed opening file '%s'.", filename))); 182 | } 183 | } 184 | 185 | template 186 | inline void 187 | RewriteBy(Context& ctx, _func f) 188 | { 189 | ctx.Rewrite(Unilang::ToReducer(ctx.get_allocator(), std::move(f))); 190 | } 191 | 192 | template 193 | void 194 | SetupExceptionHandler(Context& ctx, _func f) 195 | { 196 | ctx.SaveExceptionHandler(); 197 | ctx.HandleException = ystdex::bind1([&, f](std::exception_ptr p, 198 | const Context::ReducerSequence::const_iterator& i){ 199 | ctx.TailAction = nullptr; 200 | f(std::move(p), i); 201 | }, ctx.GetCurrent().cbegin()); 202 | } 203 | 204 | } // unnamed namespace; 205 | 206 | 207 | Interpreter::Interpreter() 208 | { 209 | Global.UseSourceLocation = UseSourceLocation; 210 | } 211 | 212 | void 213 | Interpreter::Evaluate(TermNode& term) 214 | { 215 | Global.Preprocess(term); 216 | Main.RewriteTermGuarded(term); 217 | } 218 | 219 | ReductionStatus 220 | Interpreter::ExecuteOnce(Context& ctx) 221 | { 222 | Global.Preprocess(Term); 223 | return ReduceOnce(Term, ctx); 224 | } 225 | 226 | ReductionStatus 227 | Interpreter::ExecuteString(string_view unit, Context& ctx) 228 | { 229 | PrepareExecution(ctx); 230 | Term = Read(unit); 231 | return ExecuteOnce(ctx); 232 | } 233 | 234 | ReductionStatus 235 | Interpreter::Exit() 236 | { 237 | Main.UnwindCurrent(); 238 | return ReductionStatus::Neutral; 239 | } 240 | 241 | void 242 | Interpreter::HandleWithTrace(std::exception_ptr p, Context& ctx, 243 | Context::ReducerSequence::const_iterator i) 244 | { 245 | assert(p); 246 | try 247 | { 248 | std::rethrow_exception(std::move(p)); 249 | } 250 | catch(std::exception& e) 251 | { 252 | auto& cur(ctx.GetCurrentRef()); 253 | const auto gd(ystdex::make_guard([&]() noexcept{ 254 | cur.UnwindUntil(i); 255 | })); 256 | static YSLib::Logger trace; 257 | 258 | TraceException(e, trace); 259 | trace.TraceFormat(YSLib::Notice, "Location: %s.", 260 | Main.CurrentSource ? Main.CurrentSource->c_str() : ""); 261 | TraceBacktrace(cur.cbegin(), i, trace); 262 | } 263 | } 264 | 265 | YSLib::unique_ptr 266 | Interpreter::OpenUnique(Context& ctx, string filename) 267 | { 268 | using namespace YSLib; 269 | auto p_is(OpenFile(filename.c_str())); 270 | 271 | ctx.CurrentSource = YSLib::share_move(filename); 272 | return p_is; 273 | } 274 | 275 | void 276 | Interpreter::PrepareExecution(Context& ctx) 277 | { 278 | SetupExceptionHandler(ctx, [&](std::exception_ptr p, 279 | const Context::ReducerSequence::const_iterator& i){ 280 | HandleWithTrace(std::move(p), ctx, i); 281 | }); 282 | if(Echo) 283 | RelaySwitched(ctx, Unilang::NameTypedReducerHandler([&]{ 284 | Print(Term); 285 | return ReductionStatus::Neutral; 286 | }, "repl-print")); 287 | } 288 | 289 | void 290 | Interpreter::Print(const TermNode& term) 291 | { 292 | PrintTermNode(std::cout, term); 293 | std::cout << std::endl; 294 | } 295 | 296 | TermNode 297 | Interpreter::Perform(string_view unit) 298 | { 299 | auto term(Read(unit)); 300 | 301 | Evaluate(term); 302 | return term; 303 | } 304 | 305 | TermNode 306 | Interpreter::Read(string_view unit) 307 | { 308 | return Global.Read(unit, Main); 309 | } 310 | 311 | void 312 | Interpreter::Run() 313 | { 314 | RewriteBy(Main, 315 | std::bind(&Interpreter::RunLoop, this, std::placeholders::_1)); 316 | } 317 | 318 | void 319 | Interpreter::RunLine(string_view unit) 320 | { 321 | if(!unit.empty() && unit != "exit") 322 | { 323 | Main.ShareCurrentSource("*STDIN*"); 324 | RewriteBy(Main, [&](Context& ctx){ 325 | return ExecuteString(unit, ctx); 326 | }); 327 | } 328 | } 329 | 330 | void 331 | Interpreter::RunScript(string filename) 332 | { 333 | if(filename == "-") 334 | { 335 | Main.ShareCurrentSource("*STDIN*"); 336 | RewriteBy(Main, [&](Context& ctx){ 337 | PrepareExecution(ctx); 338 | Term = Global.ReadFrom(std::cin, Main); 339 | return ExecuteOnce(ctx); 340 | }); 341 | } 342 | else if(!filename.empty()) 343 | { 344 | Main.ShareCurrentSource(filename); 345 | RewriteBy(Main, [&](Context& ctx){ 346 | PrepareExecution(ctx); 347 | Term 348 | = Global.ReadFrom(*OpenUnique(Main, std::move(filename)), Main); 349 | return ExecuteOnce(ctx); 350 | }); 351 | } 352 | } 353 | 354 | ReductionStatus 355 | Interpreter::RunLoop(Context& ctx) 356 | { 357 | if(WaitForLine()) 358 | { 359 | Main.ShareCurrentSource("*STDIN*"); 360 | RelaySwitched(ctx, std::bind(&Interpreter::RunLoop, std::ref(*this), 361 | std::placeholders::_1)); 362 | return !line.empty() ? (line != "exit" ? ExecuteString(line, ctx) 363 | : Exit()) : ReductionStatus::Partial; 364 | } 365 | return ReductionStatus::Retained; 366 | } 367 | 368 | bool 369 | Interpreter::SaveGround() 370 | { 371 | if(!p_ground) 372 | { 373 | p_ground = Unilang::SwitchToFreshEnvironment(Main, Unilang::ToParent< 374 | SingleWeakParent>(Global.Allocator, Main.WeakenRecord())); 375 | return true; 376 | } 377 | return {}; 378 | } 379 | 380 | std::istream& 381 | Interpreter::WaitForLine() 382 | { 383 | using namespace std; 384 | 385 | cout << "> "; 386 | return getline(cin, line); 387 | } 388 | 389 | 390 | void 391 | DisplayTermValue(std::ostream& os, const TermNode& term) 392 | { 393 | YSLib::stringstream oss(string(term.get_allocator())); 394 | 395 | PrintTermNode(oss, term, [](std::ostream& os0, const TermNode& nd){ 396 | os0 << StringifyValueObjectForDisplay(nd.Value); 397 | }); 398 | os << oss.str().c_str(); 399 | } 400 | 401 | void 402 | PrintTermNode(std::ostream& os, const TermNode& term) 403 | { 404 | PrintTermNode(os, term, [](std::ostream& os0, const TermNode& nd){ 405 | os0 << StringifyValueObject(nd.Value); 406 | }); 407 | } 408 | 409 | void 410 | WriteTermValue(std::ostream& os, const TermNode& term) 411 | { 412 | YSLib::stringstream oss(string(term.get_allocator())); 413 | 414 | PrintTermNode(oss, term, [](std::ostream& os0, const TermNode& nd){ 415 | os0 << StringifyValueObjectForWrite(nd.Value); 416 | }); 417 | os << oss.str().c_str(); 418 | } 419 | 420 | } // namespace Unilang; 421 | 422 | -------------------------------------------------------------------------------- /src/JIT.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021-2022 UnionTech Software Technology Co.,Ltd. 2 | 3 | #include "JIT.h" 4 | #if !UNILANG_NO_LLVM 5 | #if __GNUG__ 6 | # pragma GCC diagnostic push 7 | # pragma GCC diagnostic ignored "-Wctor-dtor-privacy" 8 | # pragma GCC diagnostic ignored "-Wshadow" 9 | # pragma GCC diagnostic ignored "-Wsign-conversion" 10 | # pragma GCC diagnostic ignored "-Wunused-parameter" 11 | #endif 12 | #if __clang__ 13 | # pragma clang diagnostic push 14 | # pragma clang diagnostic ignored "-Wshorten-64-to-32" 15 | #endif 16 | #include "llvm/ADT/STLExtras.h" 17 | #include "llvm/Support/TargetSelect.h" // for llvm::InitializeNativeTarget, 18 | // llvm::InitializeNativeTargetAsmPrinter, 19 | // llvm::InitializeNativeTargetAsmParser; 20 | #if __clang__ 21 | # pragma clang diagnostic pop 22 | #endif 23 | #if __GNUG__ 24 | # pragma GCC diagnostic pop 25 | #endif 26 | #endif 27 | 28 | namespace Unilang 29 | { 30 | 31 | namespace 32 | { 33 | 34 | class Driver final 35 | { 36 | public: 37 | Driver(); 38 | 39 | void 40 | InitializeModuleAndPassManager(); 41 | 42 | void 43 | Run(); 44 | }; 45 | 46 | Driver::Driver() 47 | { 48 | #if !UNILANG_NO_LLVM 49 | llvm::InitializeNativeTarget(); 50 | llvm::InitializeNativeTargetAsmPrinter(); 51 | llvm::InitializeNativeTargetAsmParser(); 52 | #endif 53 | InitializeModuleAndPassManager(); 54 | } 55 | 56 | void 57 | Driver::InitializeModuleAndPassManager() 58 | { 59 | } 60 | 61 | void 62 | Driver::Run() 63 | { 64 | } 65 | 66 | 67 | ReductionStatus 68 | JITReduceOnce(TermNode& term, Context& ctx) 69 | { 70 | return Context::DefaultReduceOnce(term, ctx); 71 | } 72 | 73 | } // unnamed namespace; 74 | 75 | void 76 | SetupJIT(Context& ctx) 77 | { 78 | ctx.ReduceOnce = Continuation(JITReduceOnce, ctx); 79 | } 80 | 81 | void 82 | JITMain() 83 | { 84 | Driver d; 85 | 86 | d.Run(); 87 | } 88 | 89 | } // namespace Unilang; 90 | 91 | -------------------------------------------------------------------------------- /src/Lexical.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020-2021 UnionTech Software Technology Co.,Ltd. 2 | 3 | #include "Lexical.h" // for string, assert, std::string; 4 | #include // for ystdex::get_mid, ystdex::quote; 5 | #include "Exception.h" // for UnilangException; 6 | 7 | namespace Unilang 8 | { 9 | 10 | bool 11 | HandleBackslashPrefix(string_view buf, UnescapeContext& uctx) 12 | { 13 | assert(!buf.empty() && "Invalid buffer found."); 14 | if(!uctx.IsHandling() && buf.back() == '\\') 15 | { 16 | uctx.Start = buf.length() - 1; 17 | uctx.Length = 1; 18 | return true; 19 | } 20 | return {}; 21 | } 22 | 23 | bool 24 | Unescape(string& buf, char c, UnescapeContext& uctx, char ld) 25 | { 26 | if(uctx.Length == 1) 27 | { 28 | uctx.VerifyBufferLength(buf.length()); 29 | 30 | const auto i(uctx.Start); 31 | 32 | assert(i == buf.length() - 1 && "Invalid buffer found."); 33 | switch(c) 34 | { 35 | case '\\': 36 | buf[i] = '\\'; 37 | break; 38 | case 'a': 39 | buf[i] = '\a'; 40 | break; 41 | case 'b': 42 | buf[i] = '\b'; 43 | break; 44 | case 'f': 45 | buf[i] = '\f'; 46 | break; 47 | case 'n': 48 | buf[i] = '\n'; 49 | break; 50 | case 'r': 51 | buf[i] = '\r'; 52 | break; 53 | case 't': 54 | buf[i] = '\t'; 55 | break; 56 | case 'v': 57 | buf[i] = '\v'; 58 | break; 59 | case '\n': 60 | buf.pop_back(); 61 | break; 62 | case '\'': 63 | case '"': 64 | if(c == ld) 65 | { 66 | buf[i] = ld; 67 | break; 68 | } 69 | YB_ATTR_fallthrough; 70 | default: 71 | uctx.Clear(); 72 | buf += c; 73 | return {}; 74 | } 75 | uctx.Clear(); 76 | return true; 77 | } 78 | buf += c; 79 | return {}; 80 | } 81 | 82 | 83 | bool 84 | LexicalAnalyzer::UpdateBack(char& b, char c) 85 | { 86 | switch(c) 87 | { 88 | case '\'': 89 | case '"': 90 | if(ld == char()) 91 | { 92 | ld = c; 93 | return true; 94 | } 95 | else if(ld == c) 96 | { 97 | ld = char(); 98 | return true; 99 | } 100 | break; 101 | case '\f': 102 | case '\n': 103 | case '\t': 104 | case '\v': 105 | if(ld == char()) 106 | { 107 | b = ' '; 108 | break; 109 | } 110 | YB_ATTR_fallthrough; 111 | default: 112 | break; 113 | } 114 | return {}; 115 | } 116 | 117 | 118 | char 119 | CheckLiteral(string_view sv) noexcept 120 | { 121 | assert(sv.data()); 122 | if(sv.length() > 1) 123 | if(const char c = sv.front() == sv.back() ? sv.front() : char()) 124 | { 125 | if(c == '\'' || c == '"') 126 | return c; 127 | } 128 | return {}; 129 | } 130 | 131 | string 132 | Escape(string_view sv) 133 | { 134 | assert(bool(sv.data())); 135 | 136 | char last{}; 137 | string res; 138 | 139 | res.reserve(sv.length()); 140 | for(char c : sv) 141 | { 142 | char unescaped{}; 143 | 144 | switch(c) 145 | { 146 | case '\a': 147 | unescaped = 'a'; 148 | break; 149 | case '\b': 150 | unescaped = 'b'; 151 | break; 152 | case '\f': 153 | unescaped = 'f'; 154 | break; 155 | case '\n': 156 | unescaped = 'n'; 157 | break; 158 | case '\r': 159 | unescaped = 'r'; 160 | break; 161 | case '\t': 162 | unescaped = 't'; 163 | break; 164 | case '\v': 165 | unescaped = 'v'; 166 | break; 167 | case '\'': 168 | case '"': 169 | unescaped = c; 170 | } 171 | if(last == '\\') 172 | { 173 | if(c == '\\') 174 | { 175 | yunseq(last = char(), res += '\\'); 176 | continue; 177 | } 178 | switch(c) 179 | { 180 | case 'a': 181 | case 'b': 182 | case 'f': 183 | case 'n': 184 | case 'r': 185 | case 't': 186 | case 'v': 187 | case '\'': 188 | case '"': 189 | res += '\\'; 190 | } 191 | } 192 | if(unescaped == char()) 193 | res += c; 194 | else 195 | { 196 | res += '\\'; 197 | res += unescaped; 198 | unescaped = char(); 199 | } 200 | last = c; 201 | } 202 | return res; 203 | } 204 | 205 | string 206 | EscapeLiteral(string_view sv) 207 | { 208 | const char c(CheckLiteral(sv)); 209 | auto content(Escape(c == char() ? sv : ystdex::get_mid(string(sv)))); 210 | 211 | if(!content.empty() && content.back() == '\\') 212 | content += '\\'; 213 | return c == char() ? std::move(content) : ystdex::quote(content, c); 214 | } 215 | 216 | 217 | LexemeCategory 218 | CategorizeBasicLexeme(string_view id) noexcept 219 | { 220 | assert(id.data()); 221 | 222 | const auto c(CheckLiteral(id)); 223 | 224 | if(c == '\'') 225 | return LexemeCategory::Code; 226 | if(c != char()) 227 | return LexemeCategory::Data; 228 | return LexemeCategory::Symbol; 229 | } 230 | 231 | 232 | void 233 | ThrowMismatchBoundaryToken(char ldelim, char rdelim) 234 | { 235 | throw UnilangException(std::string("Mismatched right boundary token '") 236 | + rdelim + "' found for left boundary token '" + ldelim + "'."); 237 | } 238 | 239 | } // namespace Unilang; 240 | 241 | -------------------------------------------------------------------------------- /src/Parser.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020-2022 UnionTech Software Technology Co.,Ltd. 2 | 3 | #include "Parser.h" 4 | #include // for assert; 5 | #include "Lexical.h" // for IsDelimiter, IsGraphicalDelimiter; 6 | 7 | namespace Unilang 8 | { 9 | 10 | namespace 11 | { 12 | 13 | template 14 | void 15 | UpdateByteRaw(_fAdd add, _fAppend append, _tParseResult& res, bool got_delim, 16 | const LexicalAnalyzer& lexer, string& cbuf, bool& update_current) 17 | { 18 | const auto len(cbuf.length()); 19 | 20 | assert(!(res.empty() && update_current) && "Invalid state found."); 21 | if(len > 0 && !lexer.GetUnescapeContext().IsHandling()) 22 | { 23 | if(len == 1) 24 | { 25 | const auto update_c([&](char c){ 26 | if(update_current) 27 | append(res, c); 28 | else 29 | add(res, string({c}, res.get_allocator())); 30 | }); 31 | const char c(cbuf.back()); 32 | const bool unquoted(lexer.GetDelimiter() == char()); 33 | 34 | if(got_delim) 35 | { 36 | update_c(c); 37 | update_current = !unquoted; 38 | } 39 | else if(unquoted && IsDelimiter(c)) 40 | { 41 | if(IsGraphicalDelimiter(c)) 42 | add(res, string({c}, res.get_allocator())); 43 | update_current = {}; 44 | } 45 | else 46 | { 47 | update_c(c); 48 | update_current = true; 49 | } 50 | } 51 | else if(update_current) 52 | append(res, std::move(cbuf)); 53 | else 54 | add(res, std::move(cbuf)); 55 | cbuf.clear(); 56 | } 57 | } 58 | 59 | template 60 | struct SequenceAdd final 61 | { 62 | template 63 | void 64 | operator()(_tParseResult& res, _tParam&& arg) const 65 | { 66 | res.push_back(yforward(arg)); 67 | } 68 | }; 69 | 70 | template 71 | struct SequenceAppend final 72 | { 73 | template 74 | void 75 | operator()(_tParseResult& res, _tParam&& arg) const 76 | { 77 | res.back() += yforward(arg); 78 | } 79 | }; 80 | 81 | template 82 | struct SourcedSequenceAdd final 83 | { 84 | const SourcedByteParser& Parser; 85 | 86 | template 87 | void 88 | operator()(_tParseResult& res, _tParam&& arg) const 89 | { 90 | res.emplace_back(Parser.GetSourceLocation(), yforward(arg)); 91 | } 92 | }; 93 | 94 | template 95 | struct SourcedSequenceAppend final 96 | { 97 | template 98 | void 99 | operator()(_tParseResult& res, _tParam&& arg) const 100 | { 101 | res.back().second += yforward(arg); 102 | } 103 | }; 104 | 105 | } // unnamed namespace; 106 | 107 | 108 | void 109 | ByteParser::Update(bool got_delim) 110 | { 111 | UpdateByteRaw(SequenceAdd(), SequenceAppend(), 112 | lexemes, got_delim, GetLexerRef(), GetBufferRef(), update_current); 113 | } 114 | 115 | 116 | void 117 | SourcedByteParser::Update(bool got_delim) 118 | { 119 | UpdateByteRaw(SourcedSequenceAdd{*this}, 120 | SourcedSequenceAppend(), lexemes, got_delim, GetLexerRef(), 121 | GetBufferRef(), update_current); 122 | } 123 | 124 | } // namespace Unilang; 125 | 126 | -------------------------------------------------------------------------------- /src/TCO.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021-2023 UnionTech Software Technology Co.,Ltd. 2 | 3 | #include "TCO.h" // for Unilang::ToBindingsAllocator, Unilang::Deref, 4 | // Unilang::Nonnull, std::get, IsTyped; 5 | #include // for assert; 6 | #include // for ystdex::make_unique_guard, 7 | // ystdex::dismiss; 8 | #include // for ystdex::retry_on_cond, ystdex::id; 9 | #include "Context.h" // for Unilang::AssignParent; 10 | #include // for ystdex::ref_eq; 11 | #include "Exception.h" // for UnilangException; 12 | 13 | namespace Unilang 14 | { 15 | 16 | RecordCompressor::RecordCompressor(const shared_ptr& p_root) 17 | : RecordCompressor(p_root, 18 | Unilang::ToBindingsAllocator(Unilang::Deref(p_root))) 19 | {} 20 | RecordCompressor::RecordCompressor(const shared_ptr& p_root, 21 | Environment::allocator_type a) 22 | : RootPtr(p_root), Reachable({*p_root}, a), NewlyReachable(a), Universe(a) 23 | { 24 | AddParents(*p_root); 25 | } 26 | 27 | void 28 | RecordCompressor::AddParents(Environment& e) 29 | { 30 | Traverse(e, e.Parent, 31 | [this](const shared_ptr& p_dst, const Environment&){ 32 | return Universe.emplace(*p_dst, CountReferences(p_dst)).second; 33 | }); 34 | } 35 | 36 | void 37 | RecordCompressor::Compress() 38 | { 39 | const auto p_root(Unilang::Nonnull(RootPtr.lock())); 40 | const auto a(Unilang::ToBindingsAllocator(Unilang::Deref(p_root))); 41 | 42 | for(auto& pr : Universe) 43 | { 44 | auto& e(pr.first.get()); 45 | 46 | Traverse(e, e.Parent, [this](const shared_ptr& p_dst){ 47 | auto& count(Universe.at(Unilang::Deref(p_dst))); 48 | 49 | assert(count > 0); 50 | --count; 51 | return false; 52 | }); 53 | } 54 | for(auto i(Universe.cbegin()); i != Universe.cend(); ) 55 | if(i->second > 0) 56 | { 57 | NewlyReachable.insert(i->first); 58 | i = Universe.erase(i); 59 | } 60 | else 61 | ++i; 62 | 63 | ReferenceSet rs(a); 64 | 65 | while(!NewlyReachable.empty()) 66 | { 67 | for(const auto& e : NewlyReachable) 68 | Traverse(e, e.get().Parent, 69 | [&](const shared_ptr& p_dst) noexcept{ 70 | auto& dst(Unilang::Deref(p_dst)); 71 | 72 | rs.insert(dst); 73 | Universe.erase(dst); 74 | return false; 75 | }); 76 | Reachable.merge(std::move(NewlyReachable)); 77 | for(auto i(rs.cbegin()); i != rs.cend(); ) 78 | if(ystdex::exists(Reachable, *i)) 79 | i = rs.erase(i); 80 | else 81 | ++i; 82 | NewlyReachable = std::move(rs); 83 | rs.clear(); 84 | } 85 | ystdex::retry_on_cond(ystdex::id<>(), [&]() -> bool{ 86 | bool collected = {}; 87 | 88 | Traverse(*p_root, p_root->Parent, [&](const shared_ptr& 89 | p_dst, Environment& src, EnvironmentParent& parent) -> bool{ 90 | auto& dst(Unilang::Deref(p_dst)); 91 | 92 | if(rs.insert(src).second) 93 | { 94 | if(!ystdex::exists(Universe, ystdex::ref(dst))) 95 | return true; 96 | Unilang::AssignParent(parent, dst.Parent); 97 | collected = true; 98 | } 99 | return {}; 100 | }); 101 | return collected; 102 | }); 103 | } 104 | 105 | size_t 106 | RecordCompressor::CountReferences(const shared_ptr& p) noexcept 107 | { 108 | const auto acnt(p->GetAnchorCount()); 109 | 110 | assert(acnt > 0); 111 | return CountStrong(p) + size_t(acnt) - 2; 112 | } 113 | 114 | size_t 115 | RecordCompressor::CountStrong(const shared_ptr& p) noexcept 116 | { 117 | const long scnt(p.use_count()); 118 | 119 | assert(scnt > 0); 120 | return size_t(scnt); 121 | } 122 | 123 | 124 | TCOAction::TCOAction(Context& ctx, TermNode& term, bool lift) 125 | : req_lift_result(lift ? 1 : 0), record_list(ctx.get_allocator()), 126 | env_guard(ctx), term_guard(ystdex::make_unique_guard(GuardFunction{term})) 127 | {} 128 | TCOAction::TCOAction(const TCOAction& a) 129 | : req_lift_result(a.req_lift_result), env_guard(std::move(a.env_guard)), 130 | term_guard(std::move(a.term_guard)) 131 | {} 132 | 133 | ReductionStatus 134 | TCOAction::operator()(Context& ctx) const 135 | { 136 | assert(ystdex::ref_eq<>()(env_guard.func.ContextRef.get(), ctx) 137 | && "Invalid context found."); 138 | 139 | const auto res([&]() -> ReductionStatus{ 140 | if(req_lift_result != 0) 141 | { 142 | RegularizeTerm(GetTermRef(), ctx.LastStatus); 143 | for(; req_lift_result != 0; --req_lift_result) 144 | LiftToReturn(GetTermRef()); 145 | return ReductionStatus::Retained; 146 | } 147 | RegularizeTerm(GetTermRef(), ctx.LastStatus); 148 | return ctx.LastStatus; 149 | }()); 150 | 151 | ystdex::dismiss(term_guard); 152 | return res; 153 | } 154 | 155 | void 156 | TCOAction::CompressFrameList() 157 | { 158 | ystdex::retry_on_cond(ystdex::id<>(), [&]() -> bool{ 159 | bool removed = {}; 160 | decltype(record_list) spliced(record_list.get_allocator()); 161 | auto i(record_list.cbefore_begin()); 162 | const auto last(record_list.cend()); 163 | 164 | while(std::next(i) != last) 165 | { 166 | const auto& p_env(std::get(*std::next(i))); 167 | 168 | if(p_env.use_count() != 1 || Unilang::Deref(p_env).IsOrphan()) 169 | { 170 | spliced.splice_after(spliced.cbefore_begin(), record_list, i); 171 | removed = true; 172 | } 173 | else 174 | ++i; 175 | } 176 | return removed; 177 | }); 178 | } 179 | 180 | void 181 | TCOAction::CompressForGuard(Context& ctx, EnvironmentGuard&& gd) 182 | { 183 | auto& p_saved(gd.func.SavedPtr); 184 | 185 | assert(ystdex::ref_eq<>()(GetContextRef(), ctx) 186 | && "Invalid context found."); 187 | assert(ystdex::ref_eq<>()(gd.func.ContextRef.get(), ctx) 188 | && "Invalid guard found."); 189 | assert(p_saved && "Invalid guard found."); 190 | 191 | if(env_guard.func.SavedPtr) 192 | { 193 | if(!record_list.empty() 194 | && !std::get(record_list.front())) 195 | std::get(record_list.front()) 196 | = std::move(p_saved); 197 | else 198 | record_list.emplace_front(std::move(p_saved), ValueObject( 199 | record_list.get_allocator())); 200 | CompressForContext(ctx); 201 | } 202 | else 203 | env_guard.func.SavedPtr = std::move(gd.func.SavedPtr); 204 | } 205 | 206 | void 207 | TCOAction::SetupLift() const 208 | { 209 | ++req_lift_result; 210 | if(req_lift_result == 0) 211 | throw UnilangException( 212 | "TCO action lift request count overflow detected."); 213 | } 214 | 215 | TCOAction& 216 | EnsureTCOAction(Context& ctx, TermNode& term) 217 | { 218 | auto p_act(AccessTCOAction(ctx)); 219 | 220 | if(!p_act) 221 | { 222 | SetupTailTCOAction(ctx, term, {}); 223 | p_act = AccessTCOAction(ctx); 224 | } 225 | return Unilang::Deref(p_act); 226 | } 227 | 228 | void 229 | SetupTailTCOAction(Context& ctx, TermNode& term, bool lift) 230 | { 231 | ctx.SetupFront(TCOAction(ctx, term, lift)); 232 | } 233 | 234 | 235 | TCOAction& 236 | PrepareTCOEvaluation(Context& ctx, TermNode& term, EnvironmentGuard&& gd) 237 | { 238 | auto& act(RefTCOAction(ctx)); 239 | 240 | assert(&act.GetTermRef() == &term); 241 | yunused(term); 242 | act.CompressForGuard(ctx, std::move(gd)); 243 | return act; 244 | } 245 | 246 | } // namespace Unilang; 247 | 248 | -------------------------------------------------------------------------------- /src/TermAccess.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020-2023 UnionTech Software Technology Co.,Ltd. 2 | 3 | #include "TermAccess.h" // for TermToNamePtr, sfmt, ystdex::sfmt, TryAccessLeafAtom, IsTyped, 4 | // Unilang::Nonnull; 5 | #include // for assert; 6 | #include "Exception.h" // for ListTypeError, TypeError, ValueCategoryMismatch; 7 | #include // for ystdex::call_value_or; 8 | #include // for ystdex::compose, std::mem_fn, 9 | // ystdex::invoke_value_or; 10 | #include "Context.h" // for complete Environment; 11 | 12 | namespace Unilang 13 | { 14 | 15 | string 16 | TermToString(const TermNode& term, size_t n_skip) 17 | { 18 | if(const auto p = TermToNamePtr(term)) 19 | return *p; 20 | 21 | const bool non_list(!IsList(term)); 22 | 23 | assert(n_skip <= CountPrefix(term) && "Invalid skip number found."); 24 | 25 | const bool s_is_pair(n_skip < term.size() 26 | && IsSticky(std::next(term.begin(), ptrdiff_t(n_skip))->Tags)); 27 | 28 | return !non_list && n_skip == term.size() ? string("()") : sfmt( 29 | "#<%s{%zu}%s%s>", s_is_pair ? (non_list ? "improper-list" : "list") 30 | : "unknown", term.size() - n_skip, s_is_pair ? " . " 31 | : (non_list ? ":" : ""), non_list ? term.Value.type().name() : ""); 32 | } 33 | 34 | string 35 | TermToStringWithReferenceMark(const TermNode& term, bool has_ref, size_t n_skip) 36 | { 37 | auto term_str(TermToString(term, n_skip)); 38 | 39 | return has_ref ? "[*] " + std::move(term_str) : std::move(term_str); 40 | } 41 | 42 | TermTags 43 | TermToTags(TermNode& term) 44 | { 45 | AssertReferentTags(term); 46 | return ystdex::call_value_or(ystdex::compose(GetLValueTagsOf, 47 | std::mem_fn(&TermReference::GetTags)), 48 | TryAccessLeafAtom(term), term.Tags); 49 | } 50 | 51 | 52 | #if Unilang_CheckTermReferenceIndirection 53 | TermNode& 54 | TermReference::get() const 55 | { 56 | if(r_env.GetAnchorPtr() && r_env.GetPtr().lock()) 57 | return term_ref.get(); 58 | throw InvalidReference("Invalid reference found on indirection, probably" 59 | " due to invalid context access by a dangling reference."); 60 | } 61 | #endif 62 | 63 | 64 | namespace 65 | { 66 | 67 | YB_ATTR_nodiscard YB_STATELESS TermTags 68 | MergeTermTags(TermTags x, TermTags y) noexcept 69 | { 70 | return (((x & ~TermTags::Temporary) | y) & ~TermTags::Unique) 71 | | (x & y & TermTags::Unique); 72 | } 73 | 74 | } // unnamed namespace; 75 | 76 | pair 77 | Collapse(TermReference ref) 78 | { 79 | if(const auto p = TryAccessLeafAtom(ref.get())) 80 | { 81 | ref.SetTags(MergeTermTags(ref.GetTags(), p->GetTags())), 82 | ref.SetReferent(p->get()); 83 | return {std::move(ref), true}; 84 | } 85 | return {std::move(ref), {}}; 86 | } 87 | 88 | TermNode 89 | PrepareCollapse(TermNode& term, const shared_ptr& p_env) 90 | { 91 | return IsTyped(term) ? term : Unilang::AsTermNode( 92 | term.get_allocator(), TermReference(p_env->MakeTermTags(term), term, 93 | Unilang::Nonnull(p_env), Unilang::Deref(p_env).GetAnchorPtr())); 94 | } 95 | 96 | 97 | bool 98 | IsReferenceTerm(const TermNode& term) 99 | { 100 | return bool(TryAccessLeafAtom(term)); 101 | } 102 | 103 | bool 104 | IsUniqueTerm(const TermNode& term) 105 | { 106 | return ystdex::invoke_value_or(&TermReference::IsUnique, 107 | TryAccessLeafAtom(term), true); 108 | } 109 | 110 | bool 111 | IsModifiableTerm(const TermNode& term) 112 | { 113 | return ystdex::invoke_value_or(&TermReference::IsModifiable, 114 | TryAccessLeafAtom(term), 115 | !bool(term.Tags & TermTags::Nonmodifying)); 116 | } 117 | 118 | bool 119 | IsBoundLValueTerm(const TermNode& term) 120 | { 121 | return ystdex::invoke_value_or(&TermReference::IsReferencedLValue, 122 | TryAccessLeafAtom(term)); 123 | } 124 | 125 | bool 126 | IsUncollapsedTerm(const TermNode& term) 127 | { 128 | return ystdex::call_value_or(ystdex::compose(IsReferenceTerm, 129 | std::mem_fn(&TermReference::get)), 130 | TryAccessLeafAtom(term)); 131 | } 132 | 133 | } // namespace Unilang; 134 | 135 | -------------------------------------------------------------------------------- /src/TermNode.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021-2023 UnionTech Software Technology Co.,Ltd. 2 | 3 | #include "TermNode.h" // for stack, pair, lref; 4 | #include "Unilang.h" // for Unilang::Deref; 5 | #include // for ystdex::retry_on_cond; 6 | 7 | namespace Unilang 8 | { 9 | 10 | TermNode::Container 11 | TermNode::ConSub(const Container& con, allocator_type a) 12 | { 13 | Container res(a); 14 | 15 | if(!con.empty()) 16 | { 17 | stack, lref>> remained(a), tms(a); 18 | 19 | for(const auto& sub : con) 20 | { 21 | res.emplace_back(); 22 | remained.emplace(res.back(), sub), 23 | tms.emplace(res.back(), sub), 24 | res.back().Tags = sub.Tags; 25 | ystdex::retry_on_cond([&]() noexcept{ 26 | return !remained.empty(); 27 | }, [&]{ 28 | auto& dst_con(remained.top().first.get().GetContainerRef()); 29 | auto& src(remained.top().second.get()); 30 | 31 | remained.pop(); 32 | for(auto i(src.rbegin()); i != src.rend(); ++i) 33 | { 34 | dst_con.emplace_front(); 35 | 36 | auto& nterm(dst_con.front()); 37 | auto& tm(*i); 38 | 39 | remained.emplace(nterm, tm), 40 | tms.emplace(nterm, tm), 41 | nterm.Tags = tm.Tags; 42 | } 43 | }); 44 | ystdex::retry_on_cond([&]() noexcept{ 45 | return !tms.empty(); 46 | }, [&]{ 47 | tms.top().first.get().Value = tms.top().second.get().Value; 48 | tms.pop(); 49 | }); 50 | } 51 | } 52 | return res; 53 | } 54 | 55 | void 56 | TermNode::ClearContainer() noexcept 57 | { 58 | while(!container.empty()) 59 | { 60 | const auto i(container.begin()); 61 | 62 | Unilang::TransferSubterms(*this, i, Unilang::Deref(i)); 63 | container.erase(i); 64 | } 65 | assert(IsLeaf(*this) && "Failed clearing the container of a term."); 66 | } 67 | 68 | } // namespace Unilang; 69 | 70 | -------------------------------------------------------------------------------- /src/UnilangFFI.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020-2022 UnionTech Software Technology Co.,Ltd. 2 | 3 | #if __GNUC__ 4 | # pragma GCC diagnostic push 5 | # pragma GCC diagnostic ignored "-Wfloat-equal" 6 | #endif 7 | #include "UnilangFFI.h" // for Context, Unilang::allocate_shared, IsTyped; 8 | #include YFM_YSLib_Core_YCoreUtilities // for YSLib::unique_ptr_from, 9 | // YSLib::CheckUpperBound; 10 | #if __GNUC__ 11 | # pragma GCC diagnostic pop 12 | #endif 13 | #if YCL_Win32 14 | # include YFM_Win32_YCLib_MinGW32 // for ::HMODULE; 15 | # include YFM_Win32_YCLib_NLS // for platform_ex::UTF8ToWCS; 16 | #else 17 | // XXX: Must use dlfcn. Add check? 18 | # include // for ::dlerror, RTLD_LAZY, RTLD_GLOBAL, ::dlopen, 19 | // ::dlclose, ::dlsym; 20 | # include YFM_YCLib_NativeAPI // for YCL_CallGlobal; 21 | #endif 22 | #include "Exception.h" // for UnilangException, TypeError, 23 | // ThrowListTypeErrorForNonList, ListTypeError, InvalidSyntax; 24 | #include // for ::ffi_type; 25 | #include "Forms.h" // for ContextHandler, RetainN, RegisterUnary, 26 | // RegisterStrict; 27 | #include "Evaluation.h" // for Strict; 28 | 29 | namespace Unilang 30 | { 31 | 32 | namespace 33 | { 34 | 35 | struct LibraryHandleDelete final 36 | { 37 | #if YCL_Win32 38 | using pointer = ::HMODULE; 39 | #else 40 | using pointer = void*; 41 | #endif 42 | 43 | void 44 | operator()(pointer h) const noexcept 45 | { 46 | #if YCL_Win32 47 | if(h) 48 | YCL_TraceCallF_Win32(FreeModule, h); 49 | #else 50 | if(h && YCL_CallGlobal(dlclose, h) != 0) 51 | { 52 | // TODO: Report error. 53 | } 54 | #endif 55 | } 56 | }; 57 | 58 | using UniqueLibraryHandle = YSLib::unique_ptr_from; 59 | 60 | class DynamicLibrary final 61 | { 62 | public: 63 | using FPtr = void(*)(); 64 | 65 | string Name; 66 | 67 | private: 68 | UniqueLibraryHandle h_library; 69 | 70 | public: 71 | YB_NONNULL(2) 72 | DynamicLibrary(const char*); 73 | 74 | YB_ATTR_nodiscard YB_ATTR_returns_nonnull FPtr 75 | LookupFunctionPtr(const string&) const; 76 | }; 77 | 78 | 79 | DynamicLibrary::DynamicLibrary(const char* filename) 80 | : Name(filename), 81 | #if YCL_Win32 82 | h_library(::LoadLibraryW(platform_ex::UTF8ToWCS(filename).c_str())) 83 | { 84 | if(!h_library) 85 | throw UnilangException(ystdex::sfmt( 86 | "Failed loading dynamic library named '%s'.", filename)); 87 | } 88 | #else 89 | h_library(::dlopen((yunused(::dlerror()), filename), 90 | RTLD_LAZY | RTLD_GLOBAL)) 91 | { 92 | if(const auto p_err = ::dlerror()) 93 | throw UnilangException(ystdex::sfmt("Failed loading dynamic library" 94 | " named '%s' with error '%s'.", filename, p_err)); 95 | } 96 | #endif 97 | 98 | DynamicLibrary::FPtr 99 | DynamicLibrary::LookupFunctionPtr(const string& fn) const 100 | { 101 | if(const auto h = h_library.get()) 102 | { 103 | #if YCL_Win32 104 | if(const auto p_fn = ::GetProcAddress(h, fn.c_str())) 105 | return FPtr(p_fn); 106 | throw UnilangException(ystdex::sfmt("Failed looking up symbol '%s' in the" 107 | " library '%s'.", fn.c_str(), Name.c_str())); 108 | #else 109 | yunused(::dlerror()); 110 | 111 | const auto p_fn(::dlsym(h, fn.c_str())); 112 | 113 | if(const auto p_err = dlerror()) 114 | throw UnilangException(ystdex::sfmt("Failed looking up symbol '%s'" 115 | " in the library '%s': %s.", fn.c_str(), Name.c_str(), p_err)); 116 | if(p_fn) 117 | { 118 | FPtr v; 119 | static_assert(sizeof(v) == sizeof(p_fn), "Invalid platform found."); 120 | 121 | std::memcpy(&v, &p_fn, sizeof(void*)); 122 | return v; 123 | } 124 | throw UnilangException(ystdex::sfmt("Null value of symbol '%s' found in" 125 | " the library '%s'.", fn.c_str(), Name.c_str())); 126 | #endif 127 | } 128 | throw UnilangException("Invalid library handle found."); 129 | } 130 | 131 | 132 | struct FFICodec final 133 | { 134 | ::ffi_type libffi_type; 135 | 136 | ReductionStatus(*decode)(TermNode&, const void*); 137 | void(*encode)(const TermNode&, void*); 138 | }; 139 | 140 | 141 | YB_ATTR_nodiscard YB_NONNULL(2) ReductionStatus 142 | FFI_Decode_string(TermNode& term, const void* buf) 143 | { 144 | if(const auto s = *static_cast(buf)) 145 | term.Value = string(s, term.get_allocator()); 146 | term.Value = string(term.get_allocator()); 147 | return ReductionStatus::Clean; 148 | } 149 | 150 | YB_NONNULL(2) void 151 | FFI_Encode_string(const TermNode& term, void* buf) 152 | { 153 | *static_cast(buf) 154 | = &Unilang::ResolveRegular(term)[0]; 155 | } 156 | 157 | YB_ATTR_nodiscard YB_NONNULL(2) ReductionStatus 158 | FFI_Decode_void(TermNode& term, const void*) 159 | { 160 | return ReduceReturnUnspecified(term); 161 | } 162 | 163 | YB_NONNULL(2) void 164 | FFI_Encode_void(const TermNode& term, void*) 165 | { 166 | if(!(Unilang::ResolveRegular(term) 167 | == ValueToken::Unspecified)) 168 | throw TypeError("Only inert can be cast to C void."); 169 | } 170 | 171 | template 172 | struct FFI_Codec_Direct final 173 | { 174 | YB_ATTR_nodiscard YB_NONNULL(2) static ReductionStatus 175 | Decode(TermNode& term, const void* buf) 176 | { 177 | term.Value = *static_cast(buf); 178 | return ReductionStatus::Clean; 179 | } 180 | 181 | YB_NONNULL(2) static void 182 | Encode(const TermNode& term, void* buf) 183 | { 184 | *static_cast<_type*>(buf) = Unilang::ResolveRegular(term); 185 | } 186 | }; 187 | 188 | YB_ATTR_nodiscard YB_NONNULL(2) ReductionStatus 189 | FFI_Decode_pointer(TermNode& term, const void* buf) 190 | { 191 | if(const auto p = *static_cast(buf)) 192 | term.Value = p; 193 | else 194 | term.Value.Clear(); 195 | return ReductionStatus::Clean; 196 | } 197 | 198 | YB_NONNULL(2) void 199 | FFI_Encode_pointer(const TermNode& term, void* buf) 200 | { 201 | Unilang::ResolveTerm([&](const TermNode& nd, bool has_ref){ 202 | if(IsEmpty(nd)) 203 | *static_cast(buf) = {}; 204 | else if(!IsList(nd)) 205 | { 206 | if(const auto p = nd.Value.AccessPtr()) 207 | *static_cast(buf) = &(*p)[0]; 208 | else 209 | throw TypeError(ystdex::sfmt("Unsupported type '%s' to encode" 210 | " pointer found.", nd.Value.type().name())); 211 | } 212 | else 213 | ThrowListTypeErrorForNonList(nd, has_ref); 214 | }, term); 215 | } 216 | 217 | 218 | using codecs_type = map; 219 | 220 | codecs_type& 221 | get_codecs_ref(codecs_type::allocator_type a) 222 | { 223 | static map m{{ 224 | {"string", FFICodec{::ffi_type_pointer, FFI_Decode_string, 225 | FFI_Encode_string}}, 226 | #define NPL_Impl_FFI_SType_(t) \ 227 | {#t, FFICodec{::ffi_type_##t, FFI_Decode_##t, FFI_Encode_##t}} 228 | #define NPL_Impl_FFI_DType_(t, _tp) \ 229 | {#t, FFICodec{::ffi_type_##t, FFI_Codec_Direct<_tp>::Decode, \ 230 | FFI_Codec_Direct<_tp>::Encode}} 231 | NPL_Impl_FFI_SType_(void), 232 | NPL_Impl_FFI_DType_(sint, int), 233 | NPL_Impl_FFI_SType_(pointer), 234 | NPL_Impl_FFI_DType_(uint8, std::uint8_t), 235 | NPL_Impl_FFI_DType_(sint8, std::int8_t), 236 | NPL_Impl_FFI_DType_(uint16, std::uint16_t), 237 | NPL_Impl_FFI_DType_(sint16, std::int16_t), 238 | NPL_Impl_FFI_DType_(uint32, std::uint32_t), 239 | NPL_Impl_FFI_DType_(sint32, std::int32_t), 240 | NPL_Impl_FFI_DType_(uint64, std::uint64_t), 241 | NPL_Impl_FFI_DType_(float, float), 242 | NPL_Impl_FFI_DType_(double, double) 243 | #undef NPL_Impl_FFI_DType_ 244 | #undef NPL_Impl_FFI_SType_ 245 | }, a}; 246 | 247 | return m; 248 | } 249 | 250 | FFICodec& 251 | get_codec(const string& t) 252 | { 253 | auto& codecs(get_codecs_ref(t.get_allocator())); 254 | const auto i(codecs.find(t)); 255 | 256 | if(i != codecs.cend()) 257 | return i->second; 258 | throw UnilangException(ystdex::sfmt("Unsupported FFI type '%s' found.", 259 | t.c_str())); 260 | } 261 | 262 | ::ffi_abi 263 | get_abi(string abi) 264 | { 265 | if(abi == "FFI_DEFAULT_ABI") 266 | return FFI_DEFAULT_ABI; 267 | throw UnilangException( 268 | ystdex::sfmt("Unsupported FFI ABI '%s' found.", abi.c_str())); 269 | } 270 | 271 | size_t 272 | align_offset(size_t offset, size_t alignment) noexcept 273 | { 274 | assert(alignment > 0); 275 | return offset + (alignment - offset % alignment) % alignment; 276 | } 277 | 278 | 279 | class CallInterface final 280 | { 281 | private: 282 | size_t n_params; 283 | 284 | public: 285 | vector param_codecs; 286 | vector<::ffi_type*> param_types; 287 | FFICodec ret_codec; 288 | size_t buffer_size; 289 | ::ffi_cif cif; 290 | 291 | CallInterface(const string& abi, const string& s_rtype, 292 | const vector& s_ptypes) 293 | : n_params(s_ptypes.size()), param_codecs(s_ptypes.get_allocator()), 294 | param_types(s_ptypes.get_allocator()), ret_codec(get_codec(s_rtype)), 295 | buffer_size(ret_codec.libffi_type.size) 296 | { 297 | if(!ret_codec.decode) 298 | throw UnilangException(ystdex::sfmt("The type '%s' is not allowed" 299 | " as a return type.", s_rtype.c_str())); 300 | param_codecs.reserve(n_params), 301 | param_types.reserve(n_params); 302 | for(const auto& s_ptype : s_ptypes) 303 | { 304 | auto& codec(get_codec(s_ptype)); 305 | 306 | param_codecs.push_back(codec); 307 | if(!codec.encode) 308 | throw UnilangException(ystdex::sfmt("The type '%s' is not" 309 | " allowed in the parameter list.", s_ptype.c_str())); 310 | 311 | auto& t(codec.libffi_type); 312 | 313 | param_types.push_back(&t); 314 | buffer_size = align_offset(buffer_size, t.alignment) + t.size; 315 | } 316 | switch(::ffi_prep_cif(&cif, get_abi(abi), YSLib::CheckUpperBound< 317 | unsigned>(n_params), &ret_codec.libffi_type, param_types.data())) 318 | { 319 | case FFI_OK: 320 | break; 321 | case FFI_BAD_ABI: 322 | throw UnilangException("FFI_BAD_ABI"); 323 | case FFI_BAD_TYPEDEF: 324 | throw UnilangException("FFI_BAD_TYPEDEF"); 325 | default: 326 | throw UnilangException("Unknown error in '::ffi_prep_cif' found."); 327 | } 328 | } 329 | 330 | size_t 331 | GetBufferSize() const noexcept 332 | { 333 | return buffer_size; 334 | } 335 | size_t 336 | GetParameterCount() const noexcept 337 | { 338 | return n_params; 339 | } 340 | }; 341 | 342 | CallInterface& 343 | EnsureValidCIF(const shared_ptr& p_cif) 344 | { 345 | if(p_cif) 346 | return *p_cif; 347 | // NOTE: This should normally not happen. 348 | throw std::invalid_argument("Invalid call interface record pointer found."); 349 | } 350 | 351 | 352 | struct FFIClosureDelete final 353 | { 354 | void 355 | operator()(void* h) const noexcept 356 | { 357 | ::ffi_closure_free(h); 358 | } 359 | }; 360 | 361 | 362 | class Callback; 363 | 364 | struct FFICallback final 365 | { 366 | ::ffi_closure Closure; 367 | Context* ContextPtr; 368 | const ContextHandler* HandlerPtr; 369 | Callback* InfoPtr; 370 | }; 371 | 372 | static_assert(std::is_standard_layout(), 373 | "Invalid FFI closure found."); 374 | 375 | void 376 | FFICallbackEntry(::ffi_cif*, void*, void**, void*); 377 | 378 | class Callback final 379 | { 380 | private: 381 | shared_ptr cif_ptr; 382 | void* code; 383 | YSLib::unique_ptr cb_ptr; 384 | 385 | public: 386 | Callback(Context& ctx, const ContextHandler& h, 387 | const shared_ptr& p_cif) 388 | : cif_ptr(p_cif), cb_ptr(static_cast( 389 | ::ffi_closure_alloc(sizeof(FFICallback), &code))) 390 | { 391 | if(cb_ptr) 392 | { 393 | auto& cif(EnsureValidCIF(p_cif)); 394 | yunseq(cb_ptr->ContextPtr = &ctx, cb_ptr->HandlerPtr = &h, 395 | cb_ptr->InfoPtr = this); 396 | 397 | const auto status(::ffi_prep_closure_loc(&cb_ptr->Closure, &cif.cif, 398 | FFICallbackEntry, cb_ptr.get(), code)); 399 | 400 | if(status != ::FFI_OK) 401 | throw UnilangException(ystdex::sfmt("Unkown error '%zu' in" 402 | " 'ffi_prep_closure_loc' found.", size_t(status))); 403 | } 404 | else 405 | throw std::bad_alloc(); 406 | } 407 | 408 | YB_ATTR_nodiscard YB_PURE const CallInterface& 409 | GetCallInterface() const noexcept 410 | { 411 | return *cif_ptr; 412 | } 413 | }; 414 | 415 | 416 | void 417 | FFICallbackEntry(::ffi_cif*, void* ret, void** args, void* user_data) 418 | { 419 | const auto& cb(*static_cast(user_data)); 420 | auto& ctx(*cb.ContextPtr); 421 | const auto a(ctx.get_allocator()); 422 | auto p_term(Unilang::allocate_shared(a)); 423 | auto& term(*p_term); 424 | 425 | ctx.SetupFront([&, args, ret, p_term](Context& c){ 426 | const auto& h(*cb.HandlerPtr); 427 | auto& cif(cb.InfoPtr->GetCallInterface()); 428 | const auto n_params(cif.GetParameterCount()); 429 | 430 | for(size_t idx(0); idx < n_params; ++idx) 431 | { 432 | TermNode tm(a); 433 | 434 | cif.param_codecs[idx].decode(tm, args[idx]); 435 | term.GetContainerRef().emplace_back(std::move(tm)); 436 | } 437 | c.SetupFront([&, ret, p_term]{ 438 | cif.ret_codec.encode(term, ret); 439 | return ReductionStatus::Partial; 440 | }); 441 | return h(term, c); 442 | }); 443 | EnvironmentGuard gd(ctx, Unilang::SwitchToFreshEnvironment(ctx)); 444 | 445 | ctx.RewriteTerm(term); 446 | } 447 | 448 | } // unnamed namespace; 449 | 450 | 451 | void 452 | InitializeFFI(Interpreter& intp) 453 | { 454 | auto& ctx(intp.Main); 455 | using namespace Forms; 456 | 457 | RegisterUnary<>(ctx, "ffi-library?", 458 | ComposeReferencedTermOp([](const TermNode& term) noexcept{ 459 | return IsTyped(term); 460 | })); 461 | RegisterUnary(ctx, "ffi-load-library", 462 | [](const string& filename){ 463 | return DynamicLibrary(filename.c_str()); 464 | }); 465 | RegisterUnary<>(ctx, "ffi-call-interface?", 466 | ComposeReferencedTermOp([](const TermNode& term) noexcept{ 467 | return IsTyped>(term); 468 | })); 469 | RegisterStrict(ctx, "ffi-make-call-interface", [](TermNode& term){ 470 | RetainN(term, 3); 471 | 472 | auto i(std::next(term.begin())); 473 | const auto& abi(Unilang::ResolveRegular(*i)); 474 | const auto& 475 | s_ret_type(Unilang::ResolveRegular(*++i)); 476 | const auto& s_param_types_term(*++i); 477 | 478 | if(IsList(s_param_types_term)) 479 | { 480 | vector s_param_types(term.get_allocator()); 481 | 482 | s_param_types.reserve(s_param_types_term.size()); 483 | for(const auto& t : s_param_types_term) 484 | s_param_types.push_back(Unilang::ResolveRegular(t)); 485 | term.Value = std::allocate_shared( 486 | term.get_allocator(), abi, s_ret_type, s_param_types); 487 | return ReductionStatus::Clean; 488 | } 489 | throw ListTypeError("Expected a list for the 3rd parameter."); 490 | }); 491 | RegisterStrict(ctx, "ffi-make-applicative", [](TermNode& term){ 492 | RetainN(term, 3); 493 | 494 | auto i(std::next(term.begin())); 495 | const auto& 496 | lib(Unilang::ResolveRegular(*i)); 497 | const auto& fn(Unilang::ResolveRegular(*++i)); 498 | auto& cif(EnsureValidCIF(Unilang::ResolveRegular>(*++i))); 500 | const auto p_fn(lib.LookupFunctionPtr(fn)); 501 | 502 | term.Value = ContextHandler(FormContextHandler([&, p_fn](TermNode& tm){ 503 | if(IsBranch(tm)) 504 | { 505 | const auto n_params(cif.GetParameterCount()); 506 | 507 | RetainN(tm, n_params); 508 | 509 | const auto buffer_size(cif.GetBufferSize()); 510 | const auto p_buf( 511 | ystdex::make_unique_default_init( 512 | (buffer_size + sizeof(std::int64_t) - 1) 513 | / sizeof(std::int64_t))); 514 | const auto p_param_ptrs( 515 | ystdex::make_unique_default_init(n_params)); 516 | void* rptr( 517 | ystdex::aligned_store_cast(p_buf.get())); 518 | size_t offset(cif.ret_codec.libffi_type.size); 519 | auto i_tm(tm.begin()); 520 | 521 | for(size_t idx(0); idx < n_params; ++idx) 522 | { 523 | auto& codec(cif.param_codecs[idx]); 524 | const auto& t(codec.libffi_type); 525 | 526 | offset = align_offset(offset, t.alignment); 527 | p_param_ptrs[idx] = ystdex::aligned_store_cast< 528 | unsigned char*>(p_buf.get()) + offset; 529 | cif.param_codecs[idx].encode(*++i_tm, p_param_ptrs[idx]); 530 | offset += t.size; 531 | } 532 | assert(offset == buffer_size); 533 | ::ffi_call(&cif.cif, p_fn, rptr, p_param_ptrs.get()); 534 | return cif.ret_codec.decode(tm, rptr); 535 | } 536 | else 537 | throw InvalidSyntax("Invalid function application found."); 538 | }, Strict)); 539 | return ReductionStatus::Clean; 540 | }); 541 | RegisterStrict(ctx, "ffi-make-callback", [](TermNode& term, Context& c){ 542 | auto i(std::next(term.begin())); 543 | const auto& h(Unilang::ResolveRegular(*i)); 544 | 545 | term.Value = YSLib::allocate_shared(term.get_allocator(), c, 546 | h, Unilang::ResolveRegular>( 547 | *++i)); 548 | return ReductionStatus::Clean; 549 | }); 550 | } 551 | 552 | } // namespace Unilang; 553 | 554 | -------------------------------------------------------------------------------- /std.txt: -------------------------------------------------------------------------------- 1 | "SPDX-FileCopyrightText: 2022 UnionTech Software Technology Co.,Ltd.", 2 | "Unilang standard library initialization."; 3 | "This file is a component of Unilang installation.", 4 | "To keep the environment well-formed,", 5 | " users shall not modify the content in this file."; 6 | 7 | $def! std.classes $let () 8 | ( 9 | $def! impl__ $provide! 10 | ( 11 | class? 12 | make-class 13 | object? 14 | make-object 15 | $access 16 | ) 17 | ( 18 | $def! (encapsulate-class% class? decapsulate-class) 19 | () make-encapsulation-type; 20 | $defl! make-class (base ctor) 21 | encapsulate-class% (list base ctor); 22 | $defl! ctor-of (c) first (rest% (decapsulate-class c)); 23 | $defl! base-of (c) first (decapsulate-class c); 24 | $defl! apply-ctor (c self args) 25 | apply (ctor-of c) (list* self args); 26 | $def! (encapsulate-object% object? decapsulate-object) 27 | () make-encapsulation-type; 28 | $defl! make-object (c .args) 29 | ( 30 | $def! self () make-environment; 31 | $def! base base-of c; 32 | $if (null? base) () (apply-ctor base self ()); 33 | apply-ctor c self args; 34 | encapsulate-object% (move! self) 35 | ); 36 | $defv%! $access (&o &id) d 37 | eval% id (decapsulate-object (eval% o d)); 38 | ); 39 | () lock-current-environment 40 | ); 41 | 42 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # SPDX-FileCopyrightText: 2020-2023 UnionTech Software Technology Co.,Ltd. 3 | # shellcheck disable=2016 4 | 5 | set -e 6 | 7 | OUT=/tmp/out 8 | ERR=/tmp/err 9 | 10 | touch "$OUT" 11 | touch "$ERR" 12 | 13 | : "${UNILANG:=./unilang}" 14 | 15 | if test -d "$UNILANG" || test ! -x "$UNILANG"; then 16 | echo "ERROR: Wrong '$UNILANG' found as the interpreter." 17 | exit 1 18 | fi 19 | 20 | call_intp() 21 | { 22 | set +e 23 | echo > "$ERR" 24 | # echo "$1" | "$UNILANG" 1> "$OUT" 2> "$ERR" 25 | "$UNILANG" -e "$1" 1> "$OUT" 2> "$ERR" 26 | set -e 27 | } 28 | 29 | # NOTE: Test cases should print no errors. 30 | run_case() 31 | { 32 | echo "Running case:" "$1" 33 | if ! (call_intp "$1") || test -s "$ERR"; then 34 | echo "FAIL." 35 | echo "Error:" 36 | cat "$ERR" 37 | else 38 | echo "PASS." 39 | fi 40 | } 41 | 42 | if test -n "$PTC"; then 43 | # NOTE: Test cases should print no errors. 44 | echo "The following case are expected to be non-terminating." 45 | echo "However, the maximum memory consumption is expected constant." 46 | echo "Please exit manually by SIGINT." 47 | run_case '$defl! f (n) $if #t (f n); f 1' 48 | fi 49 | 50 | # Sanity. 51 | run_case 'display' 52 | 53 | # Documented examples. 54 | run_case 'load "test.txt"' 55 | 56 | -------------------------------------------------------------------------------- /test/nested.txt: -------------------------------------------------------------------------------- 1 | info "The following case test the hosted nested call safety."; 2 | "NOTE", "Debug versions of interpreter may be extremely slow."; 3 | 4 | $defl! make-list (l n) 5 | $unless (eqv? n 0) (list% (make-list (move! l) (- n 1))); 6 | 7 | subinfo "term destroy;" " fixed since V0.6.109"; 8 | $let () 9 | ( 10 | make-list () 50000; 11 | ); 12 | 13 | subinfo "term copy;" " fixed since V0.11.92"; 14 | $let () 15 | ( 16 | $def! x make-list () 120000; 17 | $def! y x 18 | ); 19 | 20 | -------------------------------------------------------------------------------- /valgrind.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # SPDX-FileCopyrightText: 2022-2023 UnionTech Software Technology Co.,Ltd. 3 | # Requires: valgrind. 4 | # Test script for running the interpreter in release mode using valgrind. 5 | # The default tool is callgrind. 6 | 7 | : "${CONF:="release"}" 8 | : "${OUTPUT:="build/.$CONF/callgrind.out"}" 9 | : "${LOGOPT:="--log-file=build/.$CONF/callgrind.log"}" 10 | : "${TOOLOPT:="--tool=callgrind --callgrind-out-file=$OUTPUT --dump-instr=yes \ 11 | --collect-jumps=yes"}" 12 | : "${UNILANG:="build/.$CONF/unilang.exe"}" 13 | 14 | echo 'Ready to run the command for Unilang interpreter "'"$UNILANG"'".' 15 | echo "Using valgrind options: '$LOGOPT $TOOLOPT'." 16 | echo "Using interpreter options: '$*'." 17 | 18 | # XXX: Value of several variables may contain whitespaces. 19 | # shellcheck disable=2086 20 | valgrind $LOGOPT $TOOLOPT --fullpath-after= "$UNILANG" "$@" 21 | 22 | echo 'Output file is placed in "'"$OUTPUT"'".' 23 | echo 'Done.' 24 | 25 | --------------------------------------------------------------------------------