Compare commits
No commits in common. "master" and "master" have entirely different histories.
|
@ -1,5 +0,0 @@
|
|||
# libcubescript CI scripts
|
||||
|
||||
These CI scripts are meant to provide a helper environment for continuous
|
||||
integration. They are written to be used only in the CI environment, not
|
||||
general purpose environments.
|
186
.ci/build-cs
186
.ci/build-cs
|
@ -1,186 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
expected_triplet=$TARGET
|
||||
|
||||
if [ -z "$expected_triplet" ]; then
|
||||
echo "ERROR: target triplet not provided!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
current_triplet=`$CC -dumpmachine`
|
||||
|
||||
if [ "$CC" = "clang" ]; then
|
||||
export CXX="clang++"
|
||||
# clang is busted in CI for some reason
|
||||
#export CXXFLAGS="-stdlib=libc++"
|
||||
elif [ "$CC" = "gcc" ]; then
|
||||
export CXX="g++"
|
||||
else
|
||||
export CXX="g++-10"
|
||||
fi
|
||||
|
||||
if [ "$TARGET" != "darwin" -a "$CC" != "clang" -a "$expected_triplet" != "$current_triplet" ]; then
|
||||
cross=yes
|
||||
export CC="${expected_triplet}-${CC}"
|
||||
export CXX="${expected_triplet}-${CXX}"
|
||||
export STRIP="${expected_triplet}-strip"
|
||||
export AR="${expected_triplet}-ar"
|
||||
export AS="${expected_triplet}-as"
|
||||
else
|
||||
export STRIP="strip"
|
||||
export AR="ar"
|
||||
fi
|
||||
|
||||
meson_system="linux"
|
||||
|
||||
case "${expected_triplet}" in
|
||||
darwin)
|
||||
# special case here
|
||||
meson_system="darwin"
|
||||
;;
|
||||
x86_64*)
|
||||
meson_cpu_family="x86_64"
|
||||
meson_cpu="x86_64"
|
||||
meson_endian="little"
|
||||
case "${expected_triplet}" in
|
||||
*w64*)
|
||||
meson_system="windows"
|
||||
;;
|
||||
*)
|
||||
qemu_cpu="x86_64"
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
i686*)
|
||||
meson_cpu_family="x86"
|
||||
meson_cpu="i686"
|
||||
meson_endian="little"
|
||||
qemu_cpu="i386"
|
||||
;;
|
||||
powerpc64le*)
|
||||
meson_cpu_family="ppc64"
|
||||
meson_cpu="ppc64le"
|
||||
meson_endian="little"
|
||||
qemu_cpu="ppc64le"
|
||||
;;
|
||||
powerpc64*)
|
||||
meson_cpu_family="ppc64"
|
||||
meson_cpu="ppc64"
|
||||
meson_endian="big"
|
||||
qemu_cpu="ppc64"
|
||||
;;
|
||||
powerpcle*)
|
||||
echo "ERROR: ppcle not supported in qemu"
|
||||
exit 1
|
||||
;;
|
||||
powerpc*)
|
||||
meson_cpu_family="ppc"
|
||||
meson_cpu="ppc"
|
||||
meson_endian="big"
|
||||
qemu_cpu="ppc"
|
||||
;;
|
||||
aarch64-*)
|
||||
meson_cpu_family="aarch64"
|
||||
meson_cpu="aarch64"
|
||||
meson_endian="little"
|
||||
qemu_cpu="aarch64"
|
||||
;;
|
||||
arm-*)
|
||||
meson_cpu_family="arm"
|
||||
meson_cpu="armv6l"
|
||||
meson_endian="little"
|
||||
qemu_cpu="arm"
|
||||
;;
|
||||
riscv64-*)
|
||||
meson_cpu_family="riscv64"
|
||||
meson_cpu="riscv64"
|
||||
meson_endian="little"
|
||||
qemu_cpu="riscv64"
|
||||
;;
|
||||
s390x*)
|
||||
meson_cpu_family="s390x"
|
||||
meson_cpu="s390x"
|
||||
meson_endian="big"
|
||||
qemu_cpu="s390x"
|
||||
;;
|
||||
mips-*)
|
||||
meson_cpu_family="mips"
|
||||
meson_cpu="mips"
|
||||
meson_endian="big"
|
||||
qemu_cpu="mips"
|
||||
;;
|
||||
m68k*)
|
||||
meson_cpu_family="m68k"
|
||||
meson_cpu="m68k"
|
||||
meson_endian="big"
|
||||
qemu_cpu="m68k"
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: Cross CPU unspecified"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
export PATH="$(pwd)/host_tools:$PATH"
|
||||
|
||||
if [ -n "$qemu_cpu" -a -n "$cross" ]; then
|
||||
echo ">> Preparing qemu..."
|
||||
# work around glibc being dumb
|
||||
# the cache format is not endian agnostic, so unless a dummy file exists
|
||||
# here, qemu will try to use host's and it will crash guest glibc on BE
|
||||
sudo mkdir -p /usr/${expected_triplet}/etc
|
||||
sudo touch /usr/${expected_triplet}/etc/ld.so.cache
|
||||
fi
|
||||
|
||||
echo ">> Building and testing cubescript..."
|
||||
|
||||
mkdir -p build
|
||||
cd build
|
||||
|
||||
args=""
|
||||
if [ -n "${cross}" ]; then
|
||||
if [ "${meson_system}" = "windows" ]; then
|
||||
# avoid having to look the dlls for these up
|
||||
export CXXFLAGS+=" -static-libgcc -static-libstdc++"
|
||||
# quiet wine exe wrapper
|
||||
cat << EOF > meson-exewrapper
|
||||
#!/bin/sh
|
||||
export WINEDEBUG=-all
|
||||
export WINEPREFIX="$(pwd)/.wine"
|
||||
export DISPLAY=
|
||||
wine "\$@"
|
||||
EOF
|
||||
else
|
||||
cat << EOF > meson-exewrapper
|
||||
#!/bin/sh
|
||||
qemu-${qemu_cpu} -L /usr/${expected_triplet} "\$@"
|
||||
EOF
|
||||
fi
|
||||
chmod +x meson-exewrapper
|
||||
cat << EOF > crossfile
|
||||
[binaries]
|
||||
c = '${CC}'
|
||||
cpp = '${CXX}'
|
||||
ar = '${AR}'
|
||||
as = '${AS}'
|
||||
strip = '${STRIP}'
|
||||
exe_wrapper = '$(pwd)/meson-exewrapper'
|
||||
|
||||
[host_machine]
|
||||
system = '${meson_system}'
|
||||
cpu_family = '${meson_cpu_family}'
|
||||
cpu = '${meson_cpu}'
|
||||
endian = '${meson_endian}'
|
||||
EOF
|
||||
args="${args} --cross-file=crossfile"
|
||||
fi
|
||||
if [ -n "$BUILDTYPE" ]; then
|
||||
args="${args} --buildtype=$BUILDTYPE"
|
||||
fi
|
||||
|
||||
meson .. -Dtests_cross=true ${args} || exit 1
|
||||
ninja all || exit 1
|
||||
ninja test || exit 1
|
||||
cd ..
|
||||
|
||||
exit 0
|
|
@ -1,23 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
unset CC CXX CC_FOR_BUILD CXX_FOR_BUILD
|
||||
|
||||
export PATH="$(pwd)/host_tools:$PATH"
|
||||
|
||||
echo ">> Building and testing cubescript..."
|
||||
|
||||
args=""
|
||||
if [ -n "$BUILDTYPE" ]; then
|
||||
args="${args} --buildtype=$BUILDTYPE"
|
||||
fi
|
||||
|
||||
mkdir -p build
|
||||
cd build
|
||||
|
||||
cmd.exe //C 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat' amd64 '&&' \
|
||||
meson .. ${args} '&&' \
|
||||
ninja all '&&' ninja test || exit 1
|
||||
|
||||
cd ..
|
||||
|
||||
exit 0
|
106
.ci/install-env
106
.ci/install-env
|
@ -1,106 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
expected_triplet=$TARGET
|
||||
|
||||
if [ -z "$expected_triplet" ]; then
|
||||
echo "ERROR: target triplet not provided!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ensure_tool() {
|
||||
command -v "$1" > /dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ERROR: Missing tool: $1"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
if [ "$(uname -s)" = "Linux" ]; then
|
||||
is_linux=yes
|
||||
fi
|
||||
|
||||
echo ">> Checking tools..."
|
||||
|
||||
ensure_tool gcc
|
||||
ensure_tool g++
|
||||
ensure_tool clang
|
||||
ensure_tool clang++
|
||||
|
||||
mkdir -p host_tools
|
||||
|
||||
echo ">> Updating package database..."
|
||||
|
||||
[ -n "$is_linux" ] && sudo apt-get update
|
||||
|
||||
echo ">> Installing meson..."
|
||||
|
||||
if [ -n "$is_linux" ]; then
|
||||
sudo apt-get install ninja-build
|
||||
else
|
||||
ninja_version=1.10.2
|
||||
cd host_tools
|
||||
wget "https://github.com/ninja-build/ninja/releases/download/v${ninja_version}/ninja-mac.zip" || exit 1
|
||||
tar xf ninja-mac.zip || exit 1
|
||||
rm ninja-mac.zip
|
||||
cd ..
|
||||
export PATH="$(pwd)/host_tools:$PATH"
|
||||
fi
|
||||
|
||||
if [ -n "$(command -v pip3)" ]; then
|
||||
sudo pip3 install meson || exit 1
|
||||
elif [ -n "$(command -v pip)" ]; then
|
||||
sudo pip install meson || exit 1
|
||||
else
|
||||
echo "ERROR: pip not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ensure_tool meson
|
||||
ensure_tool ninja
|
||||
|
||||
if [ "$(uname -s)" != "Linux" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "${CC/gcc}" != "${CC}" ]; then
|
||||
sudo apt-get install ${CC} ${CC/gcc/g++} || exit 1
|
||||
fi
|
||||
|
||||
current_triplet=`gcc -dumpmachine`
|
||||
|
||||
if [ "$expected_triplet" = "$current_triplet" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ">> Installing toolchain..."
|
||||
|
||||
if [ "$expected_triplet" = "x86_64-w64-mingw32" ]; then
|
||||
gcc_suffix="mingw-w64"
|
||||
extra_packages="wine"
|
||||
need_span=yes
|
||||
else
|
||||
gcc_suffix="${expected_triplet}"
|
||||
extra_packages="qemu-user"
|
||||
fi
|
||||
|
||||
sudo apt-get install ${CC}-${gcc_suffix} ${CC/gcc/g++}-${gcc_suffix} ${extra_packages} || exit 1
|
||||
|
||||
# gcc9 too old to provide its own span...
|
||||
if [ -n "$need_span" ]; then
|
||||
wget -O include/cubescript/span.hpp \
|
||||
https://raw.githubusercontent.com/tcbrindle/span/master/include/tcb/span.hpp
|
||||
|
||||
# custom config
|
||||
cat << EOF > include/cubescript/cubescript_conf_user.hh
|
||||
#include "span.hpp"
|
||||
|
||||
namespace cubescript {
|
||||
template<typename T>
|
||||
using span_type = tcb::span<T>;
|
||||
}
|
||||
|
||||
#define LIBCUBESCRIPT_CONF_USER_SPAN
|
||||
EOF
|
||||
fi
|
||||
|
||||
exit $?
|
|
@ -1,15 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
ninja_version=1.10.2
|
||||
|
||||
echo ">> Installing meson..."
|
||||
|
||||
mkdir -p host_tools
|
||||
|
||||
curl -L -o ninja.zip https://github.com/ninja-build/ninja/releases/download/v${ninja_version}/ninja-win.zip || exit 1
|
||||
7z x ninja.zip || exit 1
|
||||
mv ninja.exe host_tools
|
||||
|
||||
pip3 install meson
|
||||
|
||||
exit 0
|
|
@ -1,12 +0,0 @@
|
|||
*.py text eol=lf
|
||||
*.yml text eol=lf
|
||||
*.diff text eol=lf
|
||||
*.conf text eol=lf
|
||||
*.md text eol=lf
|
||||
*.cc text eol=lf
|
||||
*.hh text eol=lf
|
||||
*.txt text eol=lf
|
||||
*.build text eol=lf
|
||||
*.gitattributes text eol=lf
|
||||
*.gitignores text eol=lf
|
||||
*.wrap text eol=lf
|
|
@ -1,86 +0,0 @@
|
|||
name: CI
|
||||
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
name: Linux
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
env:
|
||||
TARGET: '${{ matrix.config.target }}'
|
||||
CC: '${{ matrix.config.cc }}'
|
||||
BUILDTYPE: '${{ matrix.config.buildtype }}'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
config:
|
||||
# x86_64: test gcc, clang, + release mode to catch assert bugs
|
||||
- { target: x86_64-linux-gnu, cc: gcc-10, buildtype: debugoptimized }
|
||||
- { target: x86_64-linux-gnu, cc: gcc-10, buildtype: release }
|
||||
- { target: x86_64-linux-gnu, cc: clang, buildtype: debugoptimized }
|
||||
# 32-bit x86
|
||||
- { target: i686-linux-gnu, cc: gcc-10, buildtype: debugoptimized }
|
||||
# all powerpc
|
||||
- { target: powerpc64le-linux-gnu, cc: gcc-10, buildtype: debugoptimized }
|
||||
- { target: powerpc64-linux-gnu, cc: gcc-10, buildtype: debugoptimized }
|
||||
- { target: powerpc-linux-gnu, cc: gcc-10, buildtype: debugoptimized }
|
||||
# aarch64 and arm
|
||||
- { target: aarch64-linux-gnu, cc: gcc-10, buildtype: debugoptimized }
|
||||
- { target: arm-linux-gnueabi, cc: gcc-10, buildtype: debugoptimized }
|
||||
# riscv64 and s390x
|
||||
- { target: riscv64-linux-gnu, cc: gcc-10, buildtype: debugoptimized }
|
||||
- { target: s390x-linux-gnu, cc: gcc-10, buildtype: debugoptimized }
|
||||
# mips, m68k
|
||||
- { target: mips-linux-gnu, cc: gcc-10, buildtype: debugoptimized }
|
||||
- { target: m68k-linux-gnu, cc: gcc-10, buildtype: debug }
|
||||
# x86_64 windows cross, release mode
|
||||
- { target: x86_64-w64-mingw32, cc: gcc, buildtype: release }
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Prepare environment
|
||||
run: bash ./.ci/install-env
|
||||
|
||||
- name: Build and test cubescript
|
||||
run: bash ./.ci/build-cs
|
||||
|
||||
windows:
|
||||
name: Windows
|
||||
runs-on: windows-2019
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Prepare environment
|
||||
run: bash ./.ci/install-env-windows
|
||||
|
||||
- name: Build and test cubescript
|
||||
run: bash ./.ci/build-cs-windows
|
||||
|
||||
mac:
|
||||
name: MacOS
|
||||
runs-on: macos-10.15
|
||||
|
||||
env:
|
||||
TARGET: 'darwin'
|
||||
CC: 'clang'
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Prepare environment
|
||||
run: bash ./.ci/install-env
|
||||
|
||||
- name: Build and test cubescript
|
||||
run: bash ./.ci/build-cs
|
|
@ -1,7 +1,4 @@
|
|||
subprojects/libostd
|
||||
*.o
|
||||
*.core
|
||||
*.so
|
||||
build/
|
||||
.idea/
|
||||
.vscode/
|
||||
doc/output
|
||||
|
|
65
COPYING.md
65
COPYING.md
|
@ -1,35 +1,54 @@
|
|||
# License
|
||||
|
||||
Libcubescript is provided to you under the terms of the zlib license, just
|
||||
like the source it was originally derived from.
|
||||
|
||||
The software is originally based on the CubeScript implementation in the Cube 2
|
||||
game/engine, which by now serves mostly as an inspiration, as the code has been
|
||||
largely rewritten (though isolated bits of the original source may remain).
|
||||
|
||||
For copyright holders beyond just CubeScript, please refer to Cube 2's original
|
||||
license file.
|
||||
Libcubescript is licensed under the University of Illinois/NCSA Open Source License,
|
||||
a permissive, non-copyleft, BSD style license. The license text goes as follows:
|
||||
|
||||
Copyright (c):
|
||||
|
||||
* 2001-2015 Wouter "aardappel" van Oortmerssen and Lee "eihrul" Salzman
|
||||
* 2016-2020 Daniel "q66" Kolesa
|
||||
* 2016 Daniel "q66" Kolesa
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
All rights reserved.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal with
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimers.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimers in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the names of libcubescript developers nor any contributors may be
|
||||
used to endorse or promote products derived from this Software without
|
||||
specific prior written permission.
|
||||
|
||||
**THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
|
||||
SOFTWARE.**
|
||||
|
||||
# Original copyright
|
||||
|
||||
The software is originally based on the CubeScript implementation in the Cube 2
|
||||
game/engine, which by now serves mostly as an inspiration, as the code has been
|
||||
largely rewritten (though isolated bits of the original source may remain); it
|
||||
was provided under the zlib license and was:
|
||||
|
||||
Copyright (c) 2001-2015 Wouter "aardappel" van Oortmerssen and Lee "eihrul" Salzman
|
||||
|
||||
For copyright holders beyond just CubeScript, please refer to Cube 2's original
|
||||
license file. Permission was given by the original copyright holders to re-license
|
||||
the source code under the same license as the rest of OctaForge projects and this
|
||||
note now serves to credit the original authors.
|
||||
|
||||
# Bundled source
|
||||
|
||||
|
|
187
README.md
187
README.md
|
@ -1,152 +1,91 @@
|
|||
# libcubescript 1.0.0 alpha1
|
||||
|
||||
[![Build Status](https://github.com/octaforge/libcubescript/actions/workflows/build.yaml/badge.svg)](https://github.com/octaforge/libcubescript/actions)
|
||||
# libcubescript
|
||||
|
||||
![CubeScript REPL](https://ftp.octaforge.org/q66/random/libcs_repl.gif)
|
||||
|
||||
## Overview
|
||||
|
||||
Cubescript is a minimal scripting language first introduced in the Cube FPS
|
||||
and carried over into derived games and game engines such as Sauerbraten.
|
||||
Originally being little more than a few hundred lines of code, serving
|
||||
primarily as the console and configuration file format of the game, it
|
||||
grew more advanced features as well as a bytecode VM.
|
||||
Libcubescript is an embeddable implementation of the CubeScript scripting
|
||||
language. CubeScript is the console/config language of the Cube engines/games
|
||||
(and derived engines/games). It's a simplistic language defined around the
|
||||
idea of everything being a string, with Lisp-like syntax (allowing various
|
||||
control structures to be defined as commands).
|
||||
|
||||
Nowadays, it is a minimal but relatively fully featured scripting language
|
||||
based around the concept that everything can be interpreted as a string.
|
||||
It excels at its original purpose as well as things like text preprocessing.
|
||||
It comes with a Lisp-like syntax and a variety of standard library functions.
|
||||
## Benefits and use cases
|
||||
|
||||
Libcubescript is a project that aims to provide an independent, improved,
|
||||
separate implementation of the language, available as a library, intended to
|
||||
satisfy the needs of the OctaForge project. It was originally forked from
|
||||
Cubescript as present in the Tesseract game/engine and gradually rewritten;
|
||||
right now, very little of the original code remains. At language level it is
|
||||
mostly compatible with the other implementations (although with a stricter
|
||||
parser and extra features), while the standard library does not aim to be
|
||||
fully compatible. Some features are also left up to the user to customize,
|
||||
so that it is not tied to game engines feature-wise.
|
||||
CubeScript is suitable for any use that calls for a simple scripting language
|
||||
that is easy to embed. It's particularly strong at macro processing, so it can
|
||||
be used as a preprocessor, or for any string-heavy use. Since it has descended
|
||||
from a console language for a video game, it can still be used for that very
|
||||
purpose, as well as a configuration file language.
|
||||
|
||||
Like the codebase it is derived from, it is available under the permissive
|
||||
zlib license, and therefore compatible with just about anything.
|
||||
Its thread-friendliness allows for usage in any context that requires parallel
|
||||
processing and involvement of the scripting system in it.
|
||||
|
||||
## Benefits and differences
|
||||
As far as benefits over the original implementation go, while it is based on
|
||||
the original implementation, it's largely rewritten; thus, it's gained many
|
||||
advantages, including:
|
||||
|
||||
There's a variety of things that set this implementation apart:
|
||||
* Independent implementation (can be embedded in any project)
|
||||
* No global state (multiple CubeScripts in a single program)
|
||||
* Modern C++17 API (no macros, use of strongly typed enums, lambdas, ranges etc.)
|
||||
* C++17 lambdas can be used as commands (including captures and type inference)
|
||||
* Error handling including recovery (protected call system similar to Lua)
|
||||
* Stricter parsing (strings cannot be left unfinished etc.)
|
||||
* Loop control statements (`break` and `continue`)
|
||||
* No manual memory mangement, values manage themselves
|
||||
* Clean codebase that is easy to read and contribute to
|
||||
* Support for arbitrary size integers and floats (can be set at compile time)
|
||||
* Allows building into a static or shared library, supports `-fvisibility=hidden`
|
||||
|
||||
* It's independent and can be embedded in any project
|
||||
* There is no global state, so you can have as many Cubescripts as you want,
|
||||
in one program
|
||||
* Written in C++20, following modern language conventions, both internally
|
||||
and at API level
|
||||
* That means the ability to use lambdas as commands, including captures,
|
||||
type inference and so on
|
||||
* There is a robust allocator system in place, and all memory the library
|
||||
uses is allocated through it; that gives you complete control over its
|
||||
memory (for tracking, sandboxing, limits, etc.)
|
||||
* A large degree of memory safety, with no manual management
|
||||
* Strings are interned, with a single reference counted instance of any
|
||||
string existing at a time, which lowers memory usage and simplifies its
|
||||
management
|
||||
* Minimal stack memory usage, which means no artificial limits on recursion
|
||||
depth as well as safe usage from threads and coroutines with small stacks
|
||||
* Errors will no longer cause the interpreter to march on, instead acting
|
||||
like real errors
|
||||
* Protected calls allow you to catch errors in a similar way to exceptions,
|
||||
and nearly every error can be caught
|
||||
* Stricter parsing, with things like unfinished strings being caught
|
||||
* Loops now have `break` and `continue` statements
|
||||
* Customizable integer and floating point types
|
||||
* Full support for symbol visibility in API
|
||||
* Highly portable and cross-platform, no dependencies other than a compiler
|
||||
* Clean codebase that is easy to pick up and contribute to
|
||||
There are some features that are a work in progress and will come later:
|
||||
|
||||
More features and enhancements are planned, such as:
|
||||
* More helpful debug information (proper line infos at both parse and run time)
|
||||
* A degree of thread safety (see below)
|
||||
* Custom allocator support (control over how heap memory is allocated)
|
||||
* Coroutines
|
||||
|
||||
* Improved support for debugging information (line information tracking
|
||||
at runtime rather than just compile-time)
|
||||
* Thread safety
|
||||
The API is currently very unstable, as is the actual codebase. Therefore you
|
||||
should not use the project in production environments just yet, but you're
|
||||
also free to experiment - feedback is welcome.
|
||||
|
||||
Right now, the codebase is unstable, but quickly approaching production
|
||||
readiness. You are encouraged to test things and report bugs; contributions
|
||||
of any kind are also welcome (you can use pull requests in our Gitea instance
|
||||
as well as the GitHub mirror).
|
||||
**The project is also open for contributions.** You can use pull requests on
|
||||
GitHub and there is also a discussion channel `#octaforge` on FreeNode; this
|
||||
project is a part of the larger OctaForge umbrella.
|
||||
|
||||
Our primary means of communication is the `#octaforge` IRC channel on OFTC.
|
||||
## Threads and coroutines
|
||||
|
||||
### Threads
|
||||
*(In progress)*
|
||||
|
||||
The API provides a concept of threads. The first created thread is the main
|
||||
thread, which owns all variables and most state. Based on the main thread
|
||||
you can create side threads, which share a lot of state with the main thread
|
||||
but have their own call stack.
|
||||
Libcubescript supports integration with coroutines and threads by providing a
|
||||
concept of threads itself. You can create a thread (child state) using the
|
||||
main state and it will share global data with the main state, but it also
|
||||
has its own call stack.
|
||||
|
||||
In the future, accesses to "global" state (the state shared between threads)
|
||||
will be made thread safe.
|
||||
The "global" state is thread safe, allowing concurrent access from multiple
|
||||
threads. The "local" state can be yielded as a part of the coroutine without
|
||||
affecting any other threads.
|
||||
|
||||
That means you will be able to use the library in multithreaded contexts, as
|
||||
long as you make sure to only use any Cubescript thread from at most one
|
||||
real thread at a time (accesses to thread state will not be thread-safe).
|
||||
|
||||
Right now, this at least means the library is coroutine-safe. You can call
|
||||
into a Cubescript thread inside a coroutine, yield somewhere mid-command,
|
||||
and still be able to access the state safely through other Cubescript
|
||||
threads. Once you resume the coroutine, it will continue where it left
|
||||
off, without anything being wrong.
|
||||
|
||||
Since strings are interned and reference counted, this is also geared
|
||||
towards thread safety - any API returning a string will give you your own
|
||||
reference, which means nothing can free it while you are still using it.
|
||||
Similarly, things taking string references will generally increment the
|
||||
count for their own purposes. This all happens automatically thanks to
|
||||
C++'s scoped value handling.
|
||||
This functionality is not exposed into the language itself, but it can be
|
||||
utilized in the outside native code.
|
||||
|
||||
## Building and usage
|
||||
|
||||
The library has absolutely no dependencies other than a C++20 compiler,
|
||||
similarly there are no dependencies on system or architecture specific
|
||||
things, so it should work on any OS and any CPU.
|
||||
The only dependency is libostd:
|
||||
|
||||
The C++20 support does not have to be complete. These are the baselines
|
||||
(which are ensured by the CI):
|
||||
https://git.octaforge.org/tools/libostd.git/
|
||||
https://github.com/OctaForge/libostd
|
||||
|
||||
* GCC 10
|
||||
* Clang 10 (with libstdc++ or libc++)
|
||||
* Microsoft Visual C++ 2019
|
||||
If libostd can work on your system, so can libcubescript.
|
||||
|
||||
Older compilers generally do not work out of box (but for example, GCC 9
|
||||
may work if you provide an `std::span` implementation; see the docs for
|
||||
how, but keep in mind that the resulting library will have incompatible
|
||||
ABI with newer standard library versions that do provide it).
|
||||
The supplied Makefile builds a static library on Unix-like OSes. Link this
|
||||
library together with your application and everything should just work. It also
|
||||
builds the REPL.
|
||||
|
||||
You will need [Meson](https://mesonbuild.com/) to build the project. Most
|
||||
Unix-like systems have it in their package management, on Windows there is
|
||||
an installer available on their website. Being written in Python, you can
|
||||
also use `pip` to get an up to date version on any OS.
|
||||
The project also bundles the linenoise line editing library which has been modified
|
||||
to compile cleanly as C++ (with the same flags as libcubescript). It's used strictly
|
||||
for the REPL only (you don't need it to build libcubescript itself). The version
|
||||
in the repository tracks Git revision https://github.com/antirez/linenoise/commit/c894b9e59f02203dbe4e2be657572cf88c4230c3.
|
||||
|
||||
Once you have it, compiling is simple, e.g. on Unix-likes you can do:
|
||||
## Licensing
|
||||
|
||||
~~~
|
||||
mkdir build && cd build
|
||||
meson ..
|
||||
ninja all
|
||||
~~~
|
||||
|
||||
Refer to Meson's manual for how to customize whether you want a shared or
|
||||
static library and so on. By default, you will get a shared library plus
|
||||
a REPL (interactive interpreter). The REPL also serves as an example of
|
||||
how to use the API.
|
||||
|
||||
If you don't want the REPL, use `-Drepl=disabled`. When compiled, it can
|
||||
have support for line editing and command history. This is provided through
|
||||
`linenoise` (which is a minimal single-file line editing library bundled
|
||||
with the project, and is the default). In case you're on a platform that
|
||||
`linenoise` does not support (highly unlikely), there is a fallback without
|
||||
any line editing as well. Pass `-Dlinenoise=disabled` to use the fallback.
|
||||
|
||||
The version of `linenoise` bundled with the project is `cpp-linenoise`, available
|
||||
at https://github.com/yhirose/cpp-linenoise. Our version is modified, so that
|
||||
it builds cleanly with our flags, and so that it supports the "hints" feature
|
||||
available in original `linenoise`. Other than the modifications, it is baseed
|
||||
on upstream git revision `a927043cdd5bfe203560802e56a7e7ed43156ed3`. The reason
|
||||
we use this instead of upstream `linenoise` is Windows support.
|
||||
See COPYING.md for licensing information.
|
||||
|
|
2619
doc/Doxyfile
2619
doc/Doxyfile
File diff suppressed because it is too large
Load Diff
107
doc/main_page.md
107
doc/main_page.md
|
@ -1,107 +0,0 @@
|
|||
# Libcubescript Documentation {#index}
|
||||
|
||||
## What is Cubescript?
|
||||
|
||||
ubescript is a minimal scripting language first introduced in the Cube FPS
|
||||
and carried over into derived games and game engines such as Sauerbraten.
|
||||
Originally being little more than a few hundred lines of code, serving
|
||||
primarily as the console and configuration file format of the game, it
|
||||
grew more advanced features as well as a bytecode VM.
|
||||
|
||||
Nowadays, it is a minimal but relatively fully featured scripting language
|
||||
based around the concept that everything can be interpreted as a string.
|
||||
It excels at its original purpose as well as things like text preprocessing.
|
||||
It comes with a Lisp-like syntax and a variety of standard library functions.
|
||||
|
||||
## What is Libcubescript?
|
||||
|
||||
Libcubescript is a project that aims to provide an independent, improved,
|
||||
separate implementation of the language, available as a library, intended to
|
||||
satisfy the needs of the OctaForge project. It was originally forked from
|
||||
Cubescript as present in the Tesseract game/engine and gradually rewritten;
|
||||
right now, very little of the original code remains. At language level it is
|
||||
mostly compatible with the other implementations (although with a stricter
|
||||
parser and extra features), while the standard library does not aim to be
|
||||
fully compatible. Some features are also left up to the user to customize,
|
||||
so that it is not tied to game engines feature-wise.
|
||||
|
||||
Like the codebase it is derived from, it is available under the permissive
|
||||
zlib license, and therefore compatible with just about anything.
|
||||
|
||||
## Benefits and differences
|
||||
|
||||
There's a variety of things that set this implementation apart:
|
||||
|
||||
* It's independent and can be embedded in any project
|
||||
* There is no global state, so you can have as many Cubescripts as you want,
|
||||
in one program
|
||||
* Written in C++20, following modern language conventions, both internally
|
||||
and at API level
|
||||
* That means the ability to use lambdas as commands, including captures,
|
||||
type inference and so on
|
||||
* There is a robust allocator system in place, and all memory the library
|
||||
uses is allocated through it; that gives you complete control over its
|
||||
memory (for tracking, sandboxing, limits, etc.)
|
||||
* A large degree of memory safety, with no manual management
|
||||
* Strings are interned, with a single reference counted instance of any
|
||||
string existing at a time, which lowers memory usage and simplifies its
|
||||
management
|
||||
* Minimal stack memory usage, which means no artificial limits on recursion
|
||||
depth as well as safe usage from threads and coroutines with small stacks
|
||||
* Errors will no longer cause the interpreter to march on, instead acting
|
||||
like real errors
|
||||
* Protected calls allow you to catch errors in a similar way to exceptions,
|
||||
and nearly every error can be caught
|
||||
* Stricter parsing, with things like unfinished strings being caught
|
||||
* Loops now have `break` and `continue` statements
|
||||
* Customizable integer and floating point types
|
||||
* Full support for symbol visibility in API
|
||||
* Highly portable and cross-platform, no dependencies other than a compiler
|
||||
* Clean codebase that is easy to pick up and contribute to
|
||||
|
||||
## Building and usage
|
||||
|
||||
The library has absolutely no dependencies other than a C++20 compiler,
|
||||
similarly there are no dependencies on system or architecture specific
|
||||
things, so it should work on any OS and any CPU.
|
||||
|
||||
The C++20 support does not have to be complete. These are the baselines
|
||||
(which are ensured by the CI):
|
||||
|
||||
* GCC 10
|
||||
* Clang 10 (with libstdc++ or libc++)
|
||||
* Microsoft Visual C++ 2019
|
||||
|
||||
Older versions of either of these are known not to work.
|
||||
|
||||
You will need [Meson](https://mesonbuild.com/) to build the project. Most
|
||||
Unix-like systems have it in their package management, on Windows there is
|
||||
an installer available on their website. Being written in Python, you can
|
||||
also use `pip` to get an up to date version on any OS.
|
||||
|
||||
Once you have it, compiling is simple, e.g. on Unix-likes you can do:
|
||||
|
||||
~~~
|
||||
mkdir build && cd build
|
||||
meson ..
|
||||
ninja all
|
||||
~~~
|
||||
|
||||
Refer to Meson's manual for how to customize whether you want a shared or
|
||||
static library and so on. By default, you will get a shared library plus
|
||||
a REPL (interactive interpreter). The REPL also serves as an example of
|
||||
how to use the API.
|
||||
|
||||
If you don't want the REPL, use `-Drepl=disabled`. When compiled, it can
|
||||
have support for line editing and command history. This is provided through
|
||||
`linenoise` (which is a minimal single-file line editing library bundled
|
||||
with the project, and is the default). In case you're on a platform that
|
||||
`linenoise` does not support (highly unlikely), there is a fallback without
|
||||
any line editing as well. Pass `-Dlinenoise=disabled` to use the fallback.
|
||||
|
||||
The version of `linenoise` bundled with the project is `cpp-linenoise`, available
|
||||
at https://github.com/yhirose/cpp-linenoise. Our version is modified, so that
|
||||
it builds cleanly with our flags, and so that it supports the "hints" feature
|
||||
available in original `linenoise`. Other than the modifications, it is baseed
|
||||
on upstream git revision `a927043cdd5bfe203560802e56a7e7ed43156ed3`. The reason
|
||||
we use this instead of upstream `linenoise` is Windows support.
|
|
@ -1,23 +1,869 @@
|
|||
/** @file cubescript.hh
|
||||
*
|
||||
* @brief The main include file for the library.
|
||||
*
|
||||
* Include this file (like `#include <cubescript/cubescript.hh>`) to access
|
||||
* the API. You should generally not include the individual sub-files.
|
||||
*
|
||||
* @copyright See COPYING.md in the project tree for further information.
|
||||
*/
|
||||
|
||||
#ifndef LIBCUBESCRIPT_CUBESCRIPT_HH
|
||||
#define LIBCUBESCRIPT_CUBESCRIPT_HH
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "cubescript_conf.hh"
|
||||
|
||||
#include "cubescript/platform.hh"
|
||||
#include "cubescript/error.hh"
|
||||
#include "cubescript/value.hh"
|
||||
#include "cubescript/ident.hh"
|
||||
#include "cubescript/state.hh"
|
||||
#include "cubescript/util.hh"
|
||||
#include <ostd/platform.hh>
|
||||
#include <ostd/string.hh>
|
||||
#include <ostd/range.hh>
|
||||
#include <ostd/io.hh>
|
||||
#include <ostd/format.hh>
|
||||
|
||||
namespace cscript {
|
||||
|
||||
using cs_string = std::string;
|
||||
|
||||
static_assert(std::is_integral_v<cs_int>, "cs_int must be integral");
|
||||
static_assert(std::is_signed_v<cs_int>, "cs_int must be signed");
|
||||
static_assert(std::is_floating_point_v<cs_float>, "cs_float must be floating point");
|
||||
|
||||
struct cs_internal_error: std::runtime_error {
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
enum {
|
||||
CS_IDF_PERSIST = 1 << 0,
|
||||
CS_IDF_OVERRIDE = 1 << 1,
|
||||
CS_IDF_HEX = 1 << 2,
|
||||
CS_IDF_READONLY = 1 << 3,
|
||||
CS_IDF_OVERRIDDEN = 1 << 4,
|
||||
CS_IDF_UNKNOWN = 1 << 5,
|
||||
CS_IDF_ARG = 1 << 6
|
||||
};
|
||||
|
||||
struct cs_bcode;
|
||||
|
||||
struct OSTD_EXPORT cs_bcode_ref {
|
||||
cs_bcode_ref():
|
||||
p_code(nullptr)
|
||||
{}
|
||||
cs_bcode_ref(cs_bcode *v);
|
||||
cs_bcode_ref(cs_bcode_ref const &v);
|
||||
cs_bcode_ref(cs_bcode_ref &&v):
|
||||
p_code(v.p_code)
|
||||
{
|
||||
v.p_code = nullptr;
|
||||
}
|
||||
|
||||
~cs_bcode_ref();
|
||||
|
||||
cs_bcode_ref &operator=(cs_bcode_ref const &v);
|
||||
cs_bcode_ref &operator=(cs_bcode_ref &&v);
|
||||
|
||||
operator bool() const { return p_code != nullptr; }
|
||||
operator cs_bcode *() const { return p_code; }
|
||||
|
||||
private:
|
||||
cs_bcode *p_code;
|
||||
};
|
||||
|
||||
OSTD_EXPORT bool cs_code_is_empty(cs_bcode *code);
|
||||
|
||||
enum class cs_value_type {
|
||||
Null = 0, Int, Float, String, Cstring, Code, Macro, Ident
|
||||
};
|
||||
|
||||
struct OSTD_EXPORT cs_value {
|
||||
cs_value();
|
||||
~cs_value();
|
||||
|
||||
cs_value(cs_value const &);
|
||||
cs_value(cs_value &&);
|
||||
|
||||
cs_value &operator=(cs_value const &v);
|
||||
cs_value &operator=(cs_value &&v);
|
||||
|
||||
cs_value_type get_type() const;
|
||||
|
||||
void set_int(cs_int val);
|
||||
void set_float(cs_float val);
|
||||
void set_str(cs_string val);
|
||||
void set_null();
|
||||
void set_code(cs_bcode *val);
|
||||
void set_cstr(ostd::string_range val);
|
||||
void set_ident(cs_ident *val);
|
||||
void set_macro(ostd::string_range val);
|
||||
|
||||
cs_string get_str() const;
|
||||
ostd::string_range get_strr() const;
|
||||
cs_int get_int() const;
|
||||
cs_float get_float() const;
|
||||
cs_bcode *get_code() const;
|
||||
cs_ident *get_ident() const;
|
||||
void get_val(cs_value &r) const;
|
||||
|
||||
bool get_bool() const;
|
||||
|
||||
void force_null();
|
||||
cs_float force_float();
|
||||
cs_int force_int();
|
||||
ostd::string_range force_str();
|
||||
|
||||
bool code_is_empty() const;
|
||||
|
||||
private:
|
||||
std::aligned_union_t<1, cs_int, cs_float, void *> p_stor;
|
||||
size_t p_len;
|
||||
cs_value_type p_type;
|
||||
};
|
||||
|
||||
struct cs_ident_stack {
|
||||
cs_value val_s;
|
||||
cs_ident_stack *next;
|
||||
};
|
||||
|
||||
struct cs_shared_state;
|
||||
struct cs_error;
|
||||
struct cs_gen_state;
|
||||
|
||||
enum class cs_ident_type {
|
||||
Ivar = 0, Fvar, Svar, Command, Alias, Special
|
||||
};
|
||||
|
||||
struct cs_var;
|
||||
struct cs_ivar;
|
||||
struct cs_fvar;
|
||||
struct cs_svar;
|
||||
struct cs_alias;
|
||||
struct cs_command;
|
||||
|
||||
struct OSTD_EXPORT cs_ident {
|
||||
friend struct cs_state;
|
||||
friend struct cs_shared_state;
|
||||
|
||||
cs_ident() = delete;
|
||||
cs_ident(cs_ident const &) = delete;
|
||||
cs_ident(cs_ident &&) = delete;
|
||||
|
||||
/* trigger destructors for all inherited members properly */
|
||||
virtual ~cs_ident() {};
|
||||
|
||||
cs_ident &operator=(cs_ident const &) = delete;
|
||||
cs_ident &operator=(cs_ident &&) = delete;
|
||||
|
||||
cs_ident_type get_type() const;
|
||||
ostd::string_range get_name() const;
|
||||
int get_flags() const;
|
||||
int get_index() const;
|
||||
|
||||
bool is_alias() const;
|
||||
cs_alias *get_alias();
|
||||
cs_alias const *get_alias() const;
|
||||
|
||||
bool is_command() const;
|
||||
cs_command *get_command();
|
||||
cs_command const *get_command() const;
|
||||
|
||||
bool is_special() const;
|
||||
|
||||
bool is_var() const;
|
||||
cs_var *get_var();
|
||||
cs_var const *get_var() const;
|
||||
|
||||
bool is_ivar() const;
|
||||
cs_ivar *get_ivar();
|
||||
cs_ivar const *get_ivar() const;
|
||||
|
||||
bool is_fvar() const;
|
||||
cs_fvar *get_fvar();
|
||||
cs_fvar const *get_fvar() const;
|
||||
|
||||
bool is_svar() const;
|
||||
cs_svar *get_svar();
|
||||
cs_svar const *get_svar() const;
|
||||
|
||||
int get_type_raw() const {
|
||||
return p_type;
|
||||
}
|
||||
|
||||
protected:
|
||||
cs_ident(cs_ident_type tp, ostd::string_range name, int flags = 0);
|
||||
|
||||
cs_string p_name;
|
||||
/* represents the cs_ident_type above, but internally it has a wider variety
|
||||
* of values, so it's an int here (maps to an internal enum)
|
||||
*/
|
||||
int p_type, p_flags;
|
||||
|
||||
private:
|
||||
int p_index = -1;
|
||||
};
|
||||
|
||||
struct OSTD_EXPORT cs_var: cs_ident {
|
||||
friend struct cs_state;
|
||||
friend struct cs_shared_state;
|
||||
|
||||
protected:
|
||||
cs_var(cs_ident_type tp, ostd::string_range name, cs_var_cb func, int flags = 0);
|
||||
|
||||
private:
|
||||
cs_var_cb cb_var;
|
||||
|
||||
virtual cs_string to_printable() const = 0;
|
||||
|
||||
void changed(cs_state &cs) {
|
||||
if (cb_var) {
|
||||
cb_var(cs, *this);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct OSTD_EXPORT cs_ivar: cs_var {
|
||||
friend struct cs_state;
|
||||
friend struct cs_shared_state;
|
||||
|
||||
cs_int get_val_min() const;
|
||||
cs_int get_val_max() const;
|
||||
|
||||
cs_int get_value() const;
|
||||
void set_value(cs_int val);
|
||||
|
||||
cs_string to_printable() const final;
|
||||
|
||||
private:
|
||||
cs_ivar(
|
||||
ostd::string_range n, cs_int m, cs_int x, cs_int v, cs_var_cb f, int flags
|
||||
);
|
||||
|
||||
cs_int p_storage, p_minval, p_maxval, p_overrideval;
|
||||
};
|
||||
|
||||
struct OSTD_EXPORT cs_fvar: cs_var {
|
||||
friend struct cs_state;
|
||||
friend struct cs_shared_state;
|
||||
|
||||
cs_float get_val_min() const;
|
||||
cs_float get_val_max() const;
|
||||
|
||||
cs_float get_value() const;
|
||||
void set_value(cs_float val);
|
||||
|
||||
cs_string to_printable() const final;
|
||||
|
||||
private:
|
||||
cs_fvar(
|
||||
ostd::string_range n, cs_float m, cs_float x, cs_float v,
|
||||
cs_var_cb f, int flags
|
||||
);
|
||||
|
||||
cs_float p_storage, p_minval, p_maxval, p_overrideval;
|
||||
};
|
||||
|
||||
struct OSTD_EXPORT cs_svar: cs_var {
|
||||
friend struct cs_state;
|
||||
friend struct cs_shared_state;
|
||||
|
||||
ostd::string_range get_value() const;
|
||||
void set_value(cs_string val);
|
||||
|
||||
cs_string to_printable() const final;
|
||||
|
||||
private:
|
||||
cs_svar(ostd::string_range n, cs_string v, cs_var_cb f, int flags);
|
||||
|
||||
cs_string p_storage, p_overrideval;
|
||||
};
|
||||
|
||||
struct OSTD_EXPORT cs_alias: cs_ident {
|
||||
friend struct cs_state;
|
||||
friend struct cs_shared_state;
|
||||
friend struct cs_alias_internal;
|
||||
|
||||
cs_value const &get_value() const {
|
||||
return p_val;
|
||||
}
|
||||
|
||||
cs_value &get_value() {
|
||||
return p_val;
|
||||
}
|
||||
|
||||
void get_cstr(cs_value &v) const;
|
||||
void get_cval(cs_value &v) const;
|
||||
private:
|
||||
cs_alias(ostd::string_range n, cs_string a, int flags);
|
||||
cs_alias(ostd::string_range n, cs_int a, int flags);
|
||||
cs_alias(ostd::string_range n, cs_float a, int flags);
|
||||
cs_alias(ostd::string_range n, int flags);
|
||||
cs_alias(ostd::string_range n, cs_value v, int flags);
|
||||
|
||||
cs_bcode *p_acode;
|
||||
cs_ident_stack *p_astack;
|
||||
cs_value p_val;
|
||||
};
|
||||
|
||||
struct cs_command: cs_ident {
|
||||
friend struct cs_state;
|
||||
friend struct cs_shared_state;
|
||||
friend struct cs_cmd_internal;
|
||||
|
||||
ostd::string_range get_args() const;
|
||||
int get_num_args() const;
|
||||
|
||||
private:
|
||||
cs_command(
|
||||
ostd::string_range name, ostd::string_range args,
|
||||
int numargs, cs_command_cb func
|
||||
);
|
||||
|
||||
cs_string p_cargs;
|
||||
cs_command_cb p_cb_cftv;
|
||||
int p_numargs;
|
||||
};
|
||||
|
||||
struct cs_identLink;
|
||||
|
||||
enum {
|
||||
CsLibMath = 1 << 0,
|
||||
CsLibString = 1 << 1,
|
||||
CsLibList = 1 << 2,
|
||||
CsLibAll = 0b111
|
||||
};
|
||||
|
||||
enum class CsLoopState {
|
||||
Normal = 0, Break, Continue
|
||||
};
|
||||
|
||||
static inline void *cs_default_alloc(void *, void *p, size_t, size_t ns) {
|
||||
if (!ns) {
|
||||
delete[] static_cast<unsigned char *>(p);
|
||||
return nullptr;
|
||||
}
|
||||
return new unsigned char[ns];
|
||||
}
|
||||
|
||||
struct OSTD_EXPORT cs_state {
|
||||
friend struct cs_error;
|
||||
friend struct cs_gen_state;
|
||||
|
||||
cs_shared_state *p_state;
|
||||
cs_identLink *p_callstack = nullptr;
|
||||
|
||||
int identflags = 0;
|
||||
|
||||
cs_state(cs_alloc_cb func = cs_default_alloc, void *data = nullptr);
|
||||
virtual ~cs_state();
|
||||
|
||||
cs_state(cs_state const &) = delete;
|
||||
cs_state(cs_state &&s) {
|
||||
swap(s);
|
||||
}
|
||||
|
||||
cs_state &operator=(cs_state const &) = delete;
|
||||
cs_state &operator=(cs_state &&s) {
|
||||
swap(s);
|
||||
s.destroy();
|
||||
return *this;
|
||||
}
|
||||
|
||||
void destroy();
|
||||
|
||||
void swap(cs_state &s) {
|
||||
std::swap(p_state, s.p_state);
|
||||
std::swap(p_callstack, s.p_callstack);
|
||||
std::swap(identflags, s.identflags);
|
||||
std::swap(p_pstate, s.p_pstate);
|
||||
std::swap(p_inloop, s.p_inloop);
|
||||
std::swap(p_owner, s.p_owner);
|
||||
std::swap(p_callhook, s.p_callhook);
|
||||
}
|
||||
|
||||
cs_state new_thread();
|
||||
|
||||
cs_hook_cb set_call_hook(cs_hook_cb func);
|
||||
cs_hook_cb const &get_call_hook() const;
|
||||
cs_hook_cb &get_call_hook();
|
||||
|
||||
void init_libs(int libs = CsLibAll);
|
||||
|
||||
void clear_override(cs_ident &id);
|
||||
void clear_overrides();
|
||||
|
||||
cs_ident *new_ident(ostd::string_range name, int flags = CS_IDF_UNKNOWN);
|
||||
cs_ident *force_ident(cs_value &v);
|
||||
|
||||
cs_ivar *new_ivar(
|
||||
ostd::string_range n, cs_int m, cs_int x, cs_int v,
|
||||
cs_var_cb f = cs_var_cb(), int flags = 0
|
||||
);
|
||||
cs_fvar *new_fvar(
|
||||
ostd::string_range n, cs_float m, cs_float x, cs_float v,
|
||||
cs_var_cb f = cs_var_cb(), int flags = 0
|
||||
);
|
||||
cs_svar *new_svar(
|
||||
ostd::string_range n, cs_string v,
|
||||
cs_var_cb f = cs_var_cb(), int flags = 0
|
||||
);
|
||||
|
||||
cs_command *new_command(
|
||||
ostd::string_range name, ostd::string_range args, cs_command_cb func
|
||||
);
|
||||
|
||||
cs_ident *get_ident(ostd::string_range name);
|
||||
cs_alias *get_alias(ostd::string_range name);
|
||||
bool have_ident(ostd::string_range name);
|
||||
|
||||
cs_ident_r get_idents();
|
||||
cs_const_ident_r get_idents() const;
|
||||
|
||||
void reset_var(ostd::string_range name);
|
||||
void touch_var(ostd::string_range name);
|
||||
|
||||
cs_string run_str(cs_bcode *code);
|
||||
cs_string run_str(ostd::string_range code);
|
||||
cs_string run_str(cs_ident *id, cs_value_r args);
|
||||
|
||||
cs_int run_int(cs_bcode *code);
|
||||
cs_int run_int(ostd::string_range code);
|
||||
cs_int run_int(cs_ident *id, cs_value_r args);
|
||||
|
||||
cs_float run_float(cs_bcode *code);
|
||||
cs_float run_float(ostd::string_range code);
|
||||
cs_float run_float(cs_ident *id, cs_value_r args);
|
||||
|
||||
bool run_bool(cs_bcode *code);
|
||||
bool run_bool(ostd::string_range code);
|
||||
bool run_bool(cs_ident *id, cs_value_r args);
|
||||
|
||||
void run(cs_bcode *code, cs_value &ret);
|
||||
void run(ostd::string_range code, cs_value &ret);
|
||||
void run(cs_ident *id, cs_value_r args, cs_value &ret);
|
||||
|
||||
void run(cs_bcode *code);
|
||||
void run(ostd::string_range code);
|
||||
void run(cs_ident *id, cs_value_r args);
|
||||
|
||||
CsLoopState run_loop(cs_bcode *code, cs_value &ret);
|
||||
CsLoopState run_loop(cs_bcode *code);
|
||||
|
||||
bool is_in_loop() const {
|
||||
return p_inloop;
|
||||
}
|
||||
|
||||
std::optional<cs_string> run_file_str(ostd::string_range fname);
|
||||
std::optional<cs_int> run_file_int(ostd::string_range fname);
|
||||
std::optional<cs_float> run_file_float(ostd::string_range fname);
|
||||
std::optional<bool> run_file_bool(ostd::string_range fname);
|
||||
bool run_file(ostd::string_range fname, cs_value &ret);
|
||||
bool run_file(ostd::string_range fname);
|
||||
|
||||
void set_alias(ostd::string_range name, cs_value v);
|
||||
|
||||
void set_var_int(
|
||||
ostd::string_range name, cs_int v,
|
||||
bool dofunc = true, bool doclamp = true
|
||||
);
|
||||
void set_var_float(
|
||||
ostd::string_range name, cs_float v,
|
||||
bool dofunc = true, bool doclamp = true
|
||||
);
|
||||
void set_var_str(
|
||||
ostd::string_range name, ostd::string_range v, bool dofunc = true
|
||||
);
|
||||
|
||||
void set_var_int_checked(cs_ivar *iv, cs_int v);
|
||||
void set_var_int_checked(cs_ivar *iv, cs_value_r args);
|
||||
void set_var_float_checked(cs_fvar *fv, cs_float v);
|
||||
void set_var_str_checked(cs_svar *fv, ostd::string_range v);
|
||||
|
||||
std::optional<cs_int> get_var_int(ostd::string_range name);
|
||||
std::optional<cs_float> get_var_float(ostd::string_range name);
|
||||
std::optional<cs_string> get_var_str(ostd::string_range name);
|
||||
|
||||
std::optional<cs_int> get_var_min_int(ostd::string_range name);
|
||||
std::optional<cs_int> get_var_max_int(ostd::string_range name);
|
||||
|
||||
std::optional<cs_float> get_var_min_float(ostd::string_range name);
|
||||
std::optional<cs_float> get_var_max_float(ostd::string_range name);
|
||||
|
||||
std::optional<cs_string> get_alias_val(ostd::string_range name);
|
||||
|
||||
virtual void print_var(cs_var *v);
|
||||
|
||||
private:
|
||||
OSTD_LOCAL cs_state(cs_shared_state *s);
|
||||
|
||||
cs_ident *add_ident(cs_ident *id);
|
||||
|
||||
OSTD_LOCAL void *alloc(void *ptr, size_t olds, size_t news);
|
||||
|
||||
cs_gen_state *p_pstate = nullptr;
|
||||
int p_inloop = 0;
|
||||
bool p_owner = false;
|
||||
|
||||
char p_errbuf[512];
|
||||
|
||||
cs_hook_cb p_callhook;
|
||||
};
|
||||
|
||||
struct cs_stack_state_node {
|
||||
cs_stack_state_node const *next;
|
||||
cs_ident const *id;
|
||||
int index;
|
||||
};
|
||||
|
||||
struct cs_stack_state {
|
||||
cs_stack_state() = delete;
|
||||
cs_stack_state(cs_state &cs, cs_stack_state_node *nd = nullptr, bool gap = false);
|
||||
cs_stack_state(cs_stack_state const &) = delete;
|
||||
cs_stack_state(cs_stack_state &&st);
|
||||
~cs_stack_state();
|
||||
|
||||
cs_stack_state &operator=(cs_stack_state const &) = delete;
|
||||
cs_stack_state &operator=(cs_stack_state &&);
|
||||
|
||||
cs_stack_state_node const *get() const;
|
||||
bool gap() const;
|
||||
|
||||
private:
|
||||
cs_state &p_state;
|
||||
cs_stack_state_node *p_node;
|
||||
bool p_gap;
|
||||
};
|
||||
|
||||
struct cs_error {
|
||||
friend struct cs_state;
|
||||
|
||||
cs_error() = delete;
|
||||
cs_error(cs_error const &) = delete;
|
||||
cs_error(cs_error &&v):
|
||||
p_errmsg(v.p_errmsg), p_stack(std::move(v.p_stack))
|
||||
{}
|
||||
|
||||
ostd::string_range what() const {
|
||||
return p_errmsg;
|
||||
}
|
||||
|
||||
cs_stack_state &get_stack() {
|
||||
return p_stack;
|
||||
}
|
||||
|
||||
cs_stack_state const &get_stack() const {
|
||||
return p_stack;
|
||||
}
|
||||
|
||||
cs_error(cs_state &cs, ostd::string_range msg):
|
||||
p_errmsg(), p_stack(cs)
|
||||
{
|
||||
p_errmsg = save_msg(cs, msg);
|
||||
p_stack = save_stack(cs);
|
||||
}
|
||||
|
||||
template<typename ...A>
|
||||
cs_error(cs_state &cs, ostd::string_range msg, A &&...args):
|
||||
p_errmsg(), p_stack(cs)
|
||||
{
|
||||
try {
|
||||
char fbuf[512];
|
||||
auto ret = ostd::format(
|
||||
ostd::counting_sink(ostd::char_range(fbuf, fbuf + sizeof(fbuf))),
|
||||
msg, std::forward<A>(args)...
|
||||
).get_written();
|
||||
p_errmsg = save_msg(cs, ostd::char_range(fbuf, fbuf + ret));
|
||||
} catch (...) {
|
||||
p_errmsg = save_msg(cs, msg);
|
||||
}
|
||||
p_stack = save_stack(cs);
|
||||
}
|
||||
|
||||
private:
|
||||
cs_stack_state save_stack(cs_state &cs);
|
||||
ostd::string_range save_msg(cs_state &cs, ostd::string_range v);
|
||||
|
||||
ostd::string_range p_errmsg;
|
||||
cs_stack_state p_stack;
|
||||
};
|
||||
|
||||
struct OSTD_EXPORT cs_stacked_value: cs_value {
|
||||
cs_stacked_value(cs_ident *id = nullptr);
|
||||
~cs_stacked_value();
|
||||
|
||||
cs_stacked_value(cs_stacked_value const &) = delete;
|
||||
cs_stacked_value(cs_stacked_value &&) = delete;
|
||||
|
||||
cs_stacked_value &operator=(cs_stacked_value const &) = delete;
|
||||
cs_stacked_value &operator=(cs_stacked_value &&v) = delete;
|
||||
|
||||
cs_stacked_value &operator=(cs_value const &v);
|
||||
cs_stacked_value &operator=(cs_value &&v);
|
||||
|
||||
bool set_alias(cs_ident *id);
|
||||
cs_alias *get_alias() const;
|
||||
bool has_alias() const;
|
||||
|
||||
bool push();
|
||||
bool pop();
|
||||
|
||||
private:
|
||||
cs_alias *p_a;
|
||||
cs_ident_stack p_stack;
|
||||
bool p_pushed;
|
||||
};
|
||||
|
||||
namespace util {
|
||||
template<typename R>
|
||||
inline R &&escape_string(R &&writer, ostd::string_range str) {
|
||||
using namespace ostd::string_literals;
|
||||
writer.put('"');
|
||||
for (; !str.empty(); str.pop_front()) {
|
||||
switch (str.front()) {
|
||||
case '\n':
|
||||
ostd::range_put_all(writer, "^n"_sr);
|
||||
break;
|
||||
case '\t':
|
||||
ostd::range_put_all(writer, "^t"_sr);
|
||||
break;
|
||||
case '\f':
|
||||
ostd::range_put_all(writer, "^f"_sr);
|
||||
break;
|
||||
case '"':
|
||||
ostd::range_put_all(writer, "^\""_sr);
|
||||
break;
|
||||
case '^':
|
||||
ostd::range_put_all(writer, "^^"_sr);
|
||||
break;
|
||||
default:
|
||||
writer.put(str.front());
|
||||
break;
|
||||
}
|
||||
}
|
||||
writer.put('"');
|
||||
return std::forward<R>(writer);
|
||||
}
|
||||
|
||||
template<typename R>
|
||||
inline R &&unescape_string(R &&writer, ostd::string_range str) {
|
||||
for (; !str.empty(); str.pop_front()) {
|
||||
if (str.front() == '^') {
|
||||
str.pop_front();
|
||||
if (str.empty()) {
|
||||
break;
|
||||
}
|
||||
switch (str.front()) {
|
||||
case 'n':
|
||||
writer.put('\n');
|
||||
break;
|
||||
case 't':
|
||||
writer.put('\r');
|
||||
break;
|
||||
case 'f':
|
||||
writer.put('\f');
|
||||
break;
|
||||
case '"':
|
||||
writer.put('"');
|
||||
break;
|
||||
case '^':
|
||||
writer.put('^');
|
||||
break;
|
||||
default:
|
||||
writer.put(str.front());
|
||||
break;
|
||||
}
|
||||
} else if (str.front() == '\\') {
|
||||
str.pop_front();
|
||||
if (str.empty()) {
|
||||
break;
|
||||
}
|
||||
char c = str.front();
|
||||
if ((c == '\r') || (c == '\n')) {
|
||||
if (!str.empty() && (c == '\r') && (str.front() == '\n')) {
|
||||
str.pop_front();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
writer.put('\\');
|
||||
} else {
|
||||
writer.put(str.front());
|
||||
}
|
||||
}
|
||||
return std::forward<R>(writer);
|
||||
}
|
||||
|
||||
OSTD_EXPORT ostd::string_range parse_string(
|
||||
cs_state &cs, ostd::string_range str, size_t &nlines
|
||||
);
|
||||
|
||||
inline ostd::string_range parse_string(
|
||||
cs_state &cs, ostd::string_range str
|
||||
) {
|
||||
size_t nlines;
|
||||
return parse_string(cs, str, nlines);
|
||||
}
|
||||
|
||||
OSTD_EXPORT ostd::string_range parse_word(
|
||||
cs_state &cs, ostd::string_range str
|
||||
);
|
||||
|
||||
struct list_range;
|
||||
|
||||
struct OSTD_EXPORT list_parser {
|
||||
list_parser() = delete;
|
||||
list_parser(cs_state &cs, ostd::string_range src):
|
||||
p_state(cs), p_input(src)
|
||||
{}
|
||||
|
||||
void skip();
|
||||
bool parse();
|
||||
size_t count();
|
||||
|
||||
template<typename R>
|
||||
R &&get_item(R &&writer) const {
|
||||
if (!p_quote.empty() && (*p_quote == '"')) {
|
||||
return unescape_string(std::forward<R>(writer), p_item);
|
||||
} else {
|
||||
ostd::range_put_all(writer, p_item);
|
||||
return std::forward<R>(writer);
|
||||
}
|
||||
}
|
||||
|
||||
cs_string get_item() const {
|
||||
return std::move(get_item(ostd::appender<cs_string>()).get());
|
||||
}
|
||||
|
||||
ostd::string_range &get_raw_item(bool quoted = false) {
|
||||
return quoted ? p_quote : p_item;
|
||||
}
|
||||
|
||||
ostd::string_range const &get_raw_item(bool quoted = false) const {
|
||||
return quoted ? p_quote : p_item;
|
||||
}
|
||||
|
||||
ostd::string_range &get_input() {
|
||||
return p_input;
|
||||
}
|
||||
|
||||
list_range iter() noexcept;
|
||||
|
||||
private:
|
||||
ostd::string_range p_quote = ostd::string_range();
|
||||
ostd::string_range p_item = ostd::string_range();
|
||||
cs_state &p_state;
|
||||
ostd::string_range p_input;
|
||||
};
|
||||
|
||||
struct list_range: ostd::input_range<list_range> {
|
||||
using range_category = ostd::forward_range_tag;
|
||||
using value_type = ostd::string_range;
|
||||
using reference = ostd::string_range;
|
||||
using size_type = std::size_t;
|
||||
|
||||
list_range() = delete;
|
||||
|
||||
list_range(list_parser &p) noexcept: p_parser(&p) {
|
||||
pop_front();
|
||||
}
|
||||
|
||||
bool empty() const noexcept {
|
||||
return !bool(p_item);
|
||||
}
|
||||
|
||||
void pop_front() noexcept {
|
||||
if (p_parser->parse()) {
|
||||
p_item = p_parser->get_item();
|
||||
} else {
|
||||
p_item.reset();
|
||||
}
|
||||
}
|
||||
|
||||
ostd::string_range front() const noexcept {
|
||||
return *p_item;
|
||||
}
|
||||
|
||||
private:
|
||||
list_parser *p_parser;
|
||||
std::optional<cs_string> p_item{};
|
||||
};
|
||||
|
||||
inline list_range list_parser::iter() noexcept {
|
||||
return list_range{*this};
|
||||
}
|
||||
|
||||
template<typename R>
|
||||
inline void format_int(R &&writer, cs_int val) {
|
||||
try {
|
||||
ostd::format(std::forward<R>(writer), IntFormat, val);
|
||||
} catch (ostd::format_error const &e) {
|
||||
throw cs_internal_error{e.what()};
|
||||
}
|
||||
}
|
||||
|
||||
template<typename R>
|
||||
inline void format_float(R &&writer, cs_float val) {
|
||||
try {
|
||||
ostd::format(
|
||||
std::forward<R>(writer),
|
||||
(val == cs_int(val)) ? RoundFloatFormat : FloatFormat, val
|
||||
);
|
||||
} catch (ostd::format_error const &e) {
|
||||
throw cs_internal_error{e.what()};
|
||||
}
|
||||
}
|
||||
|
||||
template<typename R>
|
||||
inline void tvals_concat(
|
||||
R &&writer, cs_value_r vals,
|
||||
ostd::string_range sep = ostd::string_range()
|
||||
) {
|
||||
for (size_t i = 0; i < vals.size(); ++i) {
|
||||
switch (vals[i].get_type()) {
|
||||
case cs_value_type::Int: {
|
||||
format_int(
|
||||
std::forward<R>(writer), vals[i].get_int()
|
||||
);
|
||||
break;
|
||||
}
|
||||
case cs_value_type::Float: {
|
||||
format_float(
|
||||
std::forward<R>(writer), vals[i].get_float()
|
||||
);
|
||||
break;
|
||||
}
|
||||
case cs_value_type::String:
|
||||
case cs_value_type::Cstring:
|
||||
case cs_value_type::Macro: {
|
||||
ostd::range_put_all(writer, vals[i].get_strr());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (i == (vals.size() - 1)) {
|
||||
break;
|
||||
}
|
||||
ostd::range_put_all(writer, sep);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename R>
|
||||
inline void print_stack(R &&writer, cs_stack_state const &st) {
|
||||
auto nd = st.get();
|
||||
while (nd) {
|
||||
try {
|
||||
ostd::format(
|
||||
std::forward<R>(writer),
|
||||
((nd->index == 1) && st.gap())
|
||||
? " ..%d) %s" : " %d) %s",
|
||||
nd->index, nd->id->get_name()
|
||||
);
|
||||
} catch (ostd::format_error const &e) {
|
||||
throw cs_internal_error{e.what()};
|
||||
}
|
||||
nd = nd->next;
|
||||
if (nd) {
|
||||
writer.put('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
} /* namespace util */
|
||||
|
||||
} /* namespace cscript */
|
||||
|
||||
#endif /* LIBCUBESCRIPT_CUBESCRIPT_HH */
|
||||
|
|
|
@ -1,223 +0,0 @@
|
|||
/** @file callable.hh
|
||||
*
|
||||
* @brief Internal callable data structure.
|
||||
*
|
||||
* There is no public API in this file.
|
||||
*
|
||||
* @copyright See COPYING.md in the project tree for further information.
|
||||
*/
|
||||
|
||||
#ifndef LIBCUBESCRIPT_CUBESCRIPT_CALLABLE_HH
|
||||
#define LIBCUBESCRIPT_CUBESCRIPT_CALLABLE_HH
|
||||
|
||||
#include <cstring>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
namespace cubescript {
|
||||
namespace internal {
|
||||
|
||||
/** @private */
|
||||
template<typename R, typename ...A>
|
||||
struct callable {
|
||||
private:
|
||||
struct base {
|
||||
base(base const &);
|
||||
base &operator=(base const &);
|
||||
|
||||
public:
|
||||
base() {}
|
||||
virtual ~base() {}
|
||||
virtual void move_to(base *) = 0;
|
||||
virtual R operator()(A &&...args) const = 0;
|
||||
};
|
||||
|
||||
template<typename F>
|
||||
struct store: base {
|
||||
explicit store(F &&f): p_stor{std::move(f)} {}
|
||||
|
||||
virtual void move_to(base *p) {
|
||||
::new (p) store{std::move(p_stor)};
|
||||
}
|
||||
|
||||
virtual R operator()(A &&...args) const {
|
||||
return std::invoke(*std::launder(
|
||||
reinterpret_cast<F const *>(&p_stor)
|
||||
), std::forward<A>(args)...);
|
||||
}
|
||||
|
||||
private:
|
||||
F p_stor;
|
||||
};
|
||||
|
||||
using alloc_f = void *(*)(void *, void *, std::size_t, std::size_t);
|
||||
|
||||
struct f_alloc {
|
||||
alloc_f af;
|
||||
void *ud;
|
||||
size_t asize;
|
||||
};
|
||||
|
||||
std::aligned_storage_t<sizeof(void *) * 4> p_stor;
|
||||
base *p_func;
|
||||
|
||||
static inline base *as_base(void *p) {
|
||||
return static_cast<base *>(p);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static inline bool f_not_null(T const &) { return true; }
|
||||
|
||||
template<typename T>
|
||||
static inline bool f_not_null(T *p) { return !!p; }
|
||||
|
||||
template<typename CR, typename C>
|
||||
static inline bool f_not_null(CR C::*p) { return !!p; }
|
||||
|
||||
template<typename T>
|
||||
static inline bool f_not_null(callable<T> const &f) { return !!f; }
|
||||
|
||||
bool small_storage() {
|
||||
return (static_cast<void *>(p_func) == &p_stor);
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
if (!p_func) {
|
||||
return;
|
||||
}
|
||||
p_func->~base();
|
||||
if (!small_storage()) {
|
||||
auto &ad = *std::launder(reinterpret_cast<f_alloc *>(&p_stor));
|
||||
ad.af(ad.ud, p_func, ad.asize, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
callable() noexcept: p_func{nullptr} {}
|
||||
callable(std::nullptr_t) noexcept: p_func{nullptr} {}
|
||||
callable(std::nullptr_t, alloc_f, void *) noexcept: p_func{nullptr} {}
|
||||
|
||||
callable(callable &&f) noexcept {
|
||||
if (!f.p_func) {
|
||||
p_func = nullptr;
|
||||
} else if (f.small_storage()) {
|
||||
p_func = as_base(&p_stor);
|
||||
f.p_func->move_to(p_func);
|
||||
} else {
|
||||
p_func = f.p_func;
|
||||
f.p_func = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
callable(F func, alloc_f af, void *ud) {
|
||||
if (!f_not_null(func)) {
|
||||
return;
|
||||
}
|
||||
if constexpr (sizeof(store<F>) <= sizeof(p_stor)) {
|
||||
auto *p = static_cast<void *>(&p_stor);
|
||||
p_func = ::new (p) store<F>{std::move(func)};
|
||||
} else {
|
||||
auto &ad = *std::launder(reinterpret_cast<f_alloc *>(&p_stor));
|
||||
ad.af = af;
|
||||
ad.ud = ud;
|
||||
ad.asize = sizeof(store<F>);
|
||||
p_func = static_cast<store<F> *>(
|
||||
af(ud, nullptr, 0, sizeof(store<F>))
|
||||
);
|
||||
try {
|
||||
new (p_func) store<F>{std::move(func)};
|
||||
} catch (...) {
|
||||
af(ud, p_func, sizeof(store<F>), 0);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callable &operator=(callable const &) = delete;
|
||||
|
||||
callable &operator=(callable &&f) noexcept {
|
||||
cleanup();
|
||||
if (f.p_func == nullptr) {
|
||||
p_func = nullptr;
|
||||
} else if (f.small_storage()) {
|
||||
p_func = as_base(&p_stor);
|
||||
f.p_func->move_to(p_func);
|
||||
} else {
|
||||
p_func = f.p_func;
|
||||
f.p_func = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
callable &operator=(std::nullptr_t) noexcept {
|
||||
cleanup();
|
||||
p_func = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
callable &operator=(F &&func) {
|
||||
callable{std::forward<F>(func)}.swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
~callable() {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
void swap(callable &f) noexcept {
|
||||
std::aligned_storage_t<sizeof(p_stor)> tmp_stor;
|
||||
if (small_storage() && f.small_storage()) {
|
||||
auto *t = as_base(&tmp_stor);
|
||||
p_func->move_to(t);
|
||||
p_func->~base();
|
||||
p_func = nullptr;
|
||||
f.p_func->move_to(as_base(&p_stor));
|
||||
f.p_func->~base();
|
||||
f.p_func = nullptr;
|
||||
p_func = as_base(&p_stor);
|
||||
t->move_to(as_base(&f.p_stor));
|
||||
t->~base();
|
||||
f.p_func = as_base(&f.p_stor);
|
||||
} else if (small_storage()) {
|
||||
/* copy allocator address/size */
|
||||
std::memcpy(&tmp_stor, &f.p_stor, sizeof(tmp_stor));
|
||||
p_func->move_to(as_base(&f.p_stor));
|
||||
p_func->~base();
|
||||
p_func = f.p_func;
|
||||
f.p_func = as_base(&f.p_stor);
|
||||
std::memcpy(&p_stor, &tmp_stor, sizeof(tmp_stor));
|
||||
} else if (f.small_storage()) {
|
||||
/* copy allocator address/size */
|
||||
std::memcpy(&tmp_stor, &p_stor, sizeof(tmp_stor));
|
||||
f.p_func->move_to(as_base(&p_stor));
|
||||
f.p_func->~base();
|
||||
f.p_func = p_func;
|
||||
p_func = as_base(&p_stor);
|
||||
std::memcpy(&f.p_stor, &tmp_stor, sizeof(tmp_stor));
|
||||
} else {
|
||||
/* copy allocator address/size */
|
||||
std::memcpy(&tmp_stor, &p_stor, sizeof(tmp_stor));
|
||||
std::memcpy(&p_stor, &f.p_stor, sizeof(tmp_stor));
|
||||
std::memcpy(&f.p_stor, &tmp_stor, sizeof(tmp_stor));
|
||||
std::swap(p_func, f.p_func);
|
||||
}
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept {
|
||||
return !!p_func;
|
||||
}
|
||||
|
||||
R operator()(A ...args) const {
|
||||
return (*p_func)(std::forward<A>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
} /* namespace internal */
|
||||
} /* namespace cubescript */
|
||||
|
||||
#endif /* LIBCUBESCRIPT_CUBESCRIPT_CALLABLE_HH */
|
|
@ -1,88 +0,0 @@
|
|||
/** @file error.hh
|
||||
*
|
||||
* @brief Error handling API.
|
||||
*
|
||||
* Defines structures and methods used for error handling in the library.
|
||||
*
|
||||
* @copyright See COPYING.md in the project tree for further information.
|
||||
*/
|
||||
|
||||
#ifndef LIBCUBESCRIPT_CUBESCRIPT_ERROR_HH
|
||||
#define LIBCUBESCRIPT_CUBESCRIPT_ERROR_HH
|
||||
|
||||
#include <string_view>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
namespace cubescript {
|
||||
|
||||
struct state;
|
||||
|
||||
/** @brief Represents a Cubescript error.
|
||||
*
|
||||
* This is a standard error that can be thrown by either the Cubescript APIs
|
||||
* or from the language itself (either by the user, or by incorrect use of
|
||||
* the API).
|
||||
*
|
||||
* It has a message attached, as well as the current state of the call stack,
|
||||
* represented as cubescript::stack_state.
|
||||
*
|
||||
* Each Cubescript thread internally stores a buffer for the error message,
|
||||
* which is reused for each error raised from that thread.
|
||||
*/
|
||||
struct LIBCUBESCRIPT_EXPORT error {
|
||||
/** @brief A node in the call stack.
|
||||
*
|
||||
* The nodes are indexed. The bottommost node has index 1, the topmost
|
||||
* node has index N (where N is the number of levels the call stack has).
|
||||
*
|
||||
* There can be a gap in the stack (i.e. the bottommost node will have
|
||||
* index 1 and the one above it greater than 2). The gap is controlled
|
||||
* by the value of the `dbgalias` cubescript variable at the time of
|
||||
* creation of the error (the stack list will contain at most N nodes).
|
||||
*
|
||||
* When getting the stack state, it will be represented as a span with
|
||||
* the first element being the topmost node and the last element being
|
||||
* the bottommost (index 1).
|
||||
*/
|
||||
struct stack_node {
|
||||
struct ident const &id; /**< @brief The ident of this level. */
|
||||
std::size_t index; /**< @brief The level index. */
|
||||
};
|
||||
|
||||
error() = delete;
|
||||
error(error const &) = delete;
|
||||
|
||||
/** @brief Errors are move constructible. */
|
||||
error(error &&v);
|
||||
|
||||
error &operator=(error const &) = delete;
|
||||
|
||||
/** @brief Errors are move assignable. */
|
||||
error &operator=(error &&v);
|
||||
|
||||
/** @brief Construct an error using a string. */
|
||||
error(state &cs, std::string_view msg);
|
||||
|
||||
/** @brief Destroy the error. */
|
||||
~error();
|
||||
|
||||
/** @brief Get a view of the error message. */
|
||||
std::string_view what() const;
|
||||
|
||||
/** @brief Get the call stack state at the point of error. */
|
||||
span_type<stack_node const> stack() const;
|
||||
|
||||
private:
|
||||
friend struct error_p;
|
||||
|
||||
error(state &cs, char const *errbeg, char const *errend);
|
||||
|
||||
char const *p_errbeg, *p_errend;
|
||||
stack_node *p_sbeg, *p_send;
|
||||
state *p_state;
|
||||
};
|
||||
|
||||
} /* namespace cubescript */
|
||||
|
||||
#endif /* LIBCUBESCRIPT_CUBESCRIPT_ERROR_HH */
|
|
@ -1,356 +0,0 @@
|
|||
/** @file ident.hh
|
||||
*
|
||||
* @brief Identifier management.
|
||||
*
|
||||
* Identifiers in `libcubescript` represent variables, aliases, commands
|
||||
* and so on. This file contains the handles for those and everything you
|
||||
* need to interface with them.
|
||||
*
|
||||
* @copyright See COPYING.md in the project tree for further information.
|
||||
*/
|
||||
|
||||
#ifndef LIBCUBESCRIPT_CUBESCRIPT_IDENT_HH
|
||||
#define LIBCUBESCRIPT_CUBESCRIPT_IDENT_HH
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include "value.hh"
|
||||
|
||||
namespace cubescript {
|
||||
|
||||
/** @brief The type of the ident.
|
||||
*
|
||||
* Cubescript has a selection of idents. This represents the type of each.
|
||||
*/
|
||||
enum class ident_type {
|
||||
VAR = 0, /**< @brief Builtin variable. */
|
||||
COMMAND, /**< @brief Builtin command. */
|
||||
ALIAS, /**< @brief User assigned variable. */
|
||||
SPECIAL /**< @brief Other (internal unexposed type). */
|
||||
};
|
||||
|
||||
/** @brief The ident structure.
|
||||
*
|
||||
* Every object within the Cubescript language is represented with an ident.
|
||||
* This is the generic base interface. There are some operations that are
|
||||
* available on any ident.
|
||||
*
|
||||
* You can also check the actual type with it (cubescript::ident_type) and
|
||||
* decide to cast it to its appropriate specific type, or use the helpers.
|
||||
*
|
||||
* An ident always has a valid name. A valid name is pretty much any
|
||||
* valid Cubescript word (see cubescript::parse_word()) which does not
|
||||
* begin with a number (a digit, a `+` or `-` followed by a digit or a
|
||||
* period followed by a digit, or a period followed by a digit).
|
||||
*/
|
||||
struct LIBCUBESCRIPT_EXPORT ident {
|
||||
/** @brief Get the cubescript::ident_type of this ident. */
|
||||
ident_type type() const;
|
||||
|
||||
/** @brief Get a view to the name of the ident. */
|
||||
std::string_view name() const;
|
||||
|
||||
/** @brief Get the index of the ident.
|
||||
*
|
||||
* Idents are internally indexed. There is no guarantee of what index
|
||||
* the ident will have, but you can still use it to identify the object
|
||||
* with an integer (it is guaranteed that once created, it will stay the
|
||||
* same for the whole lifetime of the main thread).
|
||||
*/
|
||||
int index() const;
|
||||
|
||||
/** @brief Check if the idents are the same. */
|
||||
bool operator==(ident &other) const;
|
||||
|
||||
/** @brief Check if the idents are not the same. */
|
||||
bool operator!=(ident &other) const;
|
||||
|
||||
/** @brief Get if the ident is overridden.
|
||||
*
|
||||
* This can be true for aliases or builtins. When an alias or a builtin
|
||||
* is assigned to and the VM is in override mode or the builtin is
|
||||
* var_type::OVERRIDABLE, they are marked as overridden (and builtins
|
||||
* have their value saved beforehand).
|
||||
*
|
||||
* This can be cleared later, which will erase the value (for aliases)
|
||||
* or restore the saved one (for builtins). For aliases, this can be
|
||||
* specific to the Cubescript thread.
|
||||
*/
|
||||
bool is_overridden(state &cs) const;
|
||||
|
||||
/** @brief Get if the ident is persistent.
|
||||
*
|
||||
* This can be true in two cases. Either it's a builtin and it has the
|
||||
* var_type::PERSISTENT flag, or it's an alias that is assigned to while
|
||||
* the VM is in persist mode. The latter can be thread specific (when the
|
||||
* alias is currently pushed).
|
||||
*/
|
||||
bool is_persistent(state &cs) const;
|
||||
|
||||
/** @brief Call the ident.
|
||||
*
|
||||
* The default implementation just throws a cubescript::error, since it
|
||||
* is not callable. It can be overridden as needed.
|
||||
*
|
||||
* If a command, it will simply be executed with the given arguments,
|
||||
* ensuring that missing ones are filled in and types are set properly.
|
||||
* If a builtin variable, the appropriate handler will be called. If
|
||||
* an alias, the value of it will be compiled and executed. Any other
|
||||
* ident type will simply do nothing.
|
||||
*
|
||||
* @return the return value
|
||||
*/
|
||||
virtual any_value call(span_type<any_value> args, state &cs);
|
||||
|
||||
protected:
|
||||
friend struct ident_p;
|
||||
|
||||
ident() = default;
|
||||
virtual ~ident();
|
||||
|
||||
struct ident_impl *p_impl{};
|
||||
};
|
||||
|
||||
/** @brief An additional cubescript::builtin_var type.
|
||||
*
|
||||
* Global vars can have no additional type, or they can be persistent, or
|
||||
* they can be overridable. Persistent variables are meant to be saved and
|
||||
* loaded later (the actual logic is up to the user of the library).
|
||||
*
|
||||
* Overridable variables are overridden when assigned to (this can also
|
||||
* happen to normal variables when the VM is in override mode), which saves
|
||||
* their old value (which can be restored later when un-overridden). This
|
||||
* is mutually exclusive; overridable variables cannot be persistent, and
|
||||
* attempting to assign to a persistent variable while the VM is in override
|
||||
* mode will raise an error.
|
||||
*/
|
||||
enum class var_type {
|
||||
DEFAULT = 0, /**< @brief The default type. */
|
||||
PERSISTENT, /**< @brief Persistent variable. */
|
||||
OVERRIDABLE /**< @brief Overridable variable. */
|
||||
};
|
||||
|
||||
/** @brief A builtin variable.
|
||||
*
|
||||
* This represents a strictly typed variable (integer, float or string,
|
||||
* depending on the value it is created with) that is not subject to
|
||||
* usual rules like aliases (e.g. scoping). It can have additional
|
||||
* inherent properties such as being read-only or peresistent, and
|
||||
* can be monitored via a trigger callback.
|
||||
*/
|
||||
struct LIBCUBESCRIPT_EXPORT builtin_var: ident {
|
||||
/** @brief Get whether the variable is read only.
|
||||
*
|
||||
* Variables can be set as read only during their creation (but not
|
||||
* later). This will prevent assignments to them from within the language
|
||||
* or using checked APIs, but it is still possible to assign to them
|
||||
* using raw APIs. The raw APIs will not invoke value triggers, however.
|
||||
*/
|
||||
bool is_read_only() const;
|
||||
|
||||
/** @brief Get whether the variable is overridable.
|
||||
*
|
||||
* Equivalent to `variable_type() == var_type::OVERRIDABLE`.
|
||||
*/
|
||||
bool is_overridable() const;
|
||||
|
||||
/** @brief Get the cubescript::var_type of the variable. */
|
||||
var_type variable_type() const;
|
||||
|
||||
/** @brief Save the variable.
|
||||
*
|
||||
* This is mainly intended for variable assignment triggers. If the
|
||||
* variable is overridable or the given thread is in override mode,
|
||||
* this will save the current value of the variable (if not already
|
||||
* overridden). Otherwise, it will clear any existing overridden flag.
|
||||
*
|
||||
* @throw cubescript::error if the thread is in override mode and the
|
||||
* variable is persistent.
|
||||
*/
|
||||
void save(state &cs);
|
||||
|
||||
/** @brief Call the variable.
|
||||
*
|
||||
* While variables are not callable by themselves, this acts like
|
||||
* if calling the variable in the language. By default, that means
|
||||
* doing it with zero arguments retrieves its value, while passing
|
||||
* arguments will set its value. The actual semantics depend on how
|
||||
* the handler is set up for each variable type.
|
||||
*/
|
||||
any_value call(span_type<any_value> args, state &cs);
|
||||
|
||||
/** @brief Get the value of the variable. */
|
||||
any_value value() const;
|
||||
|
||||
/** @brief Set the value of the variable in a raw manner.
|
||||
*
|
||||
* This will always set the value and ignore any kinds of checks. It will
|
||||
* not invoke any triggers either, nor it will save the the value. However,
|
||||
* it will make sure to preserve the type (integer, float or string).
|
||||
*/
|
||||
void set_raw_value(state &cs, any_value val);
|
||||
|
||||
/** @brief Set the value of the variable.
|
||||
*
|
||||
* If read only, an error is raised. If `do_write` is `false`, nothing
|
||||
* will be performed other than the read-only checking. If `trigger` is
|
||||
* `false`, a potential variable change trigger command will not be
|
||||
* invoked. The value is saved with save(), assuming `do_write` is `true`.
|
||||
* After that, set_raw_value() is invoked, and then the trigger.
|
||||
*
|
||||
* @throw cubescript::error if read only or if the changed trigger throws.
|
||||
*/
|
||||
void set_value(
|
||||
state &cs, any_value val, bool do_write = true, bool trigger = true
|
||||
);
|
||||
|
||||
protected:
|
||||
builtin_var() = default;
|
||||
};
|
||||
|
||||
/** @brief An alias.
|
||||
*
|
||||
* An alias is an ident that is created inside the language, for example
|
||||
* by assignment. Any variable that you can assign to or look up and is not
|
||||
* a builtin is an alias. Aliases don't have special assignment syntax nor
|
||||
* they have changed triggers nor value saving. They technically always
|
||||
* represent a string within the language, though on C++ side they can
|
||||
* have float or integer values too.
|
||||
*/
|
||||
struct LIBCUBESCRIPT_EXPORT alias: ident {
|
||||
/** @brief Get if this alias represents a function argument.
|
||||
*
|
||||
* This is true for `argN` aliases representing the arguments passed to
|
||||
* the current function.
|
||||
*/
|
||||
bool is_arg() const;
|
||||
|
||||
/** @brief Get the value of the alias for the given thread. */
|
||||
any_value value(state &cs) const;
|
||||
|
||||
/** @brief Set the value of the alias for the given thread. */
|
||||
void set_value(state &cs, any_value v);
|
||||
|
||||
/** @brief Call an alias.
|
||||
*
|
||||
* The alias will be called like if it was called in the language.
|
||||
*/
|
||||
any_value call(span_type<any_value> args, state &cs);
|
||||
|
||||
protected:
|
||||
alias() = default;
|
||||
};
|
||||
|
||||
/** @brief A command.
|
||||
*
|
||||
* Commands are builtins that can be invoked from the language and have a
|
||||
* native implementation registered from C++. Once registered, a command
|
||||
* cannot be unregistered or otherwise changed.
|
||||
*/
|
||||
struct LIBCUBESCRIPT_EXPORT command: ident {
|
||||
/** @brief Get the argument list. */
|
||||
std::string_view args() const;
|
||||
|
||||
/** @brief Get the number of arguments the command expects.
|
||||
*
|
||||
* Only non-variadic arguments count here (i.e. no repeated arguments,
|
||||
* no `C`, no `V`; everything else counts as one argument).
|
||||
*/
|
||||
int arg_count() const;
|
||||
|
||||
/** @brief Call a command.
|
||||
*
|
||||
* The command will be called like if it was called in the language.
|
||||
*/
|
||||
any_value call(span_type<any_value> args, state &cs);
|
||||
|
||||
protected:
|
||||
command() = default;
|
||||
};
|
||||
|
||||
/** @brief A safe alias handler for commands
|
||||
*
|
||||
* In general, when dealing with aliases in commands, you do not want to
|
||||
* set them directly, since this would set the alias globally. Instead, you
|
||||
* can use this to make aliases local to the command.
|
||||
*
|
||||
* Internally, each Cubescript thread has a mapping for alias state within
|
||||
* the thread. This mapping is stack based - which means you can push an
|
||||
* alias, and then anything affecting the value of the alias in that thread
|
||||
* will only be visible until the stack is popped. This structure provides
|
||||
* a safe means of handling the alias stack; constructing it will push the
|
||||
* alias, destroying it will pop it.
|
||||
*
|
||||
* Therefore, what you can do is something like this:
|
||||
*
|
||||
* ```
|
||||
* {
|
||||
* alias_local s{my_thread, "test"};
|
||||
* // branch taken when the alias was successfully pushed
|
||||
* // setting the alias will only be visible within this scope
|
||||
* s.set(some_value); // a convenient setter
|
||||
* my_thread.run(...);
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* If the provided input is not an alias, a cubescript::error will be thrown.
|
||||
* Often you don't have to catch it (since this is primarily intended for use
|
||||
* within commands, the error will propagate outside your command).
|
||||
*
|
||||
* Since the goal is to interact tightly with RAII and ensure consistency at
|
||||
* all times, it is not possible to copy or move this object. That means you
|
||||
* should also not be storing it; it should be used purely as a scope based
|
||||
* alias stack manager.
|
||||
*/
|
||||
struct LIBCUBESCRIPT_EXPORT alias_local {
|
||||
/** @brief Construct the local handler */
|
||||
alias_local(state &cs, ident &a);
|
||||
|
||||
/** @brief Construct the local handler
|
||||
*
|
||||
* The ident will be retrieved using state::new_ident().
|
||||
*/
|
||||
alias_local(state &cs, std::string_view name);
|
||||
|
||||
/** @brief Construct the local handler
|
||||
*
|
||||
* The ident will be retrieved from the value. If the contained value
|
||||
* is not an ident, it will be treated as a name.
|
||||
*/
|
||||
alias_local(state &cs, any_value const &val);
|
||||
|
||||
/** @brief Destroy the local handler */
|
||||
~alias_local();
|
||||
|
||||
/** @brief Local handlers are not copyable */
|
||||
alias_local(alias_local const &) = delete;
|
||||
|
||||
/** @brief Local handlers are not movable */
|
||||
alias_local(alias_local &&) = delete;
|
||||
|
||||
/** @brief Local handlers are not copy assignable */
|
||||
alias_local &operator=(alias_local const &) = delete;
|
||||
|
||||
/** @brief Local handlers are not move assignable */
|
||||
alias_local &operator=(alias_local &&v) = delete;
|
||||
|
||||
/** @brief Get the contained alias */
|
||||
alias &get_alias() noexcept { return *p_alias; }
|
||||
|
||||
/** @brief Get the contained alias */
|
||||
alias const &get_alias() const noexcept { return *p_alias; }
|
||||
|
||||
/** @brief Set the contained alias's value
|
||||
*
|
||||
* @return `true` if the alias is valid, `false` otherwise
|
||||
*/
|
||||
bool set(any_value val);
|
||||
|
||||
private:
|
||||
alias *p_alias;
|
||||
void *p_sp;
|
||||
};
|
||||
|
||||
} /* namespace cubescript */
|
||||
|
||||
#endif /* LIBCUBESCRIPT_CUBESCRIPT_IDENT_HH */
|
|
@ -1,73 +0,0 @@
|
|||
/** @file platform.hh
|
||||
*
|
||||
* @brief Utility macros and platform abstraction.
|
||||
*
|
||||
* Defines utility macros that you are not supposed to use yourself.
|
||||
*
|
||||
* @copyright See COPYING.md in the project tree for further information.
|
||||
*/
|
||||
|
||||
#ifndef LIBCUBESCRIPT_CUBESCRIPT_PLATFORM_HH
|
||||
#define LIBCUBESCRIPT_CUBESCRIPT_PLATFORM_HH
|
||||
|
||||
namespace cubescript {
|
||||
|
||||
#ifdef LIBCS_GENERATING_DOC
|
||||
|
||||
/** @brief Public API tag.
|
||||
*
|
||||
* All public API of the library is tagged like this.
|
||||
*
|
||||
* On Windows, the behavior of this is conditional. If `LIBCUBESCRIPT_DLL` is
|
||||
* not defined, it expands to no value (that means we're either building or
|
||||
* using a static library). If it is defined, it will tag the API with either
|
||||
* `dllexport` (when building the lib, defined with `LIBCUBESCRIPT_BUILD`)
|
||||
* or `dllimport` (when using the lib).
|
||||
*
|
||||
* On Unix-like systems with GCC-style compilers, this will mark the API as
|
||||
* externally visible. The library is by default built so that symbols are
|
||||
* normally hidden, so any external API needs to be tagged.
|
||||
*
|
||||
* @see LIBCUBESCRIPT_LOCAL
|
||||
*/
|
||||
#define LIBCUBESCRIPT_EXPORT
|
||||
|
||||
/** @brief Private API tag.
|
||||
*
|
||||
* Since symbols are private by default, this usually has no purpose. However,
|
||||
* when marking entire structures exported, this affects all methods inside;
|
||||
* in those cases this can be used to mark specific methods as for use only
|
||||
* inside of the library (private methods not called in any public header).
|
||||
*
|
||||
* @see LIBCUBESCRIPT_EXPORT
|
||||
*/
|
||||
#define LIBCUBESCRIPT_LOCAL
|
||||
|
||||
#else
|
||||
|
||||
#if defined(__CYGWIN__) || (defined(_WIN32) && !defined(_XBOX_VER))
|
||||
# ifdef LIBCUBESCRIPT_DLL
|
||||
# ifdef LIBCUBESCRIPT_BUILD
|
||||
# define LIBCUBESCRIPT_EXPORT __declspec(dllexport)
|
||||
# else
|
||||
# define LIBCUBESCRIPT_EXPORT __declspec(dllimport)
|
||||
# endif
|
||||
# else
|
||||
# define LIBCUBESCRIPT_EXPORT
|
||||
# endif
|
||||
# define LIBCUBESCRIPT_LOCAL
|
||||
#else
|
||||
# if defined(__GNUC__) && (__GNUC__ >= 4)
|
||||
# define LIBCUBESCRIPT_EXPORT __attribute__((visibility("default")))
|
||||
# define LIBCUBESCRIPT_LOCAL __attribute__((visibility("hidden")))
|
||||
# else
|
||||
# define LIBCUBESCRIPT_EXPORT
|
||||
# define LIBCUBESCRIPT_LOCAL
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#endif /* LIBCS_GENERATING_DOC */
|
||||
|
||||
} /* namespace cubescript */
|
||||
|
||||
#endif /* LIBCUBESCRIPT_CUBESCRIPT_PLATFORM_HH */
|
|
@ -1,555 +0,0 @@
|
|||
/** @file state.hh
|
||||
*
|
||||
* @brief State API.
|
||||
*
|
||||
* The state is the main handle using which you interact with the language
|
||||
* from C++. It represents a single Cubescript thread.
|
||||
*
|
||||
* @copyright See COPYING.md in the project tree for further information.
|
||||
*/
|
||||
|
||||
#ifndef LIBCUBESCRIPT_CUBESCRIPT_STATE_HH
|
||||
#define LIBCUBESCRIPT_CUBESCRIPT_STATE_HH
|
||||
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include <optional>
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
|
||||
#include "callable.hh"
|
||||
#include "ident.hh"
|
||||
#include "value.hh"
|
||||
|
||||
namespace cubescript {
|
||||
|
||||
struct state;
|
||||
|
||||
/** @brief The allocator function signature
|
||||
*
|
||||
* This is the signature of the function pointer passed to do allocations.
|
||||
*
|
||||
* The first argument is the user data, followed by the old pointer (which
|
||||
* is `nullptr` for new allocations and a valid pointer for reallocations
|
||||
* and frees). Then follows the original size of the object (zero for new
|
||||
* allocations, a valid value for reallocations and frees) and the new
|
||||
* size of the object (zero for frees, a valid value for reallocations
|
||||
* and new allocations).
|
||||
*
|
||||
* It must return the new pointer (`nullptr` when freeing) and does not have
|
||||
* to throw (the library will throw `std::bad_alloc` itself if it receives
|
||||
* a `nullptr` upon allocation).
|
||||
*
|
||||
* A typical allocation function will look like this:
|
||||
*
|
||||
* ```
|
||||
* void *my_alloc(void *, void *p, std::size_t, std::size_t ns) {
|
||||
* if (!ns) {
|
||||
* std::free(p);
|
||||
* return nullptr;
|
||||
* }
|
||||
* return std::realloc(p, ns);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
using alloc_func = void *(*)(void *, void *, size_t, size_t);
|
||||
|
||||
/** @brief A call hook function
|
||||
*
|
||||
* It is possible to set up a call hook for each thread, which is called
|
||||
* upon entering the VM. The hook returns nothing and receives the thread
|
||||
* reference.
|
||||
*/
|
||||
using hook_func = internal::callable<void, state &>;
|
||||
|
||||
/** @brief A command function
|
||||
*
|
||||
* This is how every command looks. It returns nothing and takes the thread
|
||||
* reference, a span of input arguments, and a reference to return value.
|
||||
*/
|
||||
using command_func = internal::callable<
|
||||
void, state &, span_type<any_value>, any_value &
|
||||
>;
|
||||
|
||||
/** @brief The Cubescript thread
|
||||
*
|
||||
* Represents a Cubescript thread, either the main thread or a side thread
|
||||
* depending on how it's created. The state is what you create first and
|
||||
* also what you should always destroy last.
|
||||
*/
|
||||
struct LIBCUBESCRIPT_EXPORT state {
|
||||
/** @brief Create a new Cubescript main thread
|
||||
*
|
||||
* This creates a main thread without specifying an allocation function,
|
||||
* using a simple, builtin implementation. Otherwise it is the same.
|
||||
*/
|
||||
state();
|
||||
|
||||
/** @brief Create a new Cubescript main thread
|
||||
*
|
||||
* For this variant you have to specify a function used to allocate memory.
|
||||
* The optional data will be passed to allocation every time and is your
|
||||
* only way to pass custom data to it, since unlike other kinds of hooks,
|
||||
* the allocation function is a plain function pointer to ensure it never
|
||||
* allocates by itself.
|
||||
*/
|
||||
state(alloc_func func, void *data = nullptr);
|
||||
|
||||
/** @brief Destroy the thread
|
||||
*
|
||||
* If the thread is a main thread, all state is destroyed. That means
|
||||
* main threads should always be destroyed last.
|
||||
*/
|
||||
virtual ~state();
|
||||
|
||||
/** @brief Cubescript threads are not copyable */
|
||||
state(state const &) = delete;
|
||||
|
||||
/** @brief Move-construct the Cubescript thread
|
||||
*
|
||||
* Keep in mind that you should never use `s` after this is done.
|
||||
*/
|
||||
state(state &&s);
|
||||
|
||||
/** @brief Cubescript threads are not copy assignable */
|
||||
state &operator=(state const &) = delete;
|
||||
|
||||
/** @brief Move-assign the Cubescript thread
|
||||
*
|
||||
* Keep in mind that you should never use `s` after this is done.
|
||||
* The original `this` is destroyed in the process.
|
||||
*/
|
||||
state &operator=(state &&s);
|
||||
|
||||
/** @brief Swap two Cubescript threads */
|
||||
void swap(state &s);
|
||||
|
||||
/** @brief Create a non-main thread
|
||||
*
|
||||
* This creates a non-main thread. You can also create non-main threads
|
||||
* using other non-main threads, but they will always all be dependent
|
||||
* on the main thread they originally came from.
|
||||
*
|
||||
* @return the thread
|
||||
*/
|
||||
state new_thread();
|
||||
|
||||
/** @brief Attach a call hook to the thread
|
||||
*
|
||||
* The call hook is called every time the VM is entered. You can use
|
||||
* this for debugging and other tracking, or say, as a means of
|
||||
* interrupting execution from the side in an interactive interpreter.
|
||||
*/
|
||||
template<typename F>
|
||||
hook_func call_hook(F &&f) {
|
||||
return call_hook(
|
||||
hook_func{std::forward<F>(f), callable_alloc, this}
|
||||
);
|
||||
}
|
||||
|
||||
/** @brief Get a reference to the call hook */
|
||||
hook_func const &call_hook() const;
|
||||
|
||||
/** @brief Get a reference to the call hook */
|
||||
hook_func &call_hook();
|
||||
|
||||
/** @brief Clear override state for the given ident
|
||||
*
|
||||
* If the ident is overridden, clear the flag. Global variables will have
|
||||
* their value restored to the original, and the changed hook will be
|
||||
* triggered. Aliases will be set to an empty string.
|
||||
*
|
||||
* Other ident types will do nothing.
|
||||
*/
|
||||
void clear_override(ident &id);
|
||||
|
||||
/** @brief Clear override state for all idents.
|
||||
*
|
||||
* @see clear_override()
|
||||
*/
|
||||
void clear_overrides();
|
||||
|
||||
/** @brief Create a new integer var
|
||||
*
|
||||
* @param n the name
|
||||
* @param v the default value
|
||||
* @throw cubescript::error in case of redefinition or invalid name
|
||||
*/
|
||||
builtin_var &new_var(
|
||||
std::string_view n, integer_type v, bool read_only = false,
|
||||
var_type vtp = var_type::DEFAULT
|
||||
);
|
||||
|
||||
/** @brief Create a new float var
|
||||
*
|
||||
* @param n the name
|
||||
* @param v the default value
|
||||
* @throw cubescript::error in case of redefinition or invalid name
|
||||
*/
|
||||
builtin_var &new_var(
|
||||
std::string_view n, float_type v, bool read_only = false,
|
||||
var_type vtp = var_type::DEFAULT
|
||||
);
|
||||
|
||||
/** @brief Create a new string var
|
||||
*
|
||||
* @param n the name
|
||||
* @param v the default value
|
||||
* @throw cubescript::error in case of redefinition or invalid name
|
||||
*/
|
||||
builtin_var &new_var(
|
||||
std::string_view n, std::string_view v, bool read_only = false,
|
||||
var_type vtp = var_type::DEFAULT
|
||||
);
|
||||
|
||||
/** @brief Create a new ident
|
||||
*
|
||||
* If such ident already exists, nothing will be done and a reference
|
||||
* will be returned. Otherwise, a new alias will be created and this
|
||||
* alias will be returned, however it will not be visible from the
|
||||
* language until actually assigned (it does not exist to the language
|
||||
* just as is).
|
||||
*
|
||||
* @param n the name
|
||||
* @throw cubescript::error in case of invalid name
|
||||
*/
|
||||
ident &new_ident(std::string_view n);
|
||||
|
||||
/** @brief Get the number of idents in the state
|
||||
*
|
||||
* This returns the number of idents that the main state has stored. It
|
||||
* does not matter which thread you call this on.
|
||||
*/
|
||||
std::size_t ident_count() const;
|
||||
|
||||
/** @brief Get a specific cubescript::ident */
|
||||
std::optional<std::reference_wrapper<ident>> get_ident(
|
||||
std::string_view name
|
||||
);
|
||||
|
||||
/** @brief Get a specific cubescript::ident */
|
||||
std::optional<std::reference_wrapper<ident const>> get_ident(
|
||||
std::string_view name
|
||||
) const;
|
||||
|
||||
/** @brief Get a specific cubescript::ident by index
|
||||
*
|
||||
* Keep in mind that no bounds checking is performed, so the index must
|
||||
* be within range.
|
||||
*/
|
||||
ident &get_ident(std::size_t index);
|
||||
|
||||
/** @brief Get a specific cubescript::ident by index
|
||||
*
|
||||
* Keep in mind that no bounds checking is performed, so the index must
|
||||
* be within range.
|
||||
*/
|
||||
ident const &get_ident(std::size_t index) const;
|
||||
|
||||
/** @brief Assign a value to a name
|
||||
*
|
||||
* This will set something of the given name to the given value. The
|
||||
* something may be a variable or an alias.
|
||||
*
|
||||
* If no ident of such name exists, a new alias will be created and
|
||||
* set.
|
||||
*
|
||||
* @throw cubescript::error if `name` is a builtin ident (a registered
|
||||
* command or similar) or if it is invalid
|
||||
*
|
||||
* @see lookup_value()
|
||||
* @see reset_value()
|
||||
* @see touch_value()
|
||||
*/
|
||||
void assign_value(std::string_view name, any_value v);
|
||||
|
||||
/** @brief Lookup a value by name
|
||||
*
|
||||
* This will lookup an ident of the given name and return its value.
|
||||
* The ident may be a variable or an alias.
|
||||
*
|
||||
* @throw cubescript::error if `name` does not exist or belongs to an
|
||||
* ident that doesn't support lookups
|
||||
*
|
||||
* @see assign_value()
|
||||
* @see reset_value()
|
||||
* @see touch_value()
|
||||
*/
|
||||
any_value lookup_value(std::string_view name);
|
||||
|
||||
/** @brief Reset a value by name
|
||||
*
|
||||
* This is like clear_override() except it works by name and performs
|
||||
* extra checks.
|
||||
*
|
||||
* @throw cubescript::error if non-existent or read only
|
||||
*
|
||||
* @see assign_value()
|
||||
* @see lookup_value()
|
||||
* @see touch_value()
|
||||
*/
|
||||
void reset_value(std::string_view name);
|
||||
|
||||
/** @brief Touch a value by name
|
||||
*
|
||||
* If an ident with the given name exists and is a global variable,
|
||||
* a changed hook will be triggered with it, acting like if a new
|
||||
* value was set, but without actually setting it.
|
||||
*
|
||||
* @see assign_value()
|
||||
* @see lookup_value()
|
||||
* @see reset_value()
|
||||
*/
|
||||
void touch_value(std::string_view name);
|
||||
|
||||
/** @brief Register a command
|
||||
*
|
||||
* This registers a builtin command. A command consists of a valid name,
|
||||
* a valid argument list, and a function to call.
|
||||
*
|
||||
* The argument list is a simple list of types. Currently the following
|
||||
* simple types are recognized:
|
||||
*
|
||||
* * `s` - a string
|
||||
* * `i` - an integer
|
||||
* * `f` - a float
|
||||
* * `a` - any (passed as is)
|
||||
* * `b` - bytecode/block
|
||||
* * `c` - condition (see below)
|
||||
* * `v` - ident (variable/alias/any kind)
|
||||
* * `#` - number of real arguments passed up until now
|
||||
* * `$` - self ident (the command, except for special hooks)
|
||||
*
|
||||
* For condition types, the type of the value is generally kept as is,
|
||||
* except for non-empty strings, which are compiled as bytecode. Therefore,
|
||||
* to check condition types, you first try to get their bytecode; if it is
|
||||
* valid, you run it and use its result, otherwise use the value as is,
|
||||
* and then evaluate it as a boolean.
|
||||
*
|
||||
* When an argument is not provided by the caller, it is assigned to none
|
||||
* type. Using the appropriate getters on the value structure will get
|
||||
* you fallback defaults (e.g. 0 for integer and so on) but you can still
|
||||
* check the type explicitly for whether it was actually provided.
|
||||
*
|
||||
* Commands also support variadics. Variadic commands have their type
|
||||
* list suffixed with `...`.
|
||||
*
|
||||
* If `...` is used alone, the inputs are any arbitrary values. However,
|
||||
* they can also be used with repetition. Repetition works for example
|
||||
* like `if2...`. The `2` is the number of types to repeat; it must be at
|
||||
* most the number of simple types preceeding it. It must be followed by
|
||||
* `...`. This specific example means that the variadic arguments are a
|
||||
* sequence of integer, float, integer, float, integer, float and so on.
|
||||
*
|
||||
* The resulting command stores the number of arguments it takes. The
|
||||
* variadic part is not a part of it (neither is the part subject to
|
||||
* repetition), while all simple types are a part of it (including
|
||||
* 'fake' ones like argument count).
|
||||
*
|
||||
* It is also possible to register special commands. Special commands work
|
||||
* like normal ones but are special-purpose. The currently allowed special
|
||||
* commands are `//ivar`, `//fvar`, `//svar` and `//var_changed`. These
|
||||
* are the only commands where the name can be in this format.
|
||||
*
|
||||
* The first three are handlers for for global variables, used when either
|
||||
* printing or setting them using syntax `varname optional_vals` or using
|
||||
* `varname = value`. Their type signature must always start with `$`
|
||||
* and can be followed by any user types, generally you will also want
|
||||
* to terminate the list with `#` to find out whether any values were
|
||||
* passed.
|
||||
*
|
||||
* This way you can have custom handlers for printing as well as custom
|
||||
* syntaxes for setting (e.g. your custom integer var handler may want to
|
||||
* take up to 4 values to allow setting of RGBA color channels). When no
|
||||
* arguments are passed (checked using `#`) you will want to print the
|
||||
* value using a format you want. When using the `=` assignment syntax,
|
||||
* one value is passed.
|
||||
*
|
||||
* There are builtin default handlers that take at most one arg (`i`, `f`
|
||||
* and `s`) which also print to standard output (`name = value`).
|
||||
*
|
||||
* For `//var_changed`, there is no default handler. The arg list must be
|
||||
* `$aa`. This will be called whenever the value of a builtin variable of
|
||||
* any type changes, and will be passed the variable as its first argument,
|
||||
* the previous value as the second argument and the new value as the third
|
||||
* argument (mainly for convenience).
|
||||
*
|
||||
* For these builtins, `$` will refer to the variable ident, not to the
|
||||
* builtin command.
|
||||
*
|
||||
* @throw cubescript::error upon redefinition, invalid name or arg list
|
||||
*/
|
||||
template<typename F>
|
||||
command &new_command(
|
||||
std::string_view name, std::string_view args, F &&f
|
||||
) {
|
||||
return new_command(
|
||||
name, args,
|
||||
command_func{std::forward<F>(f), callable_alloc, this}
|
||||
);
|
||||
}
|
||||
|
||||
/** @brief Compile a string.
|
||||
*
|
||||
* This compiles the given string, optionally using `source` as a filename
|
||||
* for debug information (useful when implementing file I/O functions).
|
||||
*
|
||||
* @return a bytecode reference
|
||||
* @throw cubescript::error on compilation failure
|
||||
*/
|
||||
bcode_ref compile(
|
||||
std::string_view v, std::string_view source = std::string_view{}
|
||||
);
|
||||
|
||||
/** @brief Get if the thread is in override mode
|
||||
*
|
||||
* If the thread is in override mode, any assigned alias or variable will
|
||||
* be given the overridden flag, with variables also saving their old
|
||||
* value. Upon clearing the flag (using clear_override() or similar)
|
||||
* the old value will be restored (aliases will be set to an empty
|
||||
* string).
|
||||
*
|
||||
* Overridable variables will always act like if the thread is in override
|
||||
* mode, even if it's not.
|
||||
*
|
||||
* Keep in mind that if an alias is pushed, its flags will be cleared once
|
||||
* popped.
|
||||
*
|
||||
* @see override_mode()
|
||||
*/
|
||||
bool override_mode() const;
|
||||
|
||||
/** @brief Set the thread's override mode */
|
||||
bool override_mode(bool v);
|
||||
|
||||
/** @brief Get if the thread is in persist most
|
||||
*
|
||||
* In persist mode, newly assigned aliases will have the persist flag
|
||||
* set on them, which is an indicator that they should be saved to disk
|
||||
* like persistent variables. The library does no saving, so by default
|
||||
* it works as an indicator for the user.
|
||||
*
|
||||
* Keep in mind that if an alias is pushed, its flags will be cleared once
|
||||
* popped.
|
||||
*/
|
||||
bool persist_mode() const;
|
||||
|
||||
/** @brief Set the thread's persist mode */
|
||||
bool persist_mode(bool v);
|
||||
|
||||
/** @brief Get the maximum call depth of the VM
|
||||
*
|
||||
* If zero, it is unlimited, otherwise it specifies how much the VM is
|
||||
* allowed to recurse. By default, it is 1024.
|
||||
*/
|
||||
std::size_t max_call_depth() const;
|
||||
|
||||
/** @brief Set the maximum call depth ov the VM
|
||||
*
|
||||
* If zero, it is unlimited (the default is 1024). You can limit how much
|
||||
* the VM is allowed to recurse if you have specific constraints to adhere
|
||||
* to.
|
||||
*
|
||||
* @return the old value
|
||||
*/
|
||||
std::size_t max_call_depth(std::size_t v);
|
||||
|
||||
private:
|
||||
friend struct state_p;
|
||||
|
||||
LIBCUBESCRIPT_LOCAL state(void *is);
|
||||
|
||||
hook_func call_hook(hook_func func);
|
||||
|
||||
command &new_command(
|
||||
std::string_view name, std::string_view args, command_func func
|
||||
);
|
||||
|
||||
static void *callable_alloc(
|
||||
void *data, void *p, std::size_t os, std::size_t ns
|
||||
) {
|
||||
return static_cast<state *>(data)->alloc(p, os, ns);
|
||||
}
|
||||
|
||||
void *alloc(void *ptr, size_t olds, size_t news);
|
||||
|
||||
struct thread_state *p_tstate = nullptr;
|
||||
};
|
||||
|
||||
/** @brief Initialize the base library
|
||||
*
|
||||
* You can choose which parts of the standard library you include in your
|
||||
* program. The base library contains core constructs for things such as
|
||||
* error handling, conditionals, looping, and var/alias management.
|
||||
*
|
||||
* Calling this multiple times has no effect; commands will only be
|
||||
* registered once.
|
||||
*
|
||||
* @see cubescript::std_init_math()
|
||||
* @see cubescript::std_init_string()
|
||||
* @see cubescript::std_init_list()
|
||||
* @see cubescript::std_init_all()
|
||||
*/
|
||||
LIBCUBESCRIPT_EXPORT void std_init_base(state &cs);
|
||||
|
||||
/** @brief Initialize the math library
|
||||
*
|
||||
* You can choose which parts of the standard library you include in your
|
||||
* program. The math library contains arithmetic and other math related
|
||||
* functions.
|
||||
*
|
||||
* Calling this multiple times has no effect; commands will only be
|
||||
* registered once.
|
||||
*
|
||||
* @see cubescript::std_init_base()
|
||||
* @see cubescript::std_init_string()
|
||||
* @see cubescript::std_init_list()
|
||||
* @see cubescript::std_init_all()
|
||||
*/
|
||||
LIBCUBESCRIPT_EXPORT void std_init_math(state &cs);
|
||||
|
||||
/** @brief Initialize the string library
|
||||
*
|
||||
* You can choose which parts of the standard library you include in your
|
||||
* program. The string library contains commands to manipulate strings.
|
||||
*
|
||||
* Calling this multiple times has no effect; commands will only be
|
||||
* registered once.
|
||||
*
|
||||
* @see cubescript::std_init_base()
|
||||
* @see cubescript::std_init_math()
|
||||
* @see cubescript::std_init_list()
|
||||
* @see cubescript::std_init_all()
|
||||
*/
|
||||
LIBCUBESCRIPT_EXPORT void std_init_string(state &cs);
|
||||
|
||||
/** @brief Initialize the list library
|
||||
*
|
||||
* You can choose which parts of the standard library you include in your
|
||||
* program. The list library contains commands to manipulate lists.
|
||||
*
|
||||
* Calling this multiple times has no effect; commands will only be
|
||||
* registered once.
|
||||
*
|
||||
* @see cubescript::std_init_base()
|
||||
* @see cubescript::std_init_math()
|
||||
* @see cubescript::std_init_string()
|
||||
* @see cubescript::std_init_all()
|
||||
*/
|
||||
LIBCUBESCRIPT_EXPORT void std_init_list(state &cs);
|
||||
|
||||
/** @brief Initialize all standard libraries
|
||||
*
|
||||
* This is like calling each of the individual standard library init
|
||||
* functions and exists mostly just for convenience.
|
||||
|
||||
* @see cubescript::std_init_base()
|
||||
* @see cubescript::std_init_math()
|
||||
* @see cubescript::std_init_string()
|
||||
* @see cubescript::std_init_list()
|
||||
*/
|
||||
LIBCUBESCRIPT_EXPORT void std_init_all(state &cs);
|
||||
|
||||
} /* namespace cubescript */
|
||||
|
||||
#endif /* LIBCUBESCRIPT_CUBESCRIPT_STATE_HH */
|
|
@ -1,285 +0,0 @@
|
|||
/** @file util.hh
|
||||
*
|
||||
* @brief Utility API.
|
||||
*
|
||||
* This contains various utilities that don't quite fit within the other
|
||||
* structures, but provide convenience; this includes things such as parsing
|
||||
* of lists, strings and numbers.
|
||||
*
|
||||
* @copyright See COPYING.md in the project tree for further information.
|
||||
*/
|
||||
|
||||
#ifndef LIBCUBESCRIPT_CUBESCRIPT_UTIL_HH
|
||||
#define LIBCUBESCRIPT_CUBESCRIPT_UTIL_HH
|
||||
|
||||
#include <cstddef>
|
||||
#include <string_view>
|
||||
#include <algorithm>
|
||||
|
||||
#include "ident.hh"
|
||||
|
||||
namespace cubescript {
|
||||
|
||||
/** @brief A list parser
|
||||
*
|
||||
* Cubescript does not have data structures and everything is a string.
|
||||
* However, you can represent lists as strings; there is a standard syntax
|
||||
* to them.
|
||||
*
|
||||
* A list in Cubescript is simply a bunch of items separated by whitespace.
|
||||
* The items can take the form of any literal value Cubescript has. That means
|
||||
* they can be number literals, they can be words, and they can be strings.
|
||||
* Strings can be quoted either with double quotes, square brackets or even
|
||||
* parenthesis; basically any syntax representing a value.
|
||||
*
|
||||
* Comments (anything following two slashes, inclusive) are skipped. As far
|
||||
* as allowed whitespace consisting an item delimiter goes, this is either
|
||||
* regular spaces, horizontal tabs, or newlines.
|
||||
*
|
||||
* Keep in mind that it does not own the string it is parsing. Therefore,
|
||||
* you have to make sure to keep it alive for as long as the parser is.
|
||||
*
|
||||
* The input string by itself should not be quoted.
|
||||
*/
|
||||
struct LIBCUBESCRIPT_EXPORT list_parser {
|
||||
/** @brief Construct a list parser.
|
||||
*
|
||||
* Nothing is done until you actually start parsing.
|
||||
*
|
||||
* @param cs the thread
|
||||
* @param s the string representing the list
|
||||
*/
|
||||
list_parser(state &cs, std::string_view s = std::string_view{}):
|
||||
p_state{&cs}, p_input_beg{s.data()}, p_input_end{s.data() + s.size()}
|
||||
{}
|
||||
|
||||
/** @brief Reset the input string for the list */
|
||||
void set_input(std::string_view s) {
|
||||
p_input_beg = s.data();
|
||||
p_input_end = s.data() + s.size();
|
||||
}
|
||||
|
||||
/** @brief Get the current input string in the parser
|
||||
*
|
||||
* The already read items will not be contained in the result.
|
||||
*/
|
||||
std::string_view input() const {
|
||||
return std::string_view{
|
||||
p_input_beg, std::size_t(p_input_end - p_input_beg)
|
||||
};
|
||||
}
|
||||
|
||||
/** @brief Attempt to parse an item
|
||||
*
|
||||
* This will first skip whitespace and then attempt to read an element.
|
||||
*
|
||||
* @return `true` if an element was found, `false` otherwise
|
||||
*/
|
||||
bool parse();
|
||||
|
||||
/** @brief Get the number of items in the current list
|
||||
*
|
||||
* This will not contain items that are already parsed out, and will
|
||||
* parse the list itself, i.e. the final state will be an empty list.
|
||||
*/
|
||||
std::size_t count();
|
||||
|
||||
/** @brief Get the currently parsed item
|
||||
*
|
||||
* If the item was quoted with double quotes, the contents will be run
|
||||
* through cubescript::unescape_string() first.
|
||||
*
|
||||
* @see raw_item()
|
||||
* @see quoted_item()
|
||||
*/
|
||||
string_ref get_item() const;
|
||||
|
||||
/** @brief Get the currently parsed raw item
|
||||
*
|
||||
* Unlike get_item(), this will not unescape the string under any
|
||||
* circumstances and represents simply a slice of the original input.
|
||||
*
|
||||
* @see get_item()
|
||||
* @see quoted_item()
|
||||
*/
|
||||
std::string_view raw_item() const {
|
||||
return std::string_view{p_ibeg, std::size_t(p_iend - p_ibeg)};
|
||||
}
|
||||
|
||||
/** @brief Get the currently parsed raw item
|
||||
*
|
||||
* Like raw_item(), but contains the quotes too, if there were any.
|
||||
* Likewise, the resulting view is just a slice of the original input.
|
||||
*
|
||||
* @see get_item()
|
||||
* @see raw_item()
|
||||
*/
|
||||
std::string_view quoted_item() const {
|
||||
return std::string_view{p_qbeg, std::size_t(p_qend - p_qbeg)};
|
||||
}
|
||||
|
||||
/** @brief Skip whitespace in the input until a value is reached. */
|
||||
void skip_until_item();
|
||||
|
||||
private:
|
||||
state *p_state;
|
||||
char const *p_input_beg, *p_input_end;
|
||||
|
||||
char const *p_ibeg{}, *p_iend{};
|
||||
char const *p_qbeg{}, *p_qend{};
|
||||
};
|
||||
|
||||
/** @brief Parse a double quoted Cubescript string
|
||||
*
|
||||
* This parses double quoted strings according to the Cubescript syntax. The
|
||||
* string has to begin with a double quote; if it does not for any reason,
|
||||
* `str.data()` is returned.
|
||||
*
|
||||
* Escape sequences are not expanded and have the syntax `^X` where X is the
|
||||
* specific escape character (e.g. `^n` for newline). It is possible to make
|
||||
* the string multiline; the line needs to end with `\\`.
|
||||
*
|
||||
* Strings must be terminated again with double quotes.
|
||||
*
|
||||
* @param cs the thread
|
||||
* @param str the input string
|
||||
* @param[out] nlines the number of lines in the string
|
||||
*
|
||||
* @return a pointer to the character after the last double quotes
|
||||
* @throw cubescript::error if the string is started but not finished
|
||||
*
|
||||
* @see cubescript::parse_word()
|
||||
*/
|
||||
LIBCUBESCRIPT_EXPORT char const *parse_string(
|
||||
state &cs, std::string_view str, size_t &nlines
|
||||
);
|
||||
|
||||
/** @brief Parse a double quoted Cubescript string
|
||||
*
|
||||
* This overload has the same semantics but it does not return the number
|
||||
* of lines.
|
||||
*/
|
||||
inline char const *parse_string(
|
||||
state &cs, std::string_view str
|
||||
) {
|
||||
size_t nlines;
|
||||
return parse_string(cs, str, nlines);
|
||||
}
|
||||
|
||||
/** @brief Parse a Cubescript word.
|
||||
*
|
||||
* A Cubescript word is a sequence of any characters that are not whitespace
|
||||
* (spaces, newlines, tabs) or a comment (two consecutive slashes). It is
|
||||
* allowed to have parenthesis and square brackets as long a they are balanced.
|
||||
*
|
||||
* Examples of valid words: `foo`, `test123`, `125.4`, `[foo]`, `hi(bar)`.
|
||||
*
|
||||
* If a non-word character is encountered immediately, the resulting pointer
|
||||
* will be `str.data()`.
|
||||
*
|
||||
* Keep in mind that a valid word may not be a valid ident name (e.g. numbers
|
||||
* are valid words but not valid ident names).
|
||||
*
|
||||
* @return a pointer to the first character after the word
|
||||
* @throw cubescript::error if there is unbalanced `[` or `(`
|
||||
*/
|
||||
LIBCUBESCRIPT_EXPORT char const *parse_word(
|
||||
state &cs, std::string_view str
|
||||
);
|
||||
|
||||
/** @brief Concatenate a span of values
|
||||
*
|
||||
* The input values are concatenated by `sep`. Non-integer/float/string
|
||||
* input values are considered empty strings. Integers and floats are
|
||||
* converted to strings. The input list is not affected, however.
|
||||
*/
|
||||
LIBCUBESCRIPT_EXPORT string_ref concat_values(
|
||||
state &cs, span_type<any_value> vals,
|
||||
std::string_view sep = std::string_view{}
|
||||
);
|
||||
|
||||
/** @brief Escape a Cubescript string
|
||||
*
|
||||
* This reads and input string and writes it into `writer`, treating special
|
||||
* characters as escape sequences. Newlines are turned into `^n`, tabs are
|
||||
* turned into `^t`, vertical tabs into `^f`; double quotes are prefixed
|
||||
* with a caret, carets are duplicated. All other characters are passed
|
||||
* through.
|
||||
*
|
||||
* @return `writer` after writing into it
|
||||
*
|
||||
* @see cubescript::unescape_string()
|
||||
*/
|
||||
template<typename R>
|
||||
inline R escape_string(R writer, std::string_view str) {
|
||||
*writer++ = '"';
|
||||
for (auto c: str) {
|
||||
switch (c) {
|
||||
case '\n': *writer++ = '^'; *writer++ = 'n'; break;
|
||||
case '\t': *writer++ = '^'; *writer++ = 't'; break;
|
||||
case '\f': *writer++ = '^'; *writer++ = 'f'; break;
|
||||
case '"': *writer++ = '^'; *writer++ = '"'; break;
|
||||
case '^': *writer++ = '^'; *writer++ = '^'; break;
|
||||
default: *writer++ = c; break;
|
||||
}
|
||||
}
|
||||
*writer++ = '"';
|
||||
return writer;
|
||||
}
|
||||
|
||||
/** @brief Unscape a Cubescript string
|
||||
*
|
||||
* If a caret is encountered, it is skipped. If the following character is `n`,
|
||||
* it is turned into a newline; `t` is turned into a tab, `f` into a vertical
|
||||
* tab, double quote is written as is, as is a second caret. Any others are
|
||||
* written as they are.
|
||||
*
|
||||
* If a backslash is encountered and followed by a newline, the sequence is
|
||||
* skipped, otherwise the backslash is written out. Any other character is
|
||||
* written out as is.
|
||||
*
|
||||
* @return `writer` after writing into it
|
||||
*
|
||||
* @see cubescript::unescape_string()
|
||||
*/
|
||||
template<typename R>
|
||||
inline R unescape_string(R writer, std::string_view str) {
|
||||
for (auto it = str.begin(); it != str.end(); ++it) {
|
||||
if (*it == '^') {
|
||||
++it;
|
||||
if (it == str.end()) {
|
||||
break;
|
||||
}
|
||||
switch (*it) {
|
||||
case 'n': *writer++ = '\n'; break;
|
||||
case 't': *writer++ = '\t'; break;
|
||||
case 'f': *writer++ = '\f'; break;
|
||||
case '"': *writer++ = '"'; break;
|
||||
case '^': *writer++ = '^'; break;
|
||||
default: *writer++ = *it; break;
|
||||
}
|
||||
} else if (*it == '\\') {
|
||||
++it;
|
||||
if (it == str.end()) {
|
||||
break;
|
||||
}
|
||||
char c = *it;
|
||||
if ((c == '\r') || (c == '\n')) {
|
||||
if ((c == '\r') && ((it + 1) != str.end())) {
|
||||
if (it[1] == '\n') {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
*writer++ = '\\';
|
||||
} else {
|
||||
*writer++ = *it;
|
||||
}
|
||||
}
|
||||
return writer;
|
||||
}
|
||||
|
||||
} /* namespace cubescript */
|
||||
|
||||
#endif /* LIBCUBESCRIPT_CUBESCRIPT_UTIL_HH */
|
|
@ -1,551 +0,0 @@
|
|||
/** @file value.hh
|
||||
*
|
||||
* @brief Value API.
|
||||
*
|
||||
* This file contains value handles. These include the main value handle,
|
||||
* which represents any Cubescript value as a tagged union (and you use it
|
||||
* for handling of things such as command arguments and return values), as
|
||||
* well as string references and bytecode references.
|
||||
*
|
||||
* @copyright See COPYING.md in the project tree for further information.
|
||||
*/
|
||||
|
||||
#ifndef LIBCUBESCRIPT_CUBESCRIPT_VALUE_HH
|
||||
#define LIBCUBESCRIPT_CUBESCRIPT_VALUE_HH
|
||||
|
||||
#include <cstddef>
|
||||
#include <string_view>
|
||||
#include <new>
|
||||
|
||||
namespace cubescript {
|
||||
|
||||
struct ident;
|
||||
struct any_value;
|
||||
|
||||
/** @brief The loop state
|
||||
*
|
||||
* This is returned by state::call_loop().
|
||||
*/
|
||||
enum class loop_state {
|
||||
NORMAL = 0, /**< @brief The iteration ended normally. */
|
||||
BREAK, /**< @brief The iteration was broken out of. */
|
||||
CONTINUE /**< @brief The iteration ended early. */
|
||||
};
|
||||
|
||||
/** @brief Bytecode reference.
|
||||
*
|
||||
* This is an object representing a bytecode reference. Bytecode references
|
||||
* are executable in a Cubescript thread. The typical way to get a bytecode
|
||||
* reference is as an argument to a command. You can also compile values
|
||||
* explicitly.
|
||||
*
|
||||
* Bytecode references use refcounting to maintain their lifetime, so once
|
||||
* you hold a reference, it will not be freed at very least until you are
|
||||
* done with it and the object is destroyed.
|
||||
*
|
||||
* The API does not expose any specifics of the bytecode format either.
|
||||
* This is an implementation detail; bytecode is also not meant to be
|
||||
* serialized and stored on disk (it's not guaranteed to be portable).
|
||||
*/
|
||||
struct LIBCUBESCRIPT_EXPORT bcode_ref {
|
||||
/** @brief Initialize a null reference.
|
||||
*
|
||||
* Null references can still be executed, but will not do anything.
|
||||
*/
|
||||
bcode_ref():
|
||||
p_code(nullptr)
|
||||
{}
|
||||
|
||||
/** @brief Copy a reference.
|
||||
*
|
||||
* A reference copy will increase the internal reference count and will
|
||||
* point to the same bytecode.
|
||||
*/
|
||||
bcode_ref(bcode_ref const &v);
|
||||
|
||||
/** @brief Move a reference.
|
||||
*
|
||||
* A reference move will not change the internal reference count; the
|
||||
* other reference will become a null reference.
|
||||
*/
|
||||
bcode_ref(bcode_ref &&v):
|
||||
p_code(v.p_code)
|
||||
{
|
||||
v.p_code = nullptr;
|
||||
}
|
||||
|
||||
/** @brief Destroy a reference.
|
||||
*
|
||||
* This will decrease the reference count, and if it's zero, free the
|
||||
* bytecode.
|
||||
*/
|
||||
~bcode_ref();
|
||||
|
||||
/** @brief Copy-assign a reference.
|
||||
*
|
||||
* A reference copy will increase the internal reference count and will
|
||||
* point to the same bytecode.
|
||||
*/
|
||||
bcode_ref &operator=(bcode_ref const &v);
|
||||
|
||||
/** @brief Move-assign a reference.
|
||||
*
|
||||
* A reference move will not change the internal reference count; the
|
||||
* other reference will become a null reference.
|
||||
*/
|
||||
bcode_ref &operator=(bcode_ref &&v);
|
||||
|
||||
/** @brief Check if the bytecode is empty.
|
||||
*
|
||||
* Empty bytecode does not mean the same thing as null bytecode. While
|
||||
* null bytecode is considered empty, there can be non-null empty
|
||||
* bytecode; that is, a bytecode representing what an empty string
|
||||
* would compile into.
|
||||
*/
|
||||
bool empty() const;
|
||||
|
||||
/** @brief Check if the bytecode is null.
|
||||
*
|
||||
* This only checks if the bytecode is null. In general, you will want
|
||||
* to use bcode_ref::empty() instead.
|
||||
*/
|
||||
explicit operator bool() const;
|
||||
|
||||
/** @brief Execute the bytecode
|
||||
*
|
||||
* @return the return value
|
||||
*/
|
||||
any_value call(state &cs) const;
|
||||
|
||||
/** @brief Execute the bytecode as a loop body
|
||||
*
|
||||
* This exists to implement custom loop commands. A loop command will
|
||||
* consist of your desired loop and will take a body as an argument
|
||||
* (with bytecode type); this body will be run using this API. The
|
||||
* return value can be used to check if the loop was broken out of
|
||||
* or continued, and take steps accordingly.
|
||||
*
|
||||
* Some loops may evaluate to values, while others may not.
|
||||
*/
|
||||
loop_state call_loop(state &cs, any_value &ret) const;
|
||||
|
||||
/** @brief Execute the byctecode as a loop body
|
||||
*
|
||||
* This version ignores the return value of the body.
|
||||
*/
|
||||
loop_state call_loop(state &cs) const;
|
||||
|
||||
private:
|
||||
friend struct bcode_p;
|
||||
|
||||
bcode_ref(struct bcode *v);
|
||||
|
||||
struct bcode *p_code;
|
||||
};
|
||||
|
||||
/** @brief String reference.
|
||||
*
|
||||
* This Cubescript implementation uses interned strings everywhere in the
|
||||
* language and the API. This means every string in the language exists in
|
||||
* exactly one copy - if you try to create another string with the same
|
||||
* contents, it will point to the same data. This structure represents
|
||||
* a single reference to such string. By providing a reference counting
|
||||
* mechanism, it is possible to manage strings in a memory-safe manner.
|
||||
*
|
||||
* There is also no such thing as a null reference in this case. If you
|
||||
* have a string reference, it always points to a valid string no matter
|
||||
* what.
|
||||
*
|
||||
* It is not safe to have string references still around after the main
|
||||
* Cubescript thread is destroyed. Therefore, you should always make sure
|
||||
* that all string references you are holding are gone by the time the
|
||||
* main thread calls its destructor.
|
||||
*
|
||||
* For compatibility, all strings that are pointed to by string references
|
||||
* are null terminated and therefore can be used with C-style APIs.
|
||||
*/
|
||||
struct LIBCUBESCRIPT_EXPORT string_ref {
|
||||
friend struct any_value;
|
||||
friend struct string_pool;
|
||||
|
||||
/** @brief String references are not default-constructible. */
|
||||
string_ref() = delete;
|
||||
|
||||
/** @brief Create a new string reference.
|
||||
*
|
||||
* You need to provide a thread and a view of the string you wish
|
||||
* to create a reference for. The implementation will then ensure
|
||||
* that either a new string is allocated internally, or an existing
|
||||
* string's reference count is incremented.
|
||||
*/
|
||||
string_ref(state &cs, std::string_view str);
|
||||
|
||||
/** @brief Copy a string reference.
|
||||
*
|
||||
* This will increase the reference count for the pointed-to string.
|
||||
* There is explicitly no moving as this would create null references.
|
||||
*/
|
||||
string_ref(string_ref const &ref);
|
||||
|
||||
/** @brief Destroy a string reference.
|
||||
*
|
||||
* This will decrease the reference count. If it becomes zero, appropriate
|
||||
* actions will be taken (the exact behavior is implementation-defined).
|
||||
*
|
||||
* It is not safe to call this after destruction of the main thread.
|
||||
*/
|
||||
~string_ref();
|
||||
|
||||
/** @brief Copy-assign a string reference.
|
||||
*
|
||||
* This will increase the reference count for the pointed-to string.
|
||||
* There is explicitly no moving as this would create null references.
|
||||
*/
|
||||
string_ref &operator=(string_ref const &ref);
|
||||
|
||||
/** @brief Get a view to the pointed-to string.
|
||||
*
|
||||
* This creates a view of the string. The view is not its own reference,
|
||||
* therefore it is possible it will get invalidated after the destructor
|
||||
* of this reference is called.
|
||||
*/
|
||||
operator std::string_view() const;
|
||||
|
||||
/** @brief A convenience wrapper to get the size.
|
||||
*
|
||||
* Like `view().size()`.
|
||||
*/
|
||||
std::size_t size() const {
|
||||
return view().size();
|
||||
}
|
||||
|
||||
/** @brief A convenience wrapper to get the length.
|
||||
*
|
||||
* Like `view().length()`.
|
||||
*/
|
||||
std::size_t length() const {
|
||||
return view().length();
|
||||
}
|
||||
|
||||
/** @brief Get a C string pointer.
|
||||
*
|
||||
* The pointer may become dangling once the reference is destroyed.
|
||||
* The string itself is always null terminated.
|
||||
*/
|
||||
char const *data() const;
|
||||
|
||||
/** @brief A convenience wrapper to get the view.
|
||||
*
|
||||
* Since instantiating a view can be ugly, this is a quick method
|
||||
* to get a view of a random string reference.
|
||||
*/
|
||||
std::string_view view() const {
|
||||
return std::string_view{*this};
|
||||
}
|
||||
|
||||
/** @brief Check if the string is empty. */
|
||||
bool empty() const {
|
||||
return (size() == 0);
|
||||
}
|
||||
|
||||
/** @brief Check if the string equals another.
|
||||
*
|
||||
* This is effectively a `data() == s.data()` address comparison, and
|
||||
* therefore always has constant time complexity.
|
||||
*/
|
||||
bool operator==(string_ref const &s) const;
|
||||
|
||||
/** @brief Check if the string does not equal another.
|
||||
*
|
||||
* This is effectively a `data() != s.data()` address comparison, and
|
||||
* therefore always has constant time complexity.
|
||||
*/
|
||||
bool operator!=(string_ref const &s) const;
|
||||
|
||||
private:
|
||||
string_ref(char const *p);
|
||||
|
||||
char const *p_str;
|
||||
};
|
||||
|
||||
/** @brief The type of a value.
|
||||
*
|
||||
* The cubescript::any_value structure can hold multiple types. Not all of
|
||||
* them are representable in the language.
|
||||
*/
|
||||
enum class value_type {
|
||||
NONE = 0, /**< @brief No value. */
|
||||
INTEGER, /**< @brief Integer value (cubescript::integer_type). */
|
||||
FLOAT, /**< @brief Floating point value (cubescript::float_type). */
|
||||
STRING, /**< @brief String value (cubescript::string_ref). */
|
||||
CODE, /**< @brief Bytecode value (cubescript::bcode_ref). */
|
||||
IDENT /**< @brief Ident value (cubescript::ident). */
|
||||
};
|
||||
|
||||
/** @brief A tagged union representing a value.
|
||||
*
|
||||
* This structure is used to represent argument and result types of commands
|
||||
* as well as values of aliases. When assigned to an alias, the only value
|
||||
* must not contain bytecode or an ident reference, as those cannot be
|
||||
* represented as values in the language.
|
||||
*
|
||||
* Of course, to the language, every value looks like a string. It is however
|
||||
* still possible to differentiate them on C++ side for better performance,
|
||||
* more efficient storage and greater convenience.
|
||||
*
|
||||
* When the value contains a string or bytecode, it holds a reference like
|
||||
* cubescript::string_ref or cubescript::bcode_ref would.
|
||||
*
|
||||
* Upon setting different types, the old type will get cleared, which may
|
||||
* include a reference count decrease.
|
||||
*/
|
||||
struct LIBCUBESCRIPT_EXPORT any_value {
|
||||
/** @brief Construct a value_type::NONE value. */
|
||||
any_value();
|
||||
|
||||
/** @brief Construct a value_type::INTEGER value. */
|
||||
any_value(integer_type val);
|
||||
|
||||
/** @brief Construct a value_type::FLOAT value. */
|
||||
any_value(float_type val);
|
||||
|
||||
/** @brief Construct a value_type::STRING value. */
|
||||
any_value(std::string_view val, state &cs);
|
||||
|
||||
/** @brief Construct a value_type::STRING value. */
|
||||
any_value(string_ref const &val);
|
||||
|
||||
/** @brief Construct a value_type::CODE value. */
|
||||
any_value(bcode_ref const &val);
|
||||
|
||||
/** @brief Construct a value_type::IDENT value. */
|
||||
any_value(ident &val);
|
||||
|
||||
/** @brief Destroy the value.
|
||||
*
|
||||
* If holding a reference counted value, the refcount will be decreased
|
||||
* and the value will be possibly freed.
|
||||
*/
|
||||
~any_value();
|
||||
|
||||
/** @brief Copy the value. */
|
||||
any_value(any_value const &);
|
||||
|
||||
/** @brief Move the value.
|
||||
*
|
||||
* The other value becomes a value_type::NULL value.
|
||||
*/
|
||||
any_value(any_value &&v);
|
||||
|
||||
/** @brief Copy-assign the value. */
|
||||
any_value &operator=(any_value const &);
|
||||
|
||||
/** @brief Move-assign the value.
|
||||
*
|
||||
* The other value becomes a value_type::NULL value.
|
||||
*/
|
||||
any_value &operator=(any_value &&);
|
||||
|
||||
/** @brief Assign an integer to the value. */
|
||||
any_value &operator=(integer_type val);
|
||||
|
||||
/** @brief Assign a float to the value. */
|
||||
any_value &operator=(float_type val);
|
||||
|
||||
/** @brief Assign a string reference to the value. */
|
||||
any_value &operator=(string_ref const &val);
|
||||
|
||||
/** @brief Assign a bytecode reference to the value. */
|
||||
any_value &operator=(bcode_ref const &val);
|
||||
|
||||
/** @brief Assign an ident to the value. */
|
||||
any_value &operator=(ident &val);
|
||||
|
||||
/** @brief Get the type of the value. */
|
||||
value_type type() const;
|
||||
|
||||
/** @brief Set the value to an integer.
|
||||
*
|
||||
* The type becomes value_type::INTEGER.
|
||||
*/
|
||||
void set_integer(integer_type val);
|
||||
|
||||
/** @brief Set the value to a float.
|
||||
*
|
||||
* The type becomes value_type::FLOAT.
|
||||
*/
|
||||
void set_float(float_type val);
|
||||
|
||||
/** @brief Set the value to a string.
|
||||
*
|
||||
* The type becomes value_type::STRING. The string will be allocated
|
||||
* (if non-existent) like a cubescript::string_ref, and its reference
|
||||
* count will be increased. This is why it is necessary to provide a state.
|
||||
*/
|
||||
void set_string(std::string_view val, state &cs);
|
||||
|
||||
/** @brief Set the value to a string reference.
|
||||
*
|
||||
* The type becomes value_type::STRING. The value will get copied
|
||||
* (therefore, the reference count will be increased).
|
||||
*/
|
||||
void set_string(string_ref const &val);
|
||||
|
||||
/** @brief Set the value to a value_type::NONE. */
|
||||
void set_none();
|
||||
|
||||
/** @brief Set the value to a bytecode reference.
|
||||
*
|
||||
* The type becomes value_type::CODE. The value will get copied
|
||||
* (therefore, the reference count will be increased).
|
||||
*/
|
||||
void set_code(bcode_ref const &val);
|
||||
|
||||
/** @brief Set the value to an indent.
|
||||
*
|
||||
* The type becomes value_type::IDENT. No reference counting is
|
||||
* performed, so after main thread destruction this may become
|
||||
* dangling (and unsafe to use).
|
||||
*/
|
||||
void set_ident(ident &val);
|
||||
|
||||
/** @brief Get the value as a string reference.
|
||||
*
|
||||
* If the contained value is not a string, an appropriate conversion
|
||||
* will occur. This will not affect the contained type, all conversions
|
||||
* are only intermediate.
|
||||
*
|
||||
* If the type is not convertible, an empty string is used.
|
||||
*/
|
||||
string_ref get_string(state &cs) const;
|
||||
|
||||
/** @brief Get the value as an integer.
|
||||
*
|
||||
* If the contained value is not an integer, an appropriate conversion
|
||||
* will occur. This will not affect the contained type, all conversions
|
||||
* are only intermediate.
|
||||
*
|
||||
* Floating point values are rounded down and converted to integers.
|
||||
*
|
||||
* If the type is not convertible, 0 is returned.
|
||||
*/
|
||||
integer_type get_integer() const;
|
||||
|
||||
/** @brief Get the value as a float.
|
||||
*
|
||||
* If the contained value is not a float, an appropriate conversion
|
||||
* will occur. This will not affect the contained type, all conversions
|
||||
* are only intermediate.
|
||||
*
|
||||
* If the type is not convertible, 0 is returned.
|
||||
*/
|
||||
float_type get_float() const;
|
||||
|
||||
/** @brief Get the value as a bytecode.
|
||||
*
|
||||
* If the contained value is not bytecode, null bytecode is returned.
|
||||
*/
|
||||
bcode_ref get_code() const;
|
||||
|
||||
/** @brief Get the value as an ident.
|
||||
*
|
||||
* If the contained value is not an ident, a dummy is returned.
|
||||
*/
|
||||
ident &get_ident(state &cs) const;
|
||||
|
||||
/** @brief Get the value as representable inside the language.
|
||||
*
|
||||
* The returned value is the same value except if the original contents
|
||||
* were bytecode or an ident - in those cases the returned type is
|
||||
* value_type::NONE.
|
||||
*/
|
||||
any_value get_plain() const;
|
||||
|
||||
/** @brief Get the value converted to a boolean.
|
||||
*
|
||||
* For integer and float values, anything other than zero will become
|
||||
* `true`, while zero becomes `false`. Empty strings are `false`; other
|
||||
* strings first attempt conversion to an integer - if it's convertible
|
||||
* (strong conversion rules apply), it's treated like an integer. If it
|
||||
* is not, it's converted to a float (strong rules apply) and if it is
|
||||
* convertible, it's treated like a float. Any non-integer non-float
|
||||
* string is considered `true`.
|
||||
*
|
||||
* For any other type, `false` is returned.
|
||||
*/
|
||||
bool get_bool() const;
|
||||
|
||||
/** @brief Force the type to value_type::NONE.
|
||||
*
|
||||
* Like set_none().
|
||||
*/
|
||||
void force_none();
|
||||
|
||||
/** @brief Force the type to be representable in the language.
|
||||
*
|
||||
* Like `*this = get_plain()`.
|
||||
*/
|
||||
void force_plain();
|
||||
|
||||
/** @brief Force the type to value_type::FLOAT.
|
||||
*
|
||||
* Like `set_float(get_float())`.
|
||||
*
|
||||
* @return The value.
|
||||
*/
|
||||
float_type force_float();
|
||||
|
||||
/** @brief Force the type to value_type::INTEGER.
|
||||
*
|
||||
* Like `set_integer(get_integer())`.
|
||||
*
|
||||
* @return The value.
|
||||
*/
|
||||
integer_type force_integer();
|
||||
|
||||
/** @brief Force the type to value_type::STRING.
|
||||
*
|
||||
* Like `set_string(get_string(cs))`.
|
||||
*
|
||||
* @return A view to the string.
|
||||
*/
|
||||
std::string_view force_string(state &cs);
|
||||
|
||||
/** @brief Force the type to value_type::CODE.
|
||||
*
|
||||
* If the contained value is already bytecode, nothing happens. Otherwise
|
||||
* the value is converted to a string (like get_string()) and this string
|
||||
* is compiled as bytecode (as if using state::compile())
|
||||
*
|
||||
* @return A bytecode reference.
|
||||
*/
|
||||
bcode_ref force_code(
|
||||
state &cs, std::string_view source = std::string_view{}
|
||||
);
|
||||
|
||||
/** @brief Force the type to value_type::IDENT.
|
||||
*
|
||||
* If the contained value is already an ident, nothing happens. Otherwise
|
||||
* the value is converted to a string (like get_string()) and this string
|
||||
* is used as a name of the ident. If an ident of such name exists, it
|
||||
* will be stored, otherwise a new alias is pre-created (it will not be
|
||||
* visible to the language until a value is assigned to it though).
|
||||
*
|
||||
* @return An ident reference.
|
||||
*/
|
||||
ident &force_ident(state &cs);
|
||||
|
||||
private:
|
||||
union {
|
||||
integer_type i;
|
||||
float_type f;
|
||||
char const *s;
|
||||
struct bcode *b;
|
||||
ident *v;
|
||||
} p_stor;
|
||||
value_type p_type;
|
||||
};
|
||||
|
||||
} /* namespace cubescript */
|
||||
|
||||
#endif /* LIBCUBESCRIPT_CUBESCRIPT_VALUE_HH */
|
|
@ -1,145 +1,51 @@
|
|||
/** @file cubescript_conf.hh
|
||||
*
|
||||
* @brief Library configuration.
|
||||
*
|
||||
* While you can technically modify this directly, it is better if you use
|
||||
* a custom file `cubescript_conf_user.hh` in the same location. Most of the
|
||||
* time you will not want to override anything, but should you need to change
|
||||
* the integer, float or span types for a specific purpose, this allows you to.
|
||||
*
|
||||
* @copyright See COPYING.md in the project tree for further information.
|
||||
*/
|
||||
|
||||
#ifndef LIBCUBESCRIPT_CUBESCRIPT_CONF_HH
|
||||
#define LIBCUBESCRIPT_CUBESCRIPT_CONF_HH
|
||||
|
||||
#include <type_traits>
|
||||
#include <limits.h>
|
||||
#include <functional>
|
||||
#include <ostd/range.hh>
|
||||
|
||||
#if __has_include("cubescript_conf_user.hh")
|
||||
# include "cubescript_conf_user.hh"
|
||||
#endif
|
||||
/* do not modify */
|
||||
namespace cscript {
|
||||
struct cs_state;
|
||||
struct cs_ident;
|
||||
struct cs_value;
|
||||
|
||||
#if !defined(LIBCUBESCRIPT_CONF_USER_SPAN)
|
||||
# include <span>
|
||||
#endif
|
||||
using cs_value_r = ostd::iterator_range<cs_value *>;
|
||||
using cs_ident_r = ostd::iterator_range<cs_ident **>;
|
||||
using cs_const_ident_r = ostd::iterator_range<cs_ident const **>;
|
||||
}
|
||||
|
||||
namespace cubescript {
|
||||
#if !defined(LIBCUBESCRIPT_CONF_USER_INTEGER)
|
||||
/** @brief The integer type used.
|
||||
/* configurable section */
|
||||
namespace cscript {
|
||||
using cs_int = int;
|
||||
using cs_float = float;
|
||||
|
||||
/* probably don't want to change these, but if you use a custom allocation
|
||||
* function for your state, keep in mind potential heap allocations in
|
||||
* these are not handled by it (as std::function has no allocator support)
|
||||
*
|
||||
* While Cubescript is a stringly typed language, it uses integers and
|
||||
* floats internally in a transparent manner where possible, and allows
|
||||
* you to retrieve and pass integers and floats in commands and so on.
|
||||
*
|
||||
* This is the integer type used. By default, it's `int`, which is a
|
||||
* 32-bit signed integer on most platforms. Keep in mind that is is
|
||||
* necessary for this type to be a signed integer type.
|
||||
*
|
||||
* Define `LIBCUBESCRIPT_CONF_USER_INTEGER` in your custom conf file
|
||||
* to disable the builtin.
|
||||
*
|
||||
* @see float_type
|
||||
* @see INTEGER_FORMAT
|
||||
* normally std::function is optimized not to do allocations for small
|
||||
* objects, so as long as you don't pass a lambda that captures by copy
|
||||
* or move or something similar, you should be fine - but if you really
|
||||
* need to make sure, override this with your own type
|
||||
*/
|
||||
using integer_type = int;
|
||||
#endif
|
||||
using cs_var_cb = std::function<void(cs_state &, cs_ident &)>;
|
||||
using cs_command_cb = std::function<void(cs_state &, cs_value_r, cs_value &)>;
|
||||
using cs_hook_cb = std::function<void(cs_state &)>;
|
||||
using cs_alloc_cb = void *(*)(void *, void *, size_t, size_t);
|
||||
|
||||
#if !defined(LIBCUBESCRIPT_CONF_USER_FLOAT)
|
||||
/** @brief The floating point type used.
|
||||
*
|
||||
* By default, this is `float`, which is on most platforms an IEEE754
|
||||
* binary32 data type.
|
||||
*
|
||||
* Define `LIBCUBESCRIPT_CONF_USER_FLOAT` in your custom conf file
|
||||
* to disable the builtin.
|
||||
*
|
||||
* @see integer_type
|
||||
* @see FLOAT_FORMAT
|
||||
* @see ROUND_FLOAT_FORMAT
|
||||
*/
|
||||
using float_type = float;
|
||||
#endif
|
||||
constexpr auto const IntFormat = "%d";
|
||||
constexpr auto const FloatFormat = "%.7g";
|
||||
constexpr auto const RoundFloatFormat = "%.1f";
|
||||
|
||||
#if !defined(LIBCUBESCRIPT_CONF_USER_SPAN)
|
||||
/** @brief The span type used.
|
||||
*
|
||||
* By default, this is `std::span`. You will almost never want to override
|
||||
* this, but an alternative implementation can be supplied if your standard
|
||||
* library does not support it.
|
||||
*
|
||||
* Define `LIBCUBESCRIPT_CONF_USER_SPAN` in your custom conf file to
|
||||
* disable the builtin.
|
||||
*/
|
||||
template<typename T>
|
||||
using span_type = std::span<T>;
|
||||
#endif
|
||||
constexpr auto const IvarFormat = "%s = %d";
|
||||
constexpr auto const IvarHexFormat = "%s = 0x%X";
|
||||
constexpr auto const IvarHexColorFormat = "%s = 0x%.6X (%d, %d, %d)";
|
||||
constexpr auto const FvarFormat = "%s = %.7g";
|
||||
constexpr auto const FvarRoundFormat = "%s = %.1f";
|
||||
constexpr auto const SvarFormat = "%s = \"%s\"";
|
||||
constexpr auto const SvarQuotedFormat = "%s = [%s]";
|
||||
} /* namespace cscript */
|
||||
|
||||
#if !defined(LIBCUBESCRIPT_CONF_USER_INTEGER)
|
||||
/** @brief The integer format used.
|
||||
*
|
||||
* This is a formatting specifier as in `printf`, corresponding to the
|
||||
* `integer_type` used. It is used to handle conversions from the type
|
||||
* to strings, as well as in the default integer variable handler when
|
||||
* printing.
|
||||
*
|
||||
* There are no special restrictions imposed on the floating point type
|
||||
* other than that it actually has to be floating point.
|
||||
*
|
||||
* Define `LIBCUBESCRIPT_CONF_USER_INTEGER` in your custom conf file
|
||||
* to disable the builtin.
|
||||
*
|
||||
* @see integer_type
|
||||
* @see FLOAT_FORMAT
|
||||
*/
|
||||
constexpr auto const INTEGER_FORMAT = "%d";
|
||||
#endif
|
||||
|
||||
#if !defined(LIBCUBESCRIPT_CONF_USER_FLOAT)
|
||||
/** @brief The float format used.
|
||||
*
|
||||
* This is a formatting specifier as in `printf`, corresponding to the
|
||||
* `float_type` used. It is used to handle conversions from the type to
|
||||
* strings, as well as in the default float variable handler when printing.
|
||||
*
|
||||
* When the floating point value is equivalent to its integer value (i.e.
|
||||
* it has no decimal point), ROUND_FLOAT_FORMAT is used.
|
||||
*
|
||||
* Define `LIBCUBESCRIPT_CONF_USER_FLOAT` in your custom conf file
|
||||
* to disable the builtin.
|
||||
*
|
||||
* @see float_type
|
||||
* @see ROUND_FLOAT_FORMAT
|
||||
* @see INTEGER_FORMAT
|
||||
*/
|
||||
constexpr auto const FLOAT_FORMAT = "%.7g";
|
||||
|
||||
/** @brief The round float format used.
|
||||
*
|
||||
* This is a formatting specifier as in `printf`, corresponding to the
|
||||
* `float_type` used. It's like `FLOAT_FORMAT` but used when the value
|
||||
* has no decimal point.
|
||||
*
|
||||
* @see float_type
|
||||
* @see FLOAT_FORMAT
|
||||
*/
|
||||
constexpr auto const ROUND_FLOAT_FORMAT = "%.1f";
|
||||
#endif
|
||||
} /* namespace cubescript */
|
||||
|
||||
/* conf verification */
|
||||
|
||||
namespace cubescript {
|
||||
|
||||
static_assert(
|
||||
std::is_integral_v<integer_type>, "integer_type must be integral"
|
||||
);
|
||||
static_assert(
|
||||
std::is_signed_v<integer_type>, "integer_type must be signed"
|
||||
);
|
||||
static_assert(
|
||||
std::is_floating_point_v<float_type>, "float_type must be floating point"
|
||||
);
|
||||
|
||||
} /* namespace cubescript */
|
||||
|
||||
#endif /* LIBCUBESCRIPT_CUBESCRIPT_CONF_HH */
|
||||
#endif /* LIBCUBESCRIPT_CUBESCRIPT_CONF_HH */
|
|
@ -1,13 +0,0 @@
|
|||
libcubescript_headers = [
|
||||
'cubescript/cubescript.hh',
|
||||
'cubescript/cubescript_conf.hh',
|
||||
'cubescript/cubescript/callable.hh',
|
||||
'cubescript/cubescript/error.hh',
|
||||
'cubescript/cubescript/ident.hh',
|
||||
'cubescript/cubescript/platform.hh',
|
||||
'cubescript/cubescript/state.hh',
|
||||
'cubescript/cubescript/util.hh',
|
||||
'cubescript/cubescript/value.hh',
|
||||
]
|
||||
|
||||
install_headers(libcubescript_headers, install_dir: dir_package_include)
|
58
meson.build
58
meson.build
|
@ -1,10 +1,7 @@
|
|||
project('libcubescript', ['cpp'],
|
||||
version: '1.0.0',
|
||||
default_options: [
|
||||
'buildtype=debugoptimized', 'warning_level=3', 'cpp_rtti=false',
|
||||
'cpp_std=none'
|
||||
],
|
||||
meson_version: '>=0.50'
|
||||
version: '0.1.0',
|
||||
default_options: ['buildtype=plain', 'cpp_std=c++17'],
|
||||
meson_version: '>=0.46'
|
||||
)
|
||||
|
||||
dir_prefix = get_option('prefix')
|
||||
|
@ -16,57 +13,20 @@ dir_package_include = join_paths(dir_include, 'cubescript')
|
|||
|
||||
libcubescript_includes = [include_directories('include')]
|
||||
|
||||
cxx = meson.get_compiler('cpp')
|
||||
|
||||
extra_cxxflags = []
|
||||
|
||||
if get_option('buildtype') != 'plain'
|
||||
if cxx.has_argument('-Wshadow')
|
||||
extra_cxxflags += '-Wshadow'
|
||||
endif
|
||||
if cxx.has_argument('-Wold-style-cast')
|
||||
extra_cxxflags += '-Wold-style-cast'
|
||||
endif
|
||||
tgt_compiler_id = meson.get_compiler('cpp').get_id()
|
||||
if tgt_compiler_id == 'gcc' or tgt_compiler_id == 'clang'
|
||||
extra_cxxflags = ['-Wextra', '-Wshadow', '-Wold-style-cast']
|
||||
else
|
||||
extra_cxxflags = []
|
||||
endif
|
||||
|
||||
# Meson does not support C++20 std in a portable way in this version
|
||||
# unless specified explicitly, have our own logic which will guess it
|
||||
if get_option('cpp_std') == 'none'
|
||||
if cxx.has_argument('-std=c++20')
|
||||
# modern gcc/clang
|
||||
extra_cxxflags += '-std=c++20'
|
||||
elif cxx.has_argument('-std=c++2a')
|
||||
# older gcc/clang
|
||||
extra_cxxflags += '-std=c++2a'
|
||||
elif cxx.has_argument('/std:c++20')
|
||||
# future msvc++? not supported anywhere yet
|
||||
extra_cxxflags += '/std:c++20'
|
||||
elif cxx.has_argument('/std:c++latest')
|
||||
# msvc++ 2019
|
||||
extra_cxxflags += '/std:c++latest'
|
||||
endif
|
||||
endif
|
||||
|
||||
build_root = meson.current_build_dir()
|
||||
|
||||
subdir('include')
|
||||
subdir('src')
|
||||
subdir('tools')
|
||||
|
||||
if meson.is_cross_build() and get_option('tests')
|
||||
build_tests = get_option('tests_cross')
|
||||
else
|
||||
build_tests = get_option('tests')
|
||||
endif
|
||||
|
||||
if build_tests
|
||||
subdir('tests')
|
||||
endif
|
||||
|
||||
pkg = import('pkgconfig')
|
||||
|
||||
pkg.generate(
|
||||
libraries: libcubescript_target,
|
||||
libraries: libcubescript_lib,
|
||||
version: meson.project_version(),
|
||||
name: 'libcubescript',
|
||||
filebase: 'libcubescript',
|
||||
|
|
|
@ -1,23 +1,11 @@
|
|||
option('repl',
|
||||
type: 'feature',
|
||||
value: 'auto',
|
||||
description: 'Enable the REPL (command line tool)'
|
||||
option('readline',
|
||||
type: 'boolean',
|
||||
value: false,
|
||||
description: 'Use GNU readline for the REPL'
|
||||
)
|
||||
|
||||
option('linenoise',
|
||||
type: 'feature',
|
||||
value: 'auto',
|
||||
type: 'boolean',
|
||||
value: true,
|
||||
description: 'Use linenoise for the REPL'
|
||||
)
|
||||
|
||||
option('tests',
|
||||
type: 'boolean',
|
||||
value: 'true',
|
||||
description: 'Whether to build tests'
|
||||
)
|
||||
|
||||
option('tests_cross',
|
||||
type: 'boolean',
|
||||
value: 'false',
|
||||
description: 'Whether to build tests when cross-compiling'
|
||||
)
|
||||
|
|
181
src/cs_bcode.cc
181
src/cs_bcode.cc
|
@ -1,181 +0,0 @@
|
|||
#include "cs_bcode.hh"
|
||||
#include "cs_state.hh"
|
||||
#include "cs_vm.hh"
|
||||
|
||||
namespace cubescript {
|
||||
|
||||
/* public API impls */
|
||||
|
||||
LIBCUBESCRIPT_EXPORT bcode_ref::bcode_ref(bcode *v): p_code(v) {
|
||||
bcode_addref(v->raw());
|
||||
}
|
||||
LIBCUBESCRIPT_EXPORT bcode_ref::bcode_ref(bcode_ref const &v):
|
||||
p_code(v.p_code)
|
||||
{
|
||||
bcode_addref(p_code->raw());
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT bcode_ref::~bcode_ref() {
|
||||
bcode_unref(p_code->raw());
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT bcode_ref &bcode_ref::operator=(
|
||||
bcode_ref const &v
|
||||
) {
|
||||
bcode_unref(p_code->raw());
|
||||
p_code = v.p_code;
|
||||
bcode_addref(p_code->raw());
|
||||
return *this;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT bcode_ref &bcode_ref::operator=(bcode_ref &&v) {
|
||||
bcode_unref(p_code->raw());
|
||||
p_code = v.p_code;
|
||||
v.p_code = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT bool bcode_ref::empty() const {
|
||||
if (!p_code) {
|
||||
return true;
|
||||
}
|
||||
return (*p_code->raw() & BC_INST_OP_MASK) == BC_INST_EXIT;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT bcode_ref::operator bool() const {
|
||||
return p_code != nullptr;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT any_value bcode_ref::call(state &cs) const {
|
||||
any_value ret{};
|
||||
vm_exec(state_p{cs}.ts(), p_code->raw(), ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT loop_state bcode_ref::call_loop(
|
||||
state &cs, any_value &ret
|
||||
) const {
|
||||
auto &ts = state_p{cs}.ts();
|
||||
++ts.loop_level;
|
||||
try {
|
||||
ret = call(cs);
|
||||
} catch (break_exception) {
|
||||
--ts.loop_level;
|
||||
return loop_state::BREAK;
|
||||
} catch (continue_exception) {
|
||||
--ts.loop_level;
|
||||
return loop_state::CONTINUE;
|
||||
} catch (...) {
|
||||
--ts.loop_level;
|
||||
throw;
|
||||
}
|
||||
return loop_state::NORMAL;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT loop_state bcode_ref::call_loop(state &cs) const {
|
||||
any_value ret{};
|
||||
return call_loop(cs, ret);
|
||||
}
|
||||
|
||||
/* private funcs */
|
||||
|
||||
struct bcode_hdr {
|
||||
internal_state *cs; /* needed to construct the allocator */
|
||||
std::size_t asize; /* alloc size of the bytecode block */
|
||||
bcode bc; /* BC_INST_START + refcount */
|
||||
};
|
||||
|
||||
/* returned address is the 'init' member of the header */
|
||||
std::uint32_t *bcode_alloc(internal_state *cs, std::size_t sz) {
|
||||
auto a = std_allocator<std::uint32_t>{cs};
|
||||
std::size_t hdrs = sizeof(bcode_hdr) / sizeof(std::uint32_t);
|
||||
auto p = a.allocate(sz + hdrs - 1);
|
||||
bcode_hdr *hdr;
|
||||
std::memcpy(&hdr, &p, sizeof(hdr));
|
||||
hdr->cs = cs;
|
||||
hdr->asize = sz + hdrs - 1;
|
||||
return p + hdrs - 1;
|
||||
}
|
||||
|
||||
/* bc's address must be the 'init' member of the header */
|
||||
static inline void bcode_free(std::uint32_t *bc) {
|
||||
auto *rp = bc + 1 - (sizeof(bcode_hdr) / sizeof(std::uint32_t));
|
||||
bcode_hdr *hdr;
|
||||
std::memcpy(&hdr, &rp, sizeof(hdr));
|
||||
std_allocator<std::uint32_t>{hdr->cs}.deallocate(rp, hdr->asize);
|
||||
}
|
||||
|
||||
static inline void bcode_incr(std::uint32_t *bc) {
|
||||
*bc += 0x100;
|
||||
}
|
||||
|
||||
static inline void bcode_decr(std::uint32_t *bc) {
|
||||
*bc -= 0x100;
|
||||
if (std::int32_t(*bc) < 0x100) {
|
||||
bcode_free(bc);
|
||||
}
|
||||
}
|
||||
|
||||
void bcode_addref(std::uint32_t *code) {
|
||||
if (!code) {
|
||||
return;
|
||||
}
|
||||
if ((*code & BC_INST_OP_MASK) == BC_INST_START) {
|
||||
bcode_incr(code);
|
||||
return;
|
||||
}
|
||||
switch (code[-1]&BC_INST_OP_MASK) {
|
||||
case BC_INST_START:
|
||||
bcode_incr(&code[-1]);
|
||||
break;
|
||||
case BC_INST_OFFSET:
|
||||
code -= std::ptrdiff_t(code[-1] >> 8);
|
||||
bcode_incr(code);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void bcode_unref(std::uint32_t *code) {
|
||||
if (!code) {
|
||||
return;
|
||||
}
|
||||
if ((*code & BC_INST_OP_MASK) == BC_INST_START) {
|
||||
bcode_decr(code);
|
||||
return;
|
||||
}
|
||||
switch (code[-1]&BC_INST_OP_MASK) {
|
||||
case BC_INST_START:
|
||||
bcode_decr(&code[-1]);
|
||||
break;
|
||||
case BC_INST_OFFSET:
|
||||
code -= std::ptrdiff_t(code[-1] >> 8);
|
||||
bcode_decr(code);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* empty fallbacks */
|
||||
|
||||
static std::uint32_t emptyrets[VAL_ANY] = {
|
||||
BC_RET_NULL, BC_RET_INT, BC_RET_FLOAT, BC_RET_STRING
|
||||
};
|
||||
|
||||
empty_block *bcode_init_empty(internal_state *cs) {
|
||||
auto a = std_allocator<empty_block>{cs};
|
||||
auto *p = a.allocate(VAL_ANY);
|
||||
for (std::size_t i = 0; i < VAL_ANY; ++i) {
|
||||
p[i].init.init = BC_INST_START + 0x100;
|
||||
p[i].code = BC_INST_EXIT | emptyrets[i];
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
void bcode_free_empty(internal_state *cs, empty_block *empty) {
|
||||
std_allocator<empty_block>{cs}.deallocate(empty, VAL_ANY);
|
||||
}
|
||||
|
||||
bcode *bcode_get_empty(empty_block *empty, std::size_t val) {
|
||||
return &empty[val >> BC_INST_RET].init + 1;
|
||||
}
|
||||
|
||||
} /* namespace cubescript */
|
219
src/cs_bcode.hh
219
src/cs_bcode.hh
|
@ -1,219 +0,0 @@
|
|||
#ifndef LIBCUBESCRIPT_BCODE_HH
|
||||
#define LIBCUBESCRIPT_BCODE_HH
|
||||
|
||||
#include <cubescript/cubescript.hh>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
namespace cubescript {
|
||||
|
||||
struct internal_state;
|
||||
|
||||
struct bcode {
|
||||
std::uint32_t init;
|
||||
|
||||
std::uint32_t *raw() {
|
||||
return &init;
|
||||
}
|
||||
|
||||
std::uint32_t const *raw() const {
|
||||
return &init;
|
||||
}
|
||||
};
|
||||
|
||||
enum {
|
||||
VAL_NULL = 0, VAL_INT, VAL_FLOAT, VAL_STRING,
|
||||
VAL_ANY, VAL_CODE, VAL_IDENT, VAL_WORD,
|
||||
VAL_POP, VAL_COND
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
constexpr std::size_t bc_store_size = (
|
||||
sizeof(T) - 1
|
||||
) / sizeof(std::uint32_t) + 1;
|
||||
|
||||
/* instructions consist of:
|
||||
*
|
||||
* [D 24][M 2][O 6] == I
|
||||
*
|
||||
* I: instruction
|
||||
* O: opcode
|
||||
* M: type mask
|
||||
* D: data
|
||||
*
|
||||
* also:
|
||||
*
|
||||
* R: result slot
|
||||
*
|
||||
* "force to M" means changing the type of the value as described by the
|
||||
* type mask; this is generally string/integer/float, null in general
|
||||
* preserves the type, except where mentioned
|
||||
*/
|
||||
enum {
|
||||
/* noop */
|
||||
BC_INST_START = 0,
|
||||
BC_INST_OFFSET,
|
||||
/* set R to null/true/false according to M */
|
||||
BC_INST_NULL, BC_INST_TRUE, BC_INST_FALSE,
|
||||
/* pop a value off the stack and set R to negated value according to M */
|
||||
BC_INST_NOT,
|
||||
/* pop a value off the stack */
|
||||
BC_INST_POP,
|
||||
/* recursively invoke VM from next instruction, push result on the stack */
|
||||
BC_INST_ENTER,
|
||||
/* recursively invoke VM from next instruction, result in R */
|
||||
BC_INST_ENTER_RESULT,
|
||||
/* exit VM, force R according to M */
|
||||
BC_INST_EXIT,
|
||||
/* pop a value off the stack and set R according to M */
|
||||
BC_INST_RESULT,
|
||||
/* push R on the stack according to M */
|
||||
BC_INST_RESULT_ARG,
|
||||
/* force top of the stack according to M */
|
||||
BC_INST_FORCE,
|
||||
/* duplicate top of the stack according to M */
|
||||
BC_INST_DUP,
|
||||
/* push value after I on the stack according to M (length D if string) */
|
||||
BC_INST_VAL,
|
||||
/* push value inside D on the stack according to M
|
||||
*
|
||||
* strings are at most 3 bytes long, integers and floats must be
|
||||
* integral values between -0x800000 and 0x7FFFFF inclusive
|
||||
*/
|
||||
BC_INST_VAL_INT,
|
||||
/* pop D aliases off the stack, push their values and recurse the VM
|
||||
* pop their values afterwards (i.e. they are local to the execution)
|
||||
*/
|
||||
BC_INST_LOCAL,
|
||||
/* pop a value off the stack, execute its bytecode,
|
||||
* result in R according to M
|
||||
*/
|
||||
BC_INST_DO,
|
||||
/* like above, except argument aliases are restored to the previous
|
||||
* callstack level before calling (and restored back afterwards)
|
||||
*/
|
||||
BC_INST_DO_ARGS,
|
||||
/* jump forward by D instructions */
|
||||
BC_INST_JUMP,
|
||||
/* conditional jump: pop a value off the stack, jump only if considered
|
||||
* true or false (see BC_INST_FLAG_TRUE/FALSE)
|
||||
*/
|
||||
BC_INST_JUMP_B,
|
||||
/* conditional jump: pop a value off the stack, if it's bytecode,
|
||||
* eval it (saving the value into R), if it's not, save the value
|
||||
* into R, then jump only if the value is considered true or false
|
||||
* (see BC_INST_FLAG_TRUE/FALSE)
|
||||
*/
|
||||
BC_INST_JUMP_RESULT,
|
||||
/* break or continue a loop; if no loop is currently running, raise
|
||||
* an error, otherwise break (if BC_INST_FLAG_FALSE) or continue
|
||||
* (if BC_INST_FLAG_TRUE)
|
||||
*/
|
||||
BC_INST_BREAK,
|
||||
/* bytecode of length D follows, push on the stack as bytecode */
|
||||
BC_INST_BLOCK,
|
||||
/* push bytecode of (BC_INST_EXIT | M) on the stack */
|
||||
BC_INST_EMPTY,
|
||||
/* compile the value on top of the stack as if it was a string (null for
|
||||
* non-string/integer/float values) */
|
||||
BC_INST_COMPILE,
|
||||
/* compile the value on top of the stack if string; if string is empty,
|
||||
* force to null, if not string, keep as is
|
||||
*/
|
||||
BC_INST_COND,
|
||||
/* push ident with index D on the stack; if arg, push val and mark used */
|
||||
BC_INST_IDENT,
|
||||
/* make value on top of stack an ident; if value is string, that is
|
||||
* the ident name, otherwise dummy is used; ident is created if non
|
||||
* existent, and if arg, push val and mark used
|
||||
*/
|
||||
BC_INST_IDENT_U,
|
||||
/* lookup the alias with index D and push its value (error if unset) */
|
||||
BC_INST_LOOKUP,
|
||||
/* lookup an unknown ident with the name being given by the string on
|
||||
* top of the stack; if a var or a set alias, update top of the stack
|
||||
* to the ident's value (according to M), else raise error
|
||||
*/
|
||||
BC_INST_LOOKUP_U,
|
||||
/* concatenate D values on top of the stack together, with topmost value
|
||||
* being last; delimit with spaces; push the result according to M
|
||||
*/
|
||||
BC_INST_CONC,
|
||||
/* like above but without delimiter */
|
||||
BC_INST_CONC_W,
|
||||
/* push the value of var with index D on the stack according to M */
|
||||
BC_INST_VAR,
|
||||
/* pop a value off the stack and set alias with index D to it */
|
||||
BC_INST_ALIAS,
|
||||
/* pop 2 values off the stack; top is value to set, below is alias name */
|
||||
BC_INST_ALIAS_U,
|
||||
/* call alias with index D and arg count following the instruction, pop
|
||||
* the arguments off the stack (top being last); if unknown, raise error,
|
||||
* store result in R according to M
|
||||
*/
|
||||
BC_INST_CALL,
|
||||
/* given argument count D, pop the arguments off the stack (top being last)
|
||||
* and then pop one more value (that being the ident name); look up the
|
||||
* ident (raise error if non-existent) and then call according to its
|
||||
* type (vars behave as in PRINT); store result in R according to M
|
||||
*/
|
||||
BC_INST_CALL_U,
|
||||
/* call builtin command with index D; arguments are popped off the stack,
|
||||
* last argument being topmost; result of the call goes in R according to M
|
||||
*/
|
||||
BC_INST_COM,
|
||||
/* call builtin command with index D and arg count following the
|
||||
* instruction, arguments are popped off the stack and passed as is
|
||||
*/
|
||||
BC_INST_COM_V,
|
||||
|
||||
/* opcode mask */
|
||||
BC_INST_OP_MASK = 0x3F,
|
||||
/* type mask shift */
|
||||
BC_INST_RET = 6,
|
||||
/* type mask, shifted */
|
||||
BC_INST_RET_MASK = 0xC0,
|
||||
|
||||
/* type mask flags */
|
||||
BC_RET_NULL = VAL_NULL << BC_INST_RET,
|
||||
BC_RET_STRING = VAL_STRING << BC_INST_RET,
|
||||
BC_RET_INT = VAL_INT << BC_INST_RET,
|
||||
BC_RET_FLOAT = VAL_FLOAT << BC_INST_RET,
|
||||
|
||||
/* BC_INST_JUMP_B, BC_INST_JUMP_RESULT */
|
||||
BC_INST_FLAG_TRUE = 1 << BC_INST_RET,
|
||||
BC_INST_FLAG_FALSE = 0 << BC_INST_RET
|
||||
};
|
||||
|
||||
std::uint32_t *bcode_alloc(internal_state *cs, std::size_t sz);
|
||||
|
||||
void bcode_addref(std::uint32_t *code);
|
||||
void bcode_unref(std::uint32_t *code);
|
||||
|
||||
struct empty_block {
|
||||
bcode init;
|
||||
std::uint32_t code;
|
||||
};
|
||||
|
||||
empty_block *bcode_init_empty(internal_state *cs);
|
||||
void bcode_free_empty(internal_state *cs, empty_block *empty);
|
||||
bcode *bcode_get_empty(empty_block *empty, std::size_t val);
|
||||
|
||||
struct bcode_p {
|
||||
bcode_p(bcode_ref const &r): br{const_cast<bcode_ref *>(&r)} {}
|
||||
|
||||
bcode *get() {
|
||||
return br->p_code;
|
||||
}
|
||||
|
||||
static bcode_ref make_ref(bcode *v) {
|
||||
return bcode_ref{v};
|
||||
}
|
||||
|
||||
bcode_ref *br;
|
||||
};
|
||||
|
||||
} /* namespace cubescript */
|
||||
|
||||
#endif
|
107
src/cs_error.cc
107
src/cs_error.cc
|
@ -1,107 +0,0 @@
|
|||
#include <cubescript/cubescript.hh>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "cs_thread.hh"
|
||||
#include "cs_error.hh"
|
||||
|
||||
namespace cubescript {
|
||||
|
||||
static void save_stack(
|
||||
state &cs, typename error::stack_node *&sbeg,
|
||||
typename error::stack_node *&send
|
||||
) {
|
||||
auto &ts = state_p{cs}.ts();
|
||||
builtin_var *dalias = ts.istate->ivar_dbgalias;
|
||||
auto dval = std::size_t(std::clamp(
|
||||
dalias->value().get_integer(), integer_type(0), integer_type(1000)
|
||||
));
|
||||
if (!dval) {
|
||||
sbeg = send = nullptr;
|
||||
return;
|
||||
}
|
||||
std::size_t depth = 0;
|
||||
std::size_t total = ts.callstack.size();
|
||||
if (!total) {
|
||||
sbeg = send = nullptr;
|
||||
return;
|
||||
}
|
||||
auto slen = std::min(total, dval);
|
||||
auto *st = static_cast<typename error::stack_node *>(ts.istate->alloc(
|
||||
nullptr, 0, sizeof(typename error::stack_node) * slen
|
||||
));
|
||||
typename error::stack_node *ret = st, *nd = st;
|
||||
++st;
|
||||
for (std::size_t i = total - 1;; --i) {
|
||||
auto &lev = ts.callstack[i];
|
||||
++depth;
|
||||
if (depth < dval) {
|
||||
new (nd) typename error::stack_node{
|
||||
lev.id, total - depth + 1
|
||||
};
|
||||
if (i == 0) {
|
||||
break;
|
||||
}
|
||||
nd = st++;
|
||||
} else if (i == 0) {
|
||||
new (nd) typename error::stack_node{lev.id, 1};
|
||||
break;
|
||||
}
|
||||
}
|
||||
sbeg = ret;
|
||||
send = ret + slen;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT error::error(error &&v):
|
||||
p_errbeg{v.p_errbeg}, p_errend{v.p_errend},
|
||||
p_sbeg{v.p_sbeg}, p_send{v.p_send}, p_state{v.p_state}
|
||||
{
|
||||
v.p_sbeg = v.p_send = nullptr;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT error &error::operator=(error &&v) {
|
||||
std::swap(p_errbeg, v.p_errbeg);
|
||||
std::swap(p_errend, v.p_errend);
|
||||
std::swap(p_sbeg, v.p_sbeg);
|
||||
std::swap(p_send, v.p_send);
|
||||
std::swap(p_state, v.p_state);
|
||||
return *this;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT error::~error() {
|
||||
state_p{*p_state}.ts().istate->destroy_array(
|
||||
p_sbeg, std::size_t(p_send - p_sbeg)
|
||||
);
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT error::error(state &cs, std::string_view msg):
|
||||
p_errbeg{}, p_errend{}, p_state{&cs}
|
||||
{
|
||||
char *sp;
|
||||
char *buf = state_p{cs}.ts().request_errbuf(msg.size(), sp);
|
||||
std::memcpy(buf, msg.data(), msg.size());
|
||||
buf[msg.size()] = '\0';
|
||||
p_errbeg = sp;
|
||||
p_errend = buf + msg.size();
|
||||
save_stack(cs, p_sbeg, p_send);
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT error::error(
|
||||
state &cs, char const *errbeg, char const *errend
|
||||
): p_errbeg{errbeg}, p_errend{errend}, p_state{&cs} {
|
||||
save_stack(cs, p_sbeg, p_send);
|
||||
}
|
||||
|
||||
std::string_view error::what() const {
|
||||
return std::string_view{p_errbeg, std::size_t(p_errend - p_errbeg)};
|
||||
}
|
||||
|
||||
span_type<typename error::stack_node const> error::stack() const {
|
||||
return span_type<typename error::stack_node const>{
|
||||
p_sbeg, std::size_t(p_send - p_sbeg)
|
||||
};
|
||||
}
|
||||
|
||||
} /* namespace cubescript */
|
|
@ -1,32 +0,0 @@
|
|||
#ifndef LIBCUBESCRIPT_ERROR_HH
|
||||
#define LIBCUBESCRIPT_ERROR_HH
|
||||
|
||||
#include <cubescript/cubescript.hh>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstddef>
|
||||
|
||||
namespace cubescript {
|
||||
|
||||
struct error_p {
|
||||
template<typename ...A>
|
||||
static error make(state &cs, std::string_view msg, A const &...args) {
|
||||
std::size_t sz = msg.size() + 64;
|
||||
char *buf, *sp;
|
||||
for (;;) {
|
||||
buf = state_p{cs}.ts().request_errbuf(sz, sp);
|
||||
int written = std::snprintf(buf, sz, msg.data(), args...);
|
||||
if (written <= 0) {
|
||||
throw error{cs, "malformed format string"};
|
||||
} else if (std::size_t(written) <= sz) {
|
||||
break;
|
||||
}
|
||||
sz = std::size_t(written);
|
||||
}
|
||||
return error{cs, sp, buf + sz};
|
||||
}
|
||||
};
|
||||
|
||||
} /* namespace cubescript */
|
||||
|
||||
#endif
|
1904
src/cs_gen.cc
1904
src/cs_gen.cc
File diff suppressed because it is too large
Load Diff
109
src/cs_gen.hh
109
src/cs_gen.hh
|
@ -1,109 +0,0 @@
|
|||
#ifndef LIBCUBESCRIPT_GEN_HH
|
||||
#define LIBCUBESCRIPT_GEN_HH
|
||||
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include <cubescript/cubescript.hh>
|
||||
|
||||
#include "cs_std.hh"
|
||||
#include "cs_bcode.hh"
|
||||
#include "cs_thread.hh"
|
||||
|
||||
namespace cubescript {
|
||||
|
||||
struct gen_state {
|
||||
thread_state &ts;
|
||||
|
||||
gen_state() = delete;
|
||||
gen_state(thread_state &tsr):
|
||||
ts{tsr}, code{tsr.istate}
|
||||
{}
|
||||
|
||||
std::size_t count() const;
|
||||
std::uint32_t peek(std::size_t idx) const;
|
||||
|
||||
bcode_ref steal_ref();
|
||||
|
||||
void gen_pop();
|
||||
void gen_dup(int ltype = 0);
|
||||
void gen_result(int ltype = 0);
|
||||
void gen_push_result(int ltype = 0);
|
||||
void gen_force(int ltype);
|
||||
|
||||
void gen_not(int ltype = 0);
|
||||
bool gen_if(std::size_t tpos, std::size_t fpos, int ltype = 0);
|
||||
void gen_and_or(bool is_or, std::size_t start, int ltype = 0);
|
||||
|
||||
void gen_val_null();
|
||||
void gen_result_null(int ltype = 0);
|
||||
void gen_result_true(int ltype = 0);
|
||||
void gen_result_false(int ltype = 0);
|
||||
|
||||
void gen_val_integer(integer_type v = 0);
|
||||
void gen_val_integer(std::string_view v);
|
||||
|
||||
void gen_val_float(float_type v = 0);
|
||||
void gen_val_float(std::string_view v);
|
||||
|
||||
void gen_val_string(std::string_view v = std::string_view{});
|
||||
void gen_val_string_unescape(std::string_view str);
|
||||
void gen_val_block(std::string_view str);
|
||||
|
||||
void gen_val_ident();
|
||||
void gen_val_ident(ident &i);
|
||||
void gen_val_ident(std::string_view v);
|
||||
|
||||
void gen_val(
|
||||
int val_type, std::string_view v = std::string_view{},
|
||||
std::size_t line = 0
|
||||
);
|
||||
|
||||
void gen_lookup_var(ident &id, int ltype = 0);
|
||||
|
||||
void gen_lookup_alias(ident &id, int ltype = 0, int dtype = 0);
|
||||
void gen_lookup_ident(int ltype = 0);
|
||||
|
||||
void gen_assign_alias(ident &id);
|
||||
void gen_assign();
|
||||
|
||||
void gen_compile(bool cond = false);
|
||||
void gen_ident_lookup();
|
||||
|
||||
void gen_concat(std::size_t concs, bool space, int ltype = 0);
|
||||
|
||||
void gen_command_call(
|
||||
ident &id, int comt, int ltype = 0, std::uint32_t nargs = 0
|
||||
);
|
||||
void gen_alias_call(ident &id, std::uint32_t nargs = 0);
|
||||
void gen_call(std::uint32_t nargs = 0);
|
||||
|
||||
void gen_local(std::uint32_t nargs);
|
||||
void gen_do(bool args, int ltype = 0);
|
||||
|
||||
void gen_break();
|
||||
void gen_continue();
|
||||
|
||||
void gen_main(
|
||||
std::string_view s, std::string_view src = std::string_view{}
|
||||
);
|
||||
void gen_main_null();
|
||||
void gen_main_integer(integer_type v);
|
||||
void gen_main_float(float_type v);
|
||||
|
||||
bool is_block(std::size_t idx, std::size_t epos = 0) const;
|
||||
|
||||
void gen_block();
|
||||
std::pair<std::size_t, std::string_view> gen_block(
|
||||
std::string_view v, std::size_t line,
|
||||
int ltype = VAL_NULL, int term = '\0'
|
||||
);
|
||||
|
||||
private:
|
||||
valbuf<std::uint32_t> code;
|
||||
};
|
||||
|
||||
} /* namespace cubescript */
|
||||
|
||||
#endif /* LIBCUBESCRIPT_GEN_HH */
|
420
src/cs_ident.cc
420
src/cs_ident.cc
|
@ -1,420 +0,0 @@
|
|||
#include "cs_ident.hh"
|
||||
|
||||
#include "cs_bcode.hh"
|
||||
#include "cs_thread.hh"
|
||||
#include "cs_vm.hh"
|
||||
#include "cs_error.hh"
|
||||
|
||||
namespace cubescript {
|
||||
|
||||
ident_impl::ident_impl(ident_type tp, string_ref nm, int fl):
|
||||
p_name{nm}, p_type{int(tp)}, p_flags{fl}
|
||||
{}
|
||||
|
||||
bool ident_is_callable(ident const *id) {
|
||||
auto tp = id->type();
|
||||
if ((tp != ident_type::COMMAND) && (tp != ident_type::SPECIAL)) {
|
||||
return false;
|
||||
}
|
||||
return !!static_cast<command_impl const *>(id)->p_cb_cftv;
|
||||
}
|
||||
|
||||
var_impl::var_impl(string_ref name, int fl):
|
||||
ident_impl{ident_type::VAR, name, fl}
|
||||
{}
|
||||
|
||||
alias_impl::alias_impl(
|
||||
state &, string_ref name, string_ref a, int fl
|
||||
):
|
||||
ident_impl{ident_type::ALIAS, name, fl}, p_initial{}
|
||||
{
|
||||
p_initial.val_s.set_string(a);
|
||||
}
|
||||
|
||||
alias_impl::alias_impl(
|
||||
state &cs, string_ref name, std::string_view a, int fl
|
||||
):
|
||||
ident_impl{ident_type::ALIAS, name, fl}, p_initial{}
|
||||
{
|
||||
p_initial.val_s.set_string(a, cs);
|
||||
}
|
||||
|
||||
alias_impl::alias_impl(state &, string_ref name, integer_type a, int fl):
|
||||
ident_impl{ident_type::ALIAS, name, fl}, p_initial{}
|
||||
{
|
||||
p_initial.val_s.set_integer(a);
|
||||
}
|
||||
|
||||
alias_impl::alias_impl(state &, string_ref name, float_type a, int fl):
|
||||
ident_impl{ident_type::ALIAS, name, fl}, p_initial{}
|
||||
{
|
||||
p_initial.val_s.set_float(a);
|
||||
}
|
||||
|
||||
alias_impl::alias_impl(state &, string_ref name, int fl):
|
||||
ident_impl{ident_type::ALIAS, name, fl}, p_initial{}
|
||||
{
|
||||
p_initial.val_s.set_none();
|
||||
}
|
||||
|
||||
alias_impl::alias_impl(state &, string_ref name, any_value v, int fl):
|
||||
ident_impl{ident_type::ALIAS, name, fl}, p_initial{}
|
||||
{
|
||||
p_initial.val_s = v.get_plain();
|
||||
}
|
||||
|
||||
command_impl::command_impl(
|
||||
string_ref name, string_ref args, int nargs, command_func f
|
||||
):
|
||||
ident_impl{ident_type::COMMAND, name, 0},
|
||||
p_cargs{args}, p_cb_cftv{std::move(f)}, p_numargs{nargs}
|
||||
{}
|
||||
|
||||
void var_changed(thread_state &ts, builtin_var &id, any_value &oldval) {
|
||||
auto *cid = ts.istate->cmd_var_changed;
|
||||
if (!cid) {
|
||||
return;
|
||||
}
|
||||
auto *cimp = static_cast<command_impl *>(cid);
|
||||
any_value val[3] = {};
|
||||
val[0].set_ident(id);
|
||||
val[1] = std::move(oldval);
|
||||
val[2] = id.value();
|
||||
cimp->call(ts, span_type<any_value>{
|
||||
static_cast<any_value *>(val), 3
|
||||
}, val[0]);
|
||||
}
|
||||
|
||||
command *var_impl::get_setter(thread_state &ts) const {
|
||||
switch (p_storage.type()) {
|
||||
case value_type::INTEGER:
|
||||
return ts.istate->cmd_ivar;
|
||||
case value_type::FLOAT:
|
||||
return ts.istate->cmd_fvar;
|
||||
case value_type::STRING:
|
||||
return ts.istate->cmd_svar;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
abort();
|
||||
return nullptr; /* not reached */
|
||||
}
|
||||
|
||||
void command_impl::call(
|
||||
thread_state &ts, span_type<any_value> args, any_value &ret
|
||||
) const {
|
||||
auto idstsz = ts.idstack.size();
|
||||
try {
|
||||
p_cb_cftv(*ts.pstate, args, ret);
|
||||
} catch (...) {
|
||||
ts.idstack.resize(idstsz);
|
||||
throw;
|
||||
}
|
||||
ts.idstack.resize(idstsz);
|
||||
}
|
||||
|
||||
bool ident_is_used_arg(ident const *id, thread_state &ts) {
|
||||
if (ts.callstack.empty()) {
|
||||
return true;
|
||||
}
|
||||
return ts.callstack.back().usedargs[id->index()];
|
||||
}
|
||||
|
||||
void alias_stack::push(ident_stack &st) {
|
||||
st.next = node;
|
||||
node = &st;
|
||||
}
|
||||
|
||||
void alias_stack::pop() {
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
void alias_stack::set_arg(alias *a, thread_state &ts, any_value &v) {
|
||||
if (ident_is_used_arg(a, ts)) {
|
||||
node->code = bcode_ref{};
|
||||
} else {
|
||||
push(ts.idstack.emplace_back());
|
||||
ts.callstack.back().usedargs[a->index()] = true;
|
||||
}
|
||||
node->val_s = std::move(v);
|
||||
}
|
||||
|
||||
void alias_stack::set_alias(alias *a, thread_state &ts, any_value &v) {
|
||||
node->val_s = std::move(v);
|
||||
node->code = bcode_ref{};
|
||||
flags = ts.ident_flags;
|
||||
auto *imp = static_cast<alias_impl *>(a);
|
||||
if (node == &imp->p_initial) {
|
||||
imp->p_flags = flags;
|
||||
}
|
||||
}
|
||||
|
||||
/* public interface */
|
||||
|
||||
LIBCUBESCRIPT_EXPORT ident::~ident() {}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT ident_type ident::type() const {
|
||||
if (p_impl->p_type > ID_ALIAS) {
|
||||
return ident_type::SPECIAL;
|
||||
}
|
||||
return ident_type(p_impl->p_type);
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT std::string_view ident::name() const {
|
||||
return p_impl->p_name;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT int ident::index() const {
|
||||
return p_impl->p_index;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT bool ident::operator==(ident &other) const {
|
||||
return this == &other;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT bool ident::operator!=(ident &other) const {
|
||||
return this != &other;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT bool ident::is_overridden(state &cs) const {
|
||||
switch (type()) {
|
||||
case ident_type::VAR:
|
||||
return (p_impl->p_flags & IDENT_FLAG_OVERRIDDEN);
|
||||
case ident_type::ALIAS:
|
||||
return (state_p{cs}.ts().get_astack(
|
||||
static_cast<alias const *>(this)
|
||||
).flags & IDENT_FLAG_OVERRIDDEN);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT bool ident::is_persistent(state &cs) const {
|
||||
switch (type()) {
|
||||
case ident_type::VAR:
|
||||
return (p_impl->p_flags & IDENT_FLAG_PERSIST);
|
||||
case ident_type::ALIAS:
|
||||
return (state_p{cs}.ts().get_astack(
|
||||
static_cast<alias const *>(this)
|
||||
).flags & IDENT_FLAG_PERSIST);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT any_value ident::call(span_type<any_value>, state &cs) {
|
||||
throw error{cs, "this ident type is not callable"};
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT bool builtin_var::is_read_only() const {
|
||||
return (p_impl->p_flags & IDENT_FLAG_READONLY);
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT bool builtin_var::is_overridable() const {
|
||||
return (p_impl->p_flags & IDENT_FLAG_OVERRIDE);
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT var_type builtin_var::variable_type() const {
|
||||
if (p_impl->p_flags & IDENT_FLAG_OVERRIDE) {
|
||||
return var_type::OVERRIDABLE;
|
||||
} else if (p_impl->p_flags & IDENT_FLAG_PERSIST) {
|
||||
return var_type::PERSISTENT;
|
||||
} else {
|
||||
return var_type::DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT void builtin_var::save(state &cs) {
|
||||
auto &ts = state_p{cs}.ts();
|
||||
if ((ts.ident_flags & IDENT_FLAG_OVERRIDDEN) || is_overridable()) {
|
||||
if (p_impl->p_flags & IDENT_FLAG_PERSIST) {
|
||||
throw error_p::make(
|
||||
cs, "cannot override persistent variable '%s'",
|
||||
name().data()
|
||||
);
|
||||
}
|
||||
if (!(p_impl->p_flags & IDENT_FLAG_OVERRIDDEN)) {
|
||||
auto *imp = static_cast<var_impl *>(p_impl);
|
||||
imp->p_override = std::move(imp->p_storage);
|
||||
p_impl->p_flags |= IDENT_FLAG_OVERRIDDEN;
|
||||
}
|
||||
} else {
|
||||
p_impl->p_flags &= IDENT_FLAG_OVERRIDDEN;
|
||||
}
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT any_value builtin_var::call(
|
||||
span_type<any_value> args, state &cs
|
||||
) {
|
||||
command *hid = static_cast<var_impl *>(this)->get_setter(state_p{cs}.ts());
|
||||
any_value ret{};
|
||||
auto &ts = state_p{cs}.ts();
|
||||
auto *cimp = static_cast<command_impl *>(hid);
|
||||
auto &targs = ts.vmstack;
|
||||
auto osz = targs.size();
|
||||
auto anargs = std::size_t(cimp->arg_count());
|
||||
auto nargs = args.size();
|
||||
targs.resize(
|
||||
osz + std::max(args.size(), anargs + 1)
|
||||
);
|
||||
for (std::size_t i = 0; i < nargs; ++i) {
|
||||
targs[osz + i + 1] = args[i];
|
||||
}
|
||||
exec_command(ts, cimp, this, &targs[osz], ret, nargs + 1, false);
|
||||
return ret;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT any_value builtin_var::value() const {
|
||||
return static_cast<var_impl const *>(p_impl)->p_storage;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT void builtin_var::set_raw_value(
|
||||
state &cs, any_value val
|
||||
) {
|
||||
switch (static_cast<var_impl *>(p_impl)->p_storage.type()) {
|
||||
case value_type::INTEGER:
|
||||
val.force_integer();
|
||||
break;
|
||||
case value_type::FLOAT:
|
||||
val.force_float();
|
||||
break;
|
||||
case value_type::STRING:
|
||||
val.force_string(cs);
|
||||
break;
|
||||
default:
|
||||
abort(); /* unreachable unless we have a bug */
|
||||
break;
|
||||
}
|
||||
static_cast<var_impl *>(p_impl)->p_storage = std::move(val);
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT void builtin_var::set_value(
|
||||
state &cs, any_value val, bool do_write, bool trigger
|
||||
) {
|
||||
if (is_read_only()) {
|
||||
throw error_p::make(
|
||||
cs, "variable '%s' is read only", name().data()
|
||||
);
|
||||
}
|
||||
if (!do_write) {
|
||||
return;
|
||||
}
|
||||
save(cs);
|
||||
auto oldv = value();
|
||||
set_raw_value(cs, std::move(val));
|
||||
if (trigger) {
|
||||
var_changed(state_p{cs}.ts(), *this, oldv);
|
||||
}
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT any_value alias::value(state &cs) const {
|
||||
return state_p{cs}.ts().get_astack(this).node->val_s;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT void alias::set_value(state &cs, any_value v) {
|
||||
auto &ts = state_p{cs}.ts();
|
||||
if (is_arg()) {
|
||||
ts.get_astack(this).set_arg(this, ts, v);
|
||||
} else {
|
||||
ts.get_astack(this).set_alias(this, ts, v);
|
||||
}
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT bool alias::is_arg() const {
|
||||
return (static_cast<alias_impl const *>(this)->p_flags & IDENT_FLAG_ARG);
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT any_value alias::call(
|
||||
span_type<any_value> args, state &cs
|
||||
) {
|
||||
auto &ts = state_p{cs}.ts();
|
||||
if (is_arg() && !ident_is_used_arg(this, ts)) {
|
||||
return any_value{};
|
||||
}
|
||||
auto nargs = args.size();
|
||||
auto &ast = ts.get_astack(this);
|
||||
if (ast.node->val_s.type() != value_type::NONE) {
|
||||
return exec_alias(ts, this, &args[0], nargs, ast);
|
||||
}
|
||||
return any_value{};
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT std::string_view command::args() const {
|
||||
return static_cast<command_impl const *>(this)->p_cargs;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT int command::arg_count() const {
|
||||
return static_cast<command_impl const *>(this)->p_numargs;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT any_value command::call(
|
||||
span_type<any_value> args, state &cs
|
||||
) {
|
||||
any_value ret{};
|
||||
auto &cimpl = static_cast<command_impl &>(*this);
|
||||
if (!cimpl.p_cb_cftv) {
|
||||
return ret;
|
||||
}
|
||||
auto nargs = args.size();
|
||||
auto &ts = state_p{cs}.ts();
|
||||
if (nargs < std::size_t(cimpl.arg_count())) {
|
||||
auto &targs = ts.vmstack;
|
||||
auto osz = targs.size();
|
||||
targs.resize(osz + cimpl.arg_count());
|
||||
try {
|
||||
for (std::size_t i = 0; i < nargs; ++i) {
|
||||
targs[osz + i] = args[i];
|
||||
}
|
||||
exec_command(ts, &cimpl, this, &targs[osz], ret, nargs, false);
|
||||
} catch (...) {
|
||||
targs.resize(osz);
|
||||
throw;
|
||||
}
|
||||
targs.resize(osz);
|
||||
} else {
|
||||
exec_command(ts, &cimpl, this, &args[0], ret, nargs, false);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* external API for alias stack management */
|
||||
|
||||
LIBCUBESCRIPT_EXPORT alias_local::alias_local(state &cs, ident &a) {
|
||||
if (a.type() != ident_type::ALIAS) {
|
||||
throw error_p::make(cs, "ident '%s' is not an alias", a.name().data());
|
||||
}
|
||||
auto &ts = state_p{cs}.ts();
|
||||
p_alias = static_cast<alias *>(&a);
|
||||
auto &ast = ts.get_astack(p_alias);
|
||||
ast.push(ts.idstack.emplace_back());
|
||||
p_sp = *
|
||||
ast.flags &= ~IDENT_FLAG_UNKNOWN;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT alias_local::alias_local(state &cs, std::string_view name):
|
||||
alias_local{cs, cs.new_ident(name)}
|
||||
{}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT alias_local::alias_local(state &cs, any_value const &v):
|
||||
alias_local{cs, (
|
||||
v.type() == value_type::IDENT
|
||||
) ? v.get_ident(cs) : cs.new_ident(v.get_string(cs))}
|
||||
{}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT alias_local::~alias_local() {
|
||||
if (p_alias) {
|
||||
static_cast<alias_stack *>(p_sp)->pop();
|
||||
}
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT bool alias_local::set(any_value val) {
|
||||
if (!p_alias) {
|
||||
return false;
|
||||
}
|
||||
static_cast<alias_stack *>(p_sp)->node->val_s = std::move(val);
|
||||
return true;
|
||||
}
|
||||
|
||||
} /* namespace cubescript */
|
119
src/cs_ident.hh
119
src/cs_ident.hh
|
@ -1,119 +0,0 @@
|
|||
#ifndef LIBCUBESCRIPT_ALIAS_HH
|
||||
#define LIBCUBESCRIPT_ALIAS_HH
|
||||
|
||||
#include <cubescript/cubescript.hh>
|
||||
|
||||
#include <bitset>
|
||||
|
||||
namespace cubescript {
|
||||
|
||||
static constexpr std::size_t MAX_ARGUMENTS = 32;
|
||||
using argset = std::bitset<MAX_ARGUMENTS>;
|
||||
|
||||
enum {
|
||||
ID_UNKNOWN = -1, ID_VAR, ID_COMMAND, ID_ALIAS,
|
||||
ID_LOCAL, ID_DO, ID_DOARGS, ID_IF, ID_BREAK, ID_CONTINUE, ID_RESULT,
|
||||
ID_NOT, ID_AND, ID_OR
|
||||
};
|
||||
|
||||
enum {
|
||||
IDENT_FLAG_UNKNOWN = 1 << 0,
|
||||
IDENT_FLAG_ARG = 1 << 1,
|
||||
IDENT_FLAG_READONLY = 1 << 2,
|
||||
IDENT_FLAG_OVERRIDE = 1 << 3,
|
||||
IDENT_FLAG_OVERRIDDEN = 1 << 4,
|
||||
IDENT_FLAG_PERSIST = 1 << 5
|
||||
};
|
||||
|
||||
struct ident_stack {
|
||||
any_value val_s;
|
||||
bcode_ref code;
|
||||
ident_stack *next;
|
||||
ident_stack(): val_s{}, code{}, next{nullptr} {}
|
||||
};
|
||||
|
||||
struct alias_stack {
|
||||
ident_stack *node = nullptr;
|
||||
int flags = 0;
|
||||
|
||||
void push(ident_stack &st);
|
||||
void pop();
|
||||
|
||||
void set_arg(alias *a, thread_state &ts, any_value &v);
|
||||
void set_alias(alias *a, thread_state &ts, any_value &v);
|
||||
};
|
||||
|
||||
struct ident_impl {
|
||||
ident_impl() = delete;
|
||||
ident_impl(ident_impl const &) = delete;
|
||||
ident_impl(ident_impl &&) = delete;
|
||||
|
||||
/* trigger destructors for all inherited members properly */
|
||||
virtual ~ident_impl() {};
|
||||
|
||||
ident_impl &operator=(ident_impl const &) = delete;
|
||||
ident_impl &operator=(ident_impl &&) = delete;
|
||||
|
||||
ident_impl(ident_type tp, string_ref name, int flags = 0);
|
||||
|
||||
string_ref p_name;
|
||||
/* represents the ident_type above, but internally it has a wider
|
||||
* variety of values, so it's an int here (maps to an internal enum)
|
||||
*/
|
||||
int p_type, p_flags;
|
||||
|
||||
int p_index = -1;
|
||||
};
|
||||
|
||||
bool ident_is_callable(ident const *id);
|
||||
|
||||
struct var_impl: ident_impl, builtin_var {
|
||||
var_impl(string_ref name, int flags);
|
||||
|
||||
command *get_setter(thread_state &ts) const;
|
||||
|
||||
any_value p_storage{};
|
||||
any_value p_override{};
|
||||
};
|
||||
|
||||
void var_changed(thread_state &ts, builtin_var &id, any_value &oldval);
|
||||
|
||||
struct alias_impl: ident_impl, alias {
|
||||
alias_impl(state &cs, string_ref n, string_ref a, int flags);
|
||||
alias_impl(state &cs, string_ref n, std::string_view a, int flags);
|
||||
alias_impl(state &cs, string_ref n, integer_type a, int flags);
|
||||
alias_impl(state &cs, string_ref n, float_type a, int flags);
|
||||
alias_impl(state &cs, string_ref n, int flags);
|
||||
alias_impl(state &cs, string_ref n, any_value v, int flags);
|
||||
|
||||
ident_stack p_initial;
|
||||
};
|
||||
|
||||
struct command_impl: ident_impl, command {
|
||||
command_impl(
|
||||
string_ref name, string_ref args, int numargs, command_func func
|
||||
);
|
||||
|
||||
void call(
|
||||
thread_state &ts, span_type<any_value> args, any_value &ret
|
||||
) const;
|
||||
|
||||
string_ref p_cargs;
|
||||
command_func p_cb_cftv;
|
||||
int p_numargs;
|
||||
};
|
||||
|
||||
bool ident_is_used_arg(ident const *id, thread_state &ts);
|
||||
|
||||
struct ident_p {
|
||||
ident_p(ident &id): ip{&id} {}
|
||||
|
||||
ident_impl &impl() { return *ip->p_impl; }
|
||||
void impl(ident_impl *impl) { ip->p_impl = impl; }
|
||||
|
||||
ident *ip;
|
||||
};
|
||||
|
||||
} /* namespace cubescript */
|
||||
|
||||
#endif
|
1542
src/cs_parser.cc
1542
src/cs_parser.cc
File diff suppressed because it is too large
Load Diff
|
@ -1,90 +0,0 @@
|
|||
#ifndef LIBCUBESCRIPT_PARSER_HH
|
||||
#define LIBCUBESCRIPT_PARSER_HH
|
||||
|
||||
#include <cstdlib>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
#include <cubescript/cubescript.hh>
|
||||
|
||||
#include "cs_std.hh"
|
||||
#include "cs_bcode.hh"
|
||||
#include "cs_ident.hh"
|
||||
#include "cs_thread.hh"
|
||||
#include "cs_gen.hh"
|
||||
|
||||
namespace cubescript {
|
||||
|
||||
integer_type parse_int(std::string_view input, std::string_view *end = nullptr);
|
||||
float_type parse_float(std::string_view input, std::string_view *end = nullptr);
|
||||
|
||||
bool is_valid_name(std::string_view input);
|
||||
|
||||
struct parser_state {
|
||||
thread_state &ts;
|
||||
gen_state &gs;
|
||||
char const *source, *send;
|
||||
std::size_t current_line;
|
||||
|
||||
parser_state() = delete;
|
||||
parser_state(thread_state &tsr, gen_state &gsr):
|
||||
ts{tsr}, gs{gsr}, source{}, send{}, current_line{1}
|
||||
{
|
||||
ts.current_line = ¤t_line;
|
||||
}
|
||||
|
||||
~parser_state() {
|
||||
ts.current_line = nullptr;
|
||||
}
|
||||
|
||||
std::string_view get_str();
|
||||
charbuf get_str_dup();
|
||||
|
||||
std::string_view get_word();
|
||||
|
||||
void parse_block(int ltype, int term = '\0');
|
||||
|
||||
void next_char() {
|
||||
if (source == send) {
|
||||
return;
|
||||
}
|
||||
if (*source == '\n') {
|
||||
++current_line;
|
||||
}
|
||||
++source;
|
||||
}
|
||||
|
||||
char current(size_t ahead = 0) {
|
||||
if (std::size_t(send - source) <= ahead) {
|
||||
return '\0';
|
||||
}
|
||||
return source[ahead];
|
||||
}
|
||||
|
||||
std::string_view read_macro_name();
|
||||
|
||||
char skip_until(std::string_view chars);
|
||||
char skip_until(char cf);
|
||||
|
||||
void skip_comments();
|
||||
|
||||
void parse_lookup(int ltype);
|
||||
bool parse_subblock();
|
||||
void parse_blockarg(int ltype);
|
||||
bool parse_arg(int ltype, charbuf *word = nullptr);
|
||||
|
||||
bool parse_call_command(command_impl *id, ident &self, int rettype);
|
||||
bool parse_call_alias(alias &id);
|
||||
bool parse_call_id(ident &id, int ltype);
|
||||
|
||||
bool parse_assign(charbuf &idname, int ltype, int term, bool &noass);
|
||||
|
||||
bool parse_id_local();
|
||||
bool parse_id_do(bool args, int ltype);
|
||||
bool parse_id_if(ident &id, int ltype);
|
||||
bool parse_id_and_or(ident &id, int ltype);
|
||||
};
|
||||
|
||||
} /* namespace cubescript */
|
||||
|
||||
#endif
|
745
src/cs_state.cc
745
src/cs_state.cc
|
@ -1,745 +0,0 @@
|
|||
#include <memory>
|
||||
#include <cstdio>
|
||||
#include <cmath>
|
||||
|
||||
#include "cs_bcode.hh"
|
||||
#include "cs_state.hh"
|
||||
#include "cs_thread.hh"
|
||||
#include "cs_strman.hh"
|
||||
#include "cs_vm.hh"
|
||||
#include "cs_parser.hh"
|
||||
#include "cs_error.hh"
|
||||
|
||||
namespace cubescript {
|
||||
|
||||
internal_state::internal_state(alloc_func af, void *data):
|
||||
allocf{af}, aptr{data},
|
||||
idents{allocator_type{this}},
|
||||
identmap{allocator_type{this}},
|
||||
strman{create<string_pool>(this)},
|
||||
empty{bcode_init_empty(this)}
|
||||
{}
|
||||
|
||||
internal_state::~internal_state() {
|
||||
for (auto &p: idents) {
|
||||
destroy(&ident_p{*p.second}.impl());
|
||||
}
|
||||
bcode_free_empty(this, empty);
|
||||
destroy(strman);
|
||||
}
|
||||
|
||||
void *internal_state::alloc(void *ptr, size_t os, size_t ns) {
|
||||
void *p = allocf(aptr, ptr, os, ns);
|
||||
if (!p && ns) {
|
||||
throw std::bad_alloc{};
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static void *default_alloc(void *, void *p, size_t, size_t ns) {
|
||||
if (!ns) {
|
||||
std::free(p);
|
||||
return nullptr;
|
||||
}
|
||||
return std::realloc(p, ns);
|
||||
}
|
||||
|
||||
ident *internal_state::add_ident(ident *id, ident_impl *impl) {
|
||||
if (!id) {
|
||||
return nullptr;
|
||||
}
|
||||
ident_p{*id}.impl(impl);
|
||||
idents[id->name()] = id;
|
||||
impl->p_index = int(identmap.size());
|
||||
identmap.push_back(id);
|
||||
return identmap.back();
|
||||
}
|
||||
|
||||
ident &internal_state::new_ident(state &cs, std::string_view name, int flags) {
|
||||
ident *id = get_ident(name);
|
||||
if (!id) {
|
||||
if (!is_valid_name(name)) {
|
||||
throw error_p::make(
|
||||
cs, "'%s' is not a valid identifier name", name.data()
|
||||
);
|
||||
}
|
||||
auto *inst = create<alias_impl>(
|
||||
cs, string_ref{cs, name}, flags
|
||||
);
|
||||
id = add_ident(inst, inst);
|
||||
}
|
||||
return *id;
|
||||
}
|
||||
|
||||
ident *internal_state::get_ident(std::string_view name) const {
|
||||
auto id = idents.find(name);
|
||||
if (id == idents.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return id->second;
|
||||
}
|
||||
|
||||
/* public interfaces */
|
||||
|
||||
state::state(): state{default_alloc, nullptr} {}
|
||||
|
||||
state::state(alloc_func func, void *data) {
|
||||
command *p;
|
||||
|
||||
if (!func) {
|
||||
func = default_alloc;
|
||||
}
|
||||
/* allocator is not set up yet, use func directly */
|
||||
auto *statep = static_cast<internal_state *>(
|
||||
func(data, nullptr, 0, sizeof(internal_state))
|
||||
);
|
||||
/* allocator will be set up in the constructor */
|
||||
new (statep) internal_state{func, data};
|
||||
|
||||
try {
|
||||
p_tstate = statep->create<thread_state>(statep);
|
||||
} catch (...) {
|
||||
statep->destroy(statep);
|
||||
throw;
|
||||
}
|
||||
|
||||
p_tstate->pstate = this;
|
||||
p_tstate->istate = statep;
|
||||
p_tstate->owner = true;
|
||||
|
||||
for (std::size_t i = 0; i < MAX_ARGUMENTS; ++i) {
|
||||
char buf[16];
|
||||
snprintf(buf, sizeof(buf), "arg%zu", i + 1);
|
||||
statep->new_ident(
|
||||
*this, static_cast<char const *>(buf), IDENT_FLAG_ARG
|
||||
);
|
||||
}
|
||||
|
||||
statep->id_dummy = &statep->new_ident(*this, "//dummy", IDENT_FLAG_UNKNOWN);
|
||||
|
||||
statep->ivar_numargs = &new_var("numargs", 0, true);
|
||||
statep->ivar_dbgalias = &new_var("dbgalias", 4);
|
||||
|
||||
/* default handlers for variables */
|
||||
|
||||
statep->cmd_ivar = &new_command("//ivar_builtin", "$i#", [](
|
||||
auto &cs, auto args, auto &
|
||||
) {
|
||||
auto &iv = static_cast<builtin_var &>(args[0].get_ident(cs));
|
||||
if (args[2].get_integer() <= 1) {
|
||||
std::printf("%s = ", iv.name().data());
|
||||
std::printf(INTEGER_FORMAT, iv.value().get_integer());
|
||||
std::printf("\n");
|
||||
} else {
|
||||
iv.set_value(cs, args[1]);
|
||||
}
|
||||
});
|
||||
|
||||
statep->cmd_fvar = &new_command("//fvar_builtin", "$f#", [](
|
||||
auto &cs, auto args, auto &
|
||||
) {
|
||||
auto &fv = static_cast<builtin_var &>(args[0].get_ident(cs));
|
||||
if (args[2].get_integer() <= 1) {
|
||||
auto val = fv.value().get_float();
|
||||
std::printf("%s = ", fv.name().data());
|
||||
if (std::floor(val) == val) {
|
||||
std::printf(ROUND_FLOAT_FORMAT, val);
|
||||
} else {
|
||||
std::printf(FLOAT_FORMAT, val);
|
||||
}
|
||||
std::printf("\n");
|
||||
} else {
|
||||
fv.set_value(cs, args[1]);
|
||||
}
|
||||
});
|
||||
|
||||
statep->cmd_svar = &new_command("//svar_builtin", "$s#", [](
|
||||
auto &cs, auto args, auto &
|
||||
) {
|
||||
auto &sv = static_cast<builtin_var &>(args[0].get_ident(cs));
|
||||
if (args[2].get_integer() <= 1) {
|
||||
auto val = sv.value().get_string(cs);
|
||||
if (val.view().find('"') == std::string_view::npos) {
|
||||
std::printf("%s = \"%s\"\n", sv.name().data(), val.data());
|
||||
} else {
|
||||
std::printf("%s = [%s]\n", sv.name().data(), val.data());
|
||||
}
|
||||
} else {
|
||||
sv.set_value(cs, args[1]);
|
||||
}
|
||||
});
|
||||
|
||||
statep->cmd_var_changed = nullptr;
|
||||
|
||||
/* builtins */
|
||||
|
||||
p = &new_command("do", "b", [](auto &cs, auto args, auto &res) {
|
||||
res = args[0].get_code().call(cs);
|
||||
});
|
||||
static_cast<command_impl *>(p)->p_type = ID_DO;
|
||||
|
||||
p = &new_command("doargs", "b", [](auto &cs, auto args, auto &res) {
|
||||
res = exec_code_with_args(*cs.p_tstate, args[0].get_code());
|
||||
});
|
||||
static_cast<command_impl *>(p)->p_type = ID_DOARGS;
|
||||
|
||||
p = &new_command("if", "abb", [](auto &cs, auto args, auto &res) {
|
||||
res = (args[0].get_bool() ? args[1] : args[2]).get_code().call(cs);
|
||||
});
|
||||
static_cast<command_impl *>(p)->p_type = ID_IF;
|
||||
|
||||
p = &new_command("result", "a", [](auto &, auto args, auto &res) {
|
||||
res = std::move(args[0]);
|
||||
});
|
||||
static_cast<command_impl *>(p)->p_type = ID_RESULT;
|
||||
|
||||
p = &new_command("!", "a", [](auto &, auto args, auto &res) {
|
||||
res.set_integer(!args[0].get_bool());
|
||||
});
|
||||
static_cast<command_impl *>(p)->p_type = ID_NOT;
|
||||
|
||||
p = &new_command("&&", "c1...", [](auto &cs, auto args, auto &res) {
|
||||
if (args.empty()) {
|
||||
res.set_integer(1);
|
||||
} else {
|
||||
for (size_t i = 0; i < args.size(); ++i) {
|
||||
auto code = args[i].get_code();
|
||||
if (code) {
|
||||
res = code.call(cs);
|
||||
} else {
|
||||
res = std::move(args[i]);
|
||||
}
|
||||
if (!res.get_bool()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
static_cast<command_impl *>(p)->p_type = ID_AND;
|
||||
|
||||
p = &new_command("||", "c1...", [](auto &cs, auto args, auto &res) {
|
||||
if (args.empty()) {
|
||||
res.set_integer(0);
|
||||
} else {
|
||||
for (size_t i = 0; i < args.size(); ++i) {
|
||||
auto code = args[i].get_code();
|
||||
if (code) {
|
||||
res = code.call(cs);
|
||||
} else {
|
||||
res = std::move(args[i]);
|
||||
}
|
||||
if (res.get_bool()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
static_cast<command_impl *>(p)->p_type = ID_OR;
|
||||
|
||||
p = &new_command("local", "", nullptr);
|
||||
static_cast<command_impl *>(p)->p_type = ID_LOCAL;
|
||||
|
||||
p = &new_command("break", "", [](auto &cs, auto, auto &) {
|
||||
if (cs.p_tstate->loop_level) {
|
||||
throw break_exception{};
|
||||
} else {
|
||||
throw error{cs, "no loop to break"};
|
||||
}
|
||||
});
|
||||
static_cast<command_impl *>(p)->p_type = ID_BREAK;
|
||||
|
||||
p = &new_command("continue", "", [](auto &cs, auto, auto &) {
|
||||
if (cs.p_tstate->loop_level) {
|
||||
throw continue_exception{};
|
||||
} else {
|
||||
throw error{cs, "no loop to continue"};
|
||||
}
|
||||
});
|
||||
static_cast<command_impl *>(p)->p_type = ID_CONTINUE;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT state::~state() {
|
||||
if (!p_tstate || !p_tstate->owner) {
|
||||
return;
|
||||
}
|
||||
auto *sp = p_tstate->istate;
|
||||
sp->destroy(p_tstate);
|
||||
sp->destroy(sp);
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT state::state(state &&s) {
|
||||
swap(s);
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT state &state::operator=(state &&s) {
|
||||
if (p_tstate && p_tstate->owner) {
|
||||
auto *sp = p_tstate->istate;
|
||||
sp->destroy(p_tstate);
|
||||
sp->destroy(sp);
|
||||
}
|
||||
p_tstate = s.p_tstate;
|
||||
s.p_tstate = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT void state::swap(state &s) {
|
||||
std::swap(p_tstate, s.p_tstate);
|
||||
}
|
||||
|
||||
state::state(void *is) {
|
||||
auto *s = static_cast<internal_state *>(is);
|
||||
p_tstate = s->create<thread_state>(s);
|
||||
p_tstate->pstate = this;
|
||||
p_tstate->istate = s;
|
||||
p_tstate->owner = false;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT state state::new_thread() {
|
||||
return state{p_tstate->istate};
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT hook_func state::call_hook(hook_func func) {
|
||||
return p_tstate->set_hook(std::move(func));
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT hook_func const &state::call_hook() const {
|
||||
return p_tstate->get_hook();
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT hook_func &state::call_hook() {
|
||||
return p_tstate->get_hook();
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT void *state::alloc(void *ptr, size_t os, size_t ns) {
|
||||
return p_tstate->istate->alloc(ptr, os, ns);
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT std::size_t state::ident_count() const {
|
||||
return p_tstate->istate->identmap.size();
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT std::optional<
|
||||
std::reference_wrapper<ident>
|
||||
> state::get_ident(std::string_view name) {
|
||||
auto *id = p_tstate->istate->get_ident(name);
|
||||
if (!id) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return *id;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT std::optional<
|
||||
std::reference_wrapper<ident const>
|
||||
> state::get_ident(std::string_view name) const {
|
||||
auto *id = p_tstate->istate->get_ident(name);
|
||||
if (!id) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return *id;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT ident &state::get_ident(std::size_t index) {
|
||||
return *p_tstate->istate->identmap[index];
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT ident const &state::get_ident(std::size_t index) const {
|
||||
return *p_tstate->istate->identmap[index];
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT void state::clear_override(ident &id) {
|
||||
if (!id.is_overridden(*this)) {
|
||||
return;
|
||||
}
|
||||
switch (id.type()) {
|
||||
case ident_type::ALIAS: {
|
||||
auto &ast = p_tstate->get_astack(static_cast<alias *>(&id));
|
||||
ast.node->val_s.set_string("", *this);
|
||||
ast.node->code = bcode_ref{};
|
||||
ast.flags &= ~IDENT_FLAG_OVERRIDDEN;
|
||||
return;
|
||||
}
|
||||
case ident_type::VAR: {
|
||||
auto &v = static_cast<var_impl &>(id);
|
||||
any_value oldv = v.value();
|
||||
v.p_storage = std::move(v.p_override);
|
||||
var_changed(*p_tstate, v, oldv);
|
||||
static_cast<var_impl *>(
|
||||
static_cast<builtin_var *>(&v)
|
||||
)->p_flags &= ~IDENT_FLAG_OVERRIDDEN;
|
||||
return;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT void state::clear_overrides() {
|
||||
for (auto &p: p_tstate->istate->idents) {
|
||||
clear_override(*(p.second));
|
||||
}
|
||||
}
|
||||
|
||||
inline int var_flags(bool read_only, var_type vtp) {
|
||||
int ret = 0;
|
||||
if (read_only) {
|
||||
ret |= IDENT_FLAG_READONLY;
|
||||
}
|
||||
switch (vtp) {
|
||||
case var_type::PERSISTENT:
|
||||
ret |= IDENT_FLAG_PERSIST;
|
||||
break;
|
||||
case var_type::OVERRIDABLE:
|
||||
ret |= IDENT_FLAG_OVERRIDE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void var_name_check(
|
||||
state &cs, ident *id, std::string_view n
|
||||
) {
|
||||
if (id) {
|
||||
throw error_p::make(
|
||||
cs, "redefinition of ident '%.*s'", int(n.size()), n.data()
|
||||
);
|
||||
} else if (!is_valid_name(n)) {
|
||||
throw error_p::make(
|
||||
cs, "'%.*s' is not a valid variable name",
|
||||
int(n.size()), n.data()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT builtin_var &state::new_var(
|
||||
std::string_view n, integer_type v, bool read_only, var_type vtp
|
||||
) {
|
||||
auto *iv = p_tstate->istate->create<var_impl>(
|
||||
string_ref{*this, n},var_flags(read_only, vtp)
|
||||
);
|
||||
iv->p_storage.set_integer(v);
|
||||
try {
|
||||
var_name_check(*this, p_tstate->istate->get_ident(n), n);
|
||||
} catch (...) {
|
||||
p_tstate->istate->destroy(iv);
|
||||
throw;
|
||||
}
|
||||
p_tstate->istate->add_ident(iv, iv);
|
||||
return *iv;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT builtin_var &state::new_var(
|
||||
std::string_view n, float_type v, bool read_only, var_type vtp
|
||||
) {
|
||||
auto *fv = p_tstate->istate->create<var_impl>(
|
||||
string_ref{*this, n}, var_flags(read_only, vtp)
|
||||
);
|
||||
fv->p_storage.set_float(v);
|
||||
try {
|
||||
var_name_check(*this, p_tstate->istate->get_ident(n), n);
|
||||
} catch (...) {
|
||||
p_tstate->istate->destroy(fv);
|
||||
throw;
|
||||
}
|
||||
p_tstate->istate->add_ident(fv, fv);
|
||||
return *fv;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT builtin_var &state::new_var(
|
||||
std::string_view n, std::string_view v, bool read_only, var_type vtp
|
||||
) {
|
||||
auto *sv = p_tstate->istate->create<var_impl>(
|
||||
string_ref{*this, n}, var_flags(read_only, vtp)
|
||||
);
|
||||
sv->p_storage.set_string(v, *this);
|
||||
try {
|
||||
var_name_check(*this, p_tstate->istate->get_ident(n), n);
|
||||
} catch (...) {
|
||||
p_tstate->istate->destroy(sv);
|
||||
throw;
|
||||
}
|
||||
p_tstate->istate->add_ident(sv, sv);
|
||||
return *sv;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT ident &state::new_ident(std::string_view n) {
|
||||
return p_tstate->istate->new_ident(*this, n, IDENT_FLAG_UNKNOWN);
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT void state::assign_value(
|
||||
std::string_view name, any_value v
|
||||
) {
|
||||
auto id = get_ident(name);
|
||||
if (id) {
|
||||
switch (id->get().type()) {
|
||||
case ident_type::ALIAS: {
|
||||
static_cast<alias &>(id->get()).set_value(*this, std::move(v));
|
||||
return;
|
||||
}
|
||||
case ident_type::VAR:
|
||||
id->get().call(span_type<any_value>{&v, 1}, *this);
|
||||
break;
|
||||
default:
|
||||
throw error_p::make(
|
||||
*this, "cannot redefine builtin %s with an alias",
|
||||
id->get().name().data()
|
||||
);
|
||||
}
|
||||
} else if (!is_valid_name(name)) {
|
||||
throw error_p::make(
|
||||
*this, "cannot alias invalid name '%s'", name.data()
|
||||
);
|
||||
} else {
|
||||
auto *a = p_tstate->istate->create<alias_impl>(
|
||||
*this, string_ref{*this, name}, std::move(v),
|
||||
p_tstate->ident_flags
|
||||
);
|
||||
p_tstate->istate->add_ident(a, a);
|
||||
}
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT any_value state::lookup_value(std::string_view name) {
|
||||
ident *id = nullptr;
|
||||
auto idopt = get_ident(name);
|
||||
if (!idopt) {
|
||||
id = nullptr;
|
||||
} else {
|
||||
id = &idopt->get();
|
||||
}
|
||||
alias_stack *ast;
|
||||
if (id) {
|
||||
switch(id->type()) {
|
||||
case ident_type::ALIAS: {
|
||||
auto *a = static_cast<alias_impl *>(id);
|
||||
ast = &p_tstate->get_astack(static_cast<alias *>(id));
|
||||
if (ast->flags & IDENT_FLAG_UNKNOWN) {
|
||||
break;
|
||||
}
|
||||
if (a->is_arg() && !ident_is_used_arg(id, *p_tstate)) {
|
||||
return any_value{};
|
||||
}
|
||||
return ast->node->val_s.get_plain();
|
||||
}
|
||||
case ident_type::VAR:
|
||||
return static_cast<builtin_var *>(id)->value();
|
||||
case ident_type::COMMAND: {
|
||||
any_value val{};
|
||||
auto *cimpl = static_cast<command_impl *>(id);
|
||||
auto &args = p_tstate->vmstack;
|
||||
auto osz = args.size();
|
||||
/* pad with as many empty values as we need */
|
||||
args.resize(osz + cimpl->arg_count());
|
||||
try {
|
||||
exec_command(
|
||||
*p_tstate, cimpl, cimpl, &args[osz], val, 0, true
|
||||
);
|
||||
} catch (...) {
|
||||
args.resize(osz);
|
||||
throw;
|
||||
}
|
||||
args.resize(osz);
|
||||
return val;
|
||||
}
|
||||
default:
|
||||
return any_value{};
|
||||
}
|
||||
}
|
||||
throw error_p::make(*this, "unknown alias lookup: %s", name.data());
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT void state::reset_value(std::string_view name) {
|
||||
auto id = get_ident(name);
|
||||
if (!id) {
|
||||
throw error_p::make(
|
||||
*this, "variable '%s' does not exist", name.data()
|
||||
);
|
||||
}
|
||||
if (id->get().type() == ident_type::VAR) {
|
||||
if (static_cast<builtin_var &>(id->get()).is_read_only()) {
|
||||
throw error_p::make(
|
||||
*this, "variable '%s' is read only", name.data()
|
||||
);
|
||||
}
|
||||
}
|
||||
clear_override(id->get());
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT void state::touch_value(std::string_view name) {
|
||||
auto id = get_ident(name);
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
auto &idr = id->get();
|
||||
if (idr.type() != ident_type::VAR) {
|
||||
return;
|
||||
}
|
||||
auto &v = static_cast<builtin_var &>(idr);
|
||||
auto vv = v.value();
|
||||
var_changed(*p_tstate, v, vv);
|
||||
}
|
||||
|
||||
static char const *allowed_builtins[] = {
|
||||
"//ivar", "//fvar", "//svar", "//var_changed",
|
||||
"//ivar_builtin", "//fvar_builtin", "//svar_builtin",
|
||||
nullptr
|
||||
};
|
||||
|
||||
LIBCUBESCRIPT_EXPORT command &state::new_command(
|
||||
std::string_view name, std::string_view args, command_func func
|
||||
) {
|
||||
int nargs = 0;
|
||||
for (auto fmt = args.begin(); fmt != args.end(); ++fmt) {
|
||||
switch (*fmt) {
|
||||
case 'i':
|
||||
case 'f':
|
||||
case 'a':
|
||||
case 'c':
|
||||
case '#':
|
||||
case 's':
|
||||
case 'b':
|
||||
case 'v':
|
||||
case '$':
|
||||
++nargs;
|
||||
break;
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4': {
|
||||
int nrep = (*fmt - '0');
|
||||
if (nargs < nrep) {
|
||||
throw error{
|
||||
*this, "not enough arguments to repeat"
|
||||
};
|
||||
}
|
||||
if ((args.end() - fmt) != 4) {
|
||||
throw error{
|
||||
*this, "malformed argument list"
|
||||
};
|
||||
}
|
||||
if (fmt[1] != '.') {
|
||||
throw error{
|
||||
*this, "repetition without variadic arguments"
|
||||
};
|
||||
}
|
||||
nargs -= nrep;
|
||||
break;
|
||||
}
|
||||
case '.':
|
||||
if (
|
||||
((fmt + 3) != args.end()) ||
|
||||
std::memcmp(&fmt[0], "...", 3)
|
||||
) {
|
||||
throw error{
|
||||
*this, "unterminated variadic argument list"
|
||||
};
|
||||
}
|
||||
fmt += 2;
|
||||
break;
|
||||
default:
|
||||
throw error_p::make(
|
||||
*this, "invalid argument type: %c", *fmt
|
||||
);
|
||||
}
|
||||
}
|
||||
auto &is = *p_tstate->istate;
|
||||
auto *cmd = is.create<command_impl>(
|
||||
string_ref{*this, name}, string_ref{*this, args},
|
||||
nargs, std::move(func)
|
||||
);
|
||||
/* we can set these builtins */
|
||||
command **bptrs[] = {
|
||||
&is.cmd_ivar, &is.cmd_fvar, &is.cmd_svar, &is.cmd_var_changed
|
||||
};
|
||||
auto nbptrs = sizeof(bptrs) / sizeof(*bptrs);
|
||||
/* provided a builtin */
|
||||
if ((name.size() >= 2) && (name[0] == '/') && (name[1] == '/')) {
|
||||
/* sanitize */
|
||||
for (auto **p = allowed_builtins; *p; ++p) {
|
||||
if (!name.compare(*p)) {
|
||||
/* if it's one of the settable ones, maybe set it */
|
||||
if (std::size_t(p - allowed_builtins) < nbptrs) {
|
||||
if (!is.get_ident(name)) {
|
||||
/* only set if it does not exist already */
|
||||
*bptrs[p - allowed_builtins] = cmd;
|
||||
goto do_add;
|
||||
}
|
||||
}
|
||||
/* this will ensure we're not redefining them */
|
||||
goto valid;
|
||||
}
|
||||
}
|
||||
/* we haven't found one matching the list, so error */
|
||||
is.destroy(cmd);
|
||||
throw error_p::make(
|
||||
*this, "forbidden builtin command: %.*s",
|
||||
int(name.size()), name.data()
|
||||
);
|
||||
}
|
||||
valid:
|
||||
if (is.get_ident(name)) {
|
||||
is.destroy(cmd);
|
||||
throw error_p::make(
|
||||
*this, "redefinition of ident '%.*s'",
|
||||
int(name.size()), name.data()
|
||||
);
|
||||
}
|
||||
do_add:
|
||||
is.add_ident(cmd, cmd);
|
||||
return *cmd;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT bcode_ref state::compile(
|
||||
std::string_view v, std::string_view source
|
||||
) {
|
||||
gen_state gs{*p_tstate};
|
||||
gs.gen_main(v, source);
|
||||
return gs.steal_ref();
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT bool state::override_mode() const {
|
||||
return (p_tstate->ident_flags & IDENT_FLAG_OVERRIDDEN);
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT bool state::override_mode(bool v) {
|
||||
bool was = override_mode();
|
||||
if (v) {
|
||||
p_tstate->ident_flags |= IDENT_FLAG_OVERRIDDEN;
|
||||
} else {
|
||||
p_tstate->ident_flags &= ~IDENT_FLAG_OVERRIDDEN;
|
||||
}
|
||||
return was;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT bool state::persist_mode() const {
|
||||
return (p_tstate->ident_flags & IDENT_FLAG_PERSIST);
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT bool state::persist_mode(bool v) {
|
||||
bool was = persist_mode();
|
||||
if (v) {
|
||||
p_tstate->ident_flags |= IDENT_FLAG_PERSIST;
|
||||
} else {
|
||||
p_tstate->ident_flags &= ~IDENT_FLAG_PERSIST;
|
||||
}
|
||||
return was;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT std::size_t state::max_call_depth() const {
|
||||
return p_tstate->max_call_depth;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT std::size_t state::max_call_depth(std::size_t v) {
|
||||
auto old = p_tstate->max_call_depth;
|
||||
p_tstate->max_call_depth = v;
|
||||
return old;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT void std_init_all(state &cs) {
|
||||
std_init_base(cs);
|
||||
std_init_math(cs);
|
||||
std_init_string(cs);
|
||||
std_init_list(cs);
|
||||
}
|
||||
|
||||
} /* namespace cubescript */
|
141
src/cs_state.hh
141
src/cs_state.hh
|
@ -1,141 +0,0 @@
|
|||
#ifndef LIBCUBESCRIPT_STATE_HH
|
||||
#define LIBCUBESCRIPT_STATE_HH
|
||||
|
||||
#include <cubescript/cubescript.hh>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cs_bcode.hh"
|
||||
#include "cs_ident.hh"
|
||||
|
||||
namespace cubescript {
|
||||
|
||||
struct internal_state;
|
||||
struct string_pool;
|
||||
|
||||
template<typename T>
|
||||
struct std_allocator {
|
||||
using value_type = T;
|
||||
|
||||
inline std_allocator(internal_state *s);
|
||||
|
||||
template<typename U>
|
||||
std_allocator(std_allocator<U> const &a): istate{a.istate} {}
|
||||
|
||||
inline T *allocate(std::size_t n);
|
||||
inline void deallocate(T *p, std::size_t n);
|
||||
|
||||
template<typename U>
|
||||
bool operator==(std_allocator<U> const &a) {
|
||||
return istate == a.istate;
|
||||
}
|
||||
|
||||
internal_state *istate;
|
||||
};
|
||||
|
||||
struct internal_state {
|
||||
using allocator_type = std_allocator<
|
||||
std::pair<std::string_view const, ident *>
|
||||
>;
|
||||
alloc_func allocf;
|
||||
void *aptr;
|
||||
|
||||
std::unordered_map<
|
||||
std::string_view, ident *,
|
||||
std::hash<std::string_view>,
|
||||
std::equal_to<std::string_view>,
|
||||
allocator_type
|
||||
> idents;
|
||||
std::vector<ident *, std_allocator<ident *>> identmap;
|
||||
|
||||
string_pool *strman;
|
||||
empty_block *empty;
|
||||
|
||||
ident *id_dummy;
|
||||
|
||||
builtin_var *ivar_numargs;
|
||||
builtin_var *ivar_dbgalias;
|
||||
|
||||
command *cmd_ivar;
|
||||
command *cmd_fvar;
|
||||
command *cmd_svar;
|
||||
command *cmd_var_changed;
|
||||
|
||||
internal_state() = delete;
|
||||
|
||||
internal_state(alloc_func af, void *data);
|
||||
|
||||
~internal_state();
|
||||
|
||||
ident *add_ident(ident *id, ident_impl *impl);
|
||||
ident &new_ident(state &cs, std::string_view name, int flags);
|
||||
ident *get_ident(std::string_view name) const;
|
||||
|
||||
void *alloc(void *ptr, size_t os, size_t ns);
|
||||
|
||||
template<typename T, typename ...A>
|
||||
T *create(A &&...args) {
|
||||
T *ret = static_cast<T *>(alloc(nullptr, 0, sizeof(T)));
|
||||
new (ret) T{std::forward<A>(args)...};
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<typename T, typename ...A>
|
||||
T *create_array(size_t len, A &&...args) {
|
||||
T *ret = static_cast<T *>(alloc(nullptr, 0, len * sizeof(T)));
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
new (&ret[i]) T{std::forward<A>(args)...};
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void destroy(T *v) noexcept {
|
||||
v->~T();
|
||||
alloc(v, sizeof(T), 0);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void destroy_array(T *v, size_t len) noexcept {
|
||||
v->~T();
|
||||
alloc(v, len * sizeof(T), 0);
|
||||
}
|
||||
};
|
||||
|
||||
struct state_p {
|
||||
state_p(state &cs): csp{&cs} {}
|
||||
|
||||
thread_state &ts() { return *csp->p_tstate; }
|
||||
|
||||
state *csp;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
inline std_allocator<T>::std_allocator(internal_state *s): istate{s} {}
|
||||
|
||||
template<typename T>
|
||||
inline T *std_allocator<T>::allocate(std::size_t n) {
|
||||
return static_cast<T *>(istate->alloc(nullptr, 0, n * sizeof(T)));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline void std_allocator<T>::deallocate(T *p, std::size_t n) {
|
||||
istate->alloc(p, n, 0);
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
inline void new_cmd_quiet(
|
||||
state &cs, std::string_view name, std::string_view args, F &&f
|
||||
) {
|
||||
try {
|
||||
cs.new_command(name, args, std::forward<F>(f));
|
||||
} catch (error const &) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace cubescript */
|
||||
|
||||
#endif
|
|
@ -1,10 +0,0 @@
|
|||
#include "cs_std.hh"
|
||||
|
||||
#include "cs_thread.hh"
|
||||
|
||||
namespace cubescript {
|
||||
|
||||
charbuf::charbuf(state &cs): charbuf{state_p{cs}.ts().istate} {}
|
||||
charbuf::charbuf(thread_state &ts): charbuf{ts.istate} {}
|
||||
|
||||
} /* namespace cubescript */
|
105
src/cs_std.hh
105
src/cs_std.hh
|
@ -1,105 +0,0 @@
|
|||
#ifndef LIBCUBESCRIPT_STD_HH
|
||||
#define LIBCUBESCRIPT_STD_HH
|
||||
|
||||
#include <cubescript/cubescript.hh>
|
||||
|
||||
#include <cstddef>
|
||||
#include <new>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <type_traits>
|
||||
#include <string_view>
|
||||
|
||||
#include "cs_state.hh"
|
||||
|
||||
namespace cubescript {
|
||||
|
||||
/* a value buffer */
|
||||
|
||||
template<typename T>
|
||||
struct valbuf {
|
||||
valbuf() = delete;
|
||||
|
||||
valbuf(internal_state *cs): buf{std_allocator<T>{cs}} {}
|
||||
|
||||
using size_type = std::size_t;
|
||||
using value_type = T;
|
||||
using reference = T &;
|
||||
using const_reference = T const &;
|
||||
|
||||
void reserve(std::size_t s) { buf.reserve(s); }
|
||||
void resize(std::size_t s) { buf.resize(s); }
|
||||
|
||||
void resize(std::size_t s, value_type const &v) {
|
||||
buf.resize(s, v);
|
||||
}
|
||||
|
||||
void append(T const *beg, T const *end) {
|
||||
buf.insert(buf.end(), beg, end);
|
||||
}
|
||||
|
||||
void insert(std::size_t i, T const &it) {
|
||||
buf.insert(buf.begin() + i, it);
|
||||
}
|
||||
|
||||
template<typename ...A>
|
||||
reference emplace_back(A &&...args) {
|
||||
return buf.emplace_back(std::forward<A>(args)...);
|
||||
}
|
||||
|
||||
void push_back(T const &v) { buf.push_back(v); }
|
||||
void pop_back() { buf.pop_back(); }
|
||||
|
||||
T &back() { return buf.back(); }
|
||||
T const &back() const { return buf.back(); }
|
||||
|
||||
std::size_t size() const { return buf.size(); }
|
||||
std::size_t capacity() const { return buf.capacity(); }
|
||||
|
||||
bool empty() const { return buf.empty(); }
|
||||
|
||||
void clear() { buf.clear(); }
|
||||
|
||||
T &operator[](std::size_t i) { return buf[i]; }
|
||||
T const &operator[](std::size_t i) const { return buf[i]; }
|
||||
|
||||
T *data() { return &buf[0]; }
|
||||
T const *data() const { return &buf[0]; }
|
||||
|
||||
std::vector<T, std_allocator<T>> buf;
|
||||
};
|
||||
|
||||
/* specialization of value buffer for bytes */
|
||||
|
||||
struct charbuf: valbuf<char> {
|
||||
charbuf(internal_state *cs): valbuf<char>{cs} {}
|
||||
charbuf(state &cs);
|
||||
charbuf(thread_state &ts);
|
||||
|
||||
void append(char const *beg, char const *end) {
|
||||
valbuf<char>::append(beg, end);
|
||||
}
|
||||
|
||||
void append(std::string_view v) {
|
||||
append(&v[0], &v[v.size()]);
|
||||
}
|
||||
|
||||
std::string_view str() {
|
||||
return std::string_view{buf.data(), buf.size()};
|
||||
}
|
||||
|
||||
std::string_view str_term() {
|
||||
return std::string_view{buf.data(), buf.size() - 1};
|
||||
}
|
||||
};
|
||||
|
||||
/* because the dual-iterator constructor is not supported everywhere
|
||||
* and the pointer + size constructor is ugly as heck
|
||||
*/
|
||||
inline std::string_view make_str_view(char const *a, char const *b) {
|
||||
return std::string_view{a, std::size_t(b - a)};
|
||||
}
|
||||
|
||||
} /* namespace cubescript */
|
||||
|
||||
#endif
|
182
src/cs_strman.cc
182
src/cs_strman.cc
|
@ -1,182 +0,0 @@
|
|||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <cubescript/cubescript.hh>
|
||||
|
||||
#include "cs_strman.hh"
|
||||
#include "cs_thread.hh"
|
||||
|
||||
namespace cubescript {
|
||||
|
||||
struct string_ref_state {
|
||||
internal_state *state;
|
||||
std::size_t length;
|
||||
std::size_t refcount;
|
||||
};
|
||||
|
||||
inline string_ref_state *get_ref_state(char const *ptr) {
|
||||
string_ref_state *r;
|
||||
std::memcpy(&r, &ptr, sizeof(r));
|
||||
return r - 1;
|
||||
}
|
||||
|
||||
char const *string_pool::add(std::string_view str) {
|
||||
auto it = counts.find(str);
|
||||
/* already present: just increment ref */
|
||||
if (it != counts.end()) {
|
||||
auto *st = it->second;
|
||||
/* having a null pointer is the same as non-existence */
|
||||
if (st) {
|
||||
++st->refcount;
|
||||
st += 1;
|
||||
char const *r;
|
||||
std::memcpy(&r, &st, sizeof(r));
|
||||
return r;
|
||||
}
|
||||
}
|
||||
/* not present: allocate brand new data */
|
||||
auto ss = str.size();
|
||||
auto strp = alloc_buf(ss);
|
||||
/* write string data, it's already pre-terminated */
|
||||
memcpy(strp, str.data(), ss);
|
||||
/* store it */
|
||||
counts.emplace(std::string_view{strp, ss}, get_ref_state(strp));
|
||||
return strp;
|
||||
}
|
||||
|
||||
char const *string_pool::ref(char const *ptr) {
|
||||
auto *ss = get_ref_state(ptr);
|
||||
++ss->refcount;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
string_ref string_pool::steal(char *ptr) {
|
||||
auto *ss = get_ref_state(ptr);
|
||||
auto sr = std::string_view{ptr, ss->length};
|
||||
/* much like add(), but we already have memory */
|
||||
auto it = counts.find(sr);
|
||||
if (it != counts.end()) {
|
||||
auto *st = it->second;
|
||||
if (st) {
|
||||
/* the buffer is superfluous now */
|
||||
cstate->alloc(ss, ss->length + sizeof(string_ref_state) + 1, 0);
|
||||
st += 1;
|
||||
char const *rp;
|
||||
std::memcpy(&rp, &st, sizeof(rp));
|
||||
return string_ref{rp};
|
||||
}
|
||||
}
|
||||
ss->refcount = 0; /* string_ref will increment it */
|
||||
counts.emplace(sr, ss);
|
||||
return string_ref{ptr};
|
||||
}
|
||||
|
||||
void string_pool::unref(char const *ptr) {
|
||||
auto *ss = get_ref_state(ptr);
|
||||
if (!--ss->refcount) {
|
||||
/* refcount zero, so ditch it
|
||||
* this path is a little slow...
|
||||
*/
|
||||
auto sr = std::string_view{ptr, ss->length};
|
||||
auto it = counts.find(sr);
|
||||
/* this should *never* happen unless we have a bug */
|
||||
#ifndef NDEBUG
|
||||
assert(it != counts.end());
|
||||
#else
|
||||
if (it == counts.end()) {
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
/* we're freeing the key */
|
||||
counts.erase(it);
|
||||
/* dealloc */
|
||||
cstate->alloc(ss, ss->length + sizeof(string_ref_state) + 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
char const *string_pool::find(std::string_view str) const {
|
||||
auto it = counts.find(str);
|
||||
if (it == counts.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto *sp = it->second + 1;
|
||||
char const *rp;
|
||||
std::memcpy(&rp, &sp, sizeof(rp));
|
||||
return rp;
|
||||
}
|
||||
|
||||
std::string_view string_pool::get(char const *ptr) const {
|
||||
auto *ss = get_ref_state(ptr);
|
||||
return std::string_view{ptr, ss->length};
|
||||
}
|
||||
|
||||
char *string_pool::alloc_buf(std::size_t len) const {
|
||||
auto mem = cstate->alloc(nullptr, 0, len + sizeof(string_ref_state) + 1);
|
||||
/* write length and initial refcount */
|
||||
auto *sst = static_cast<string_ref_state *>(mem);
|
||||
sst->state = cstate;
|
||||
sst->length = len;
|
||||
sst->refcount = 1;
|
||||
/* pre-terminate */
|
||||
char *strp;
|
||||
sst += 1;
|
||||
std::memcpy(&strp, &sst, sizeof(strp));
|
||||
strp[len] = '\0';
|
||||
/* now the user can fill it */
|
||||
return strp;
|
||||
}
|
||||
|
||||
char const *str_managed_ref(char const *str) {
|
||||
return get_ref_state(str)->state->strman->ref(str);
|
||||
}
|
||||
|
||||
void str_managed_unref(char const *str) {
|
||||
get_ref_state(str)->state->strman->unref(str);
|
||||
}
|
||||
|
||||
std::string_view str_managed_view(char const *str) {
|
||||
return get_ref_state(str)->state->strman->get(str);
|
||||
}
|
||||
|
||||
/* strref implementation */
|
||||
|
||||
LIBCUBESCRIPT_EXPORT string_ref::string_ref(state &cs, std::string_view str) {
|
||||
p_str = state_p{cs}.ts().istate->strman->add(str);
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT string_ref::string_ref(string_ref const &ref):
|
||||
p_str{ref.p_str}
|
||||
{
|
||||
get_ref_state(p_str)->state->strman->ref(p_str);
|
||||
}
|
||||
|
||||
/* this can be used by friends to do quick string_ref creation */
|
||||
LIBCUBESCRIPT_EXPORT string_ref::string_ref(char const *p) {
|
||||
p_str = str_managed_ref(p);
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT string_ref::~string_ref() {
|
||||
str_managed_unref(p_str);
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT string_ref &string_ref::operator=(string_ref const &ref) {
|
||||
p_str = str_managed_ref(ref.p_str);
|
||||
return *this;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT char const *string_ref::data() const {
|
||||
return p_str;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT string_ref::operator std::string_view() const {
|
||||
return str_managed_view(p_str);
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT bool string_ref::operator==(string_ref const &s) const {
|
||||
return p_str == s.p_str;
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT bool string_ref::operator!=(string_ref const &s) const {
|
||||
return p_str != s.p_str;
|
||||
}
|
||||
|
||||
} /* namespace cubescript */
|
|
@ -1,96 +0,0 @@
|
|||
#ifndef LIBCUBESCRIPT_STRMAN_HH
|
||||
#define LIBCUBESCRIPT_STRMAN_HH
|
||||
|
||||
#include <cubescript/cubescript.hh>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <string_view>
|
||||
|
||||
#include "cs_std.hh"
|
||||
#include "cs_state.hh"
|
||||
|
||||
namespace cubescript {
|
||||
|
||||
struct string_ref_state;
|
||||
|
||||
char const *str_managed_ref(char const *str);
|
||||
void str_managed_unref(char const *str);
|
||||
std::string_view str_managed_view(char const *str);
|
||||
|
||||
/* string manager
|
||||
*
|
||||
* the purpose of this is to handle interning of strings; each string within
|
||||
* a libcs state is represented (and allocated) exactly once, and reference
|
||||
* counted; that both helps save resources, and potentially provide a means
|
||||
* to reliably represent returned strings in places that is compatible with
|
||||
* multiple threads and eliminate the chance of dangling pointers
|
||||
*
|
||||
* strings are allocated in a manner where the refcount and length are stored
|
||||
* as a part of the string's memory, so it can be easily accessed using just
|
||||
* the pointer to the string, but also this is transparent for usage
|
||||
*
|
||||
* this is not thread-safe yet, and later on it should be made that,
|
||||
* for now we don't bother...
|
||||
*/
|
||||
|
||||
struct string_pool {
|
||||
using allocator_type = std_allocator<
|
||||
std::pair<std::string_view const, string_ref_state *>
|
||||
>;
|
||||
string_pool() = delete;
|
||||
string_pool(internal_state *cs): cstate{cs}, counts{allocator_type{cs}} {}
|
||||
~string_pool() {}
|
||||
|
||||
string_pool(string_pool const &) = delete;
|
||||
string_pool(string_pool &&) = delete;
|
||||
|
||||
string_pool &operator=(string_pool const &) = delete;
|
||||
string_pool &operator=(string_pool &&) = delete;
|
||||
|
||||
/* adds a string into the manager using any source, and returns a managed
|
||||
* version; this is "slow" as it has to hash the string and potentially
|
||||
* allocate fresh memory for it, but is perfectly safe at any time
|
||||
*/
|
||||
char const *add(std::string_view str);
|
||||
|
||||
/* this simply increments the reference count of an existing managed
|
||||
* string, this is only safe when you know the pointer you are passing
|
||||
* is already managed the system
|
||||
*/
|
||||
char const *ref(char const *ptr);
|
||||
|
||||
/* this will use the provided memory, assuming it is a fresh string that
|
||||
* is yet to be added; the memory must be allocated with alloc_buf()
|
||||
*/
|
||||
string_ref steal(char *ptr);
|
||||
|
||||
/* decrements the reference count and removes it from the system if
|
||||
* that reaches zero; likewise, only safe with pointers that are managed
|
||||
*/
|
||||
void unref(char const *ptr);
|
||||
|
||||
/* just finds a managed pointer with the same contents
|
||||
* as the input, if not found then a null pointer is returned
|
||||
*/
|
||||
char const *find(std::string_view str) const;
|
||||
|
||||
/* a quick helper to make a proper string view out of a ptr */
|
||||
std::string_view get(char const *ptr) const;
|
||||
|
||||
/* this will allocate a buffer of the given length (plus one for
|
||||
* terminating zero) so you can fill it; use steal() to write it
|
||||
*/
|
||||
char *alloc_buf(std::size_t len) const;
|
||||
|
||||
internal_state *cstate;
|
||||
std::unordered_map<
|
||||
std::string_view, string_ref_state *,
|
||||
std::hash<std::string_view>,
|
||||
std::equal_to<std::string_view>,
|
||||
allocator_type
|
||||
> counts;
|
||||
};
|
||||
|
||||
} /* namespace cubescript */
|
||||
|
||||
#endif
|
|
@ -1,69 +0,0 @@
|
|||
#include "cs_thread.hh"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
namespace cubescript {
|
||||
|
||||
thread_state::thread_state(internal_state *cs):
|
||||
vmstack{cs}, idstack{cs}, callstack{cs}, astacks{cs}, errbuf{cs}
|
||||
{
|
||||
vmstack.reserve(32);
|
||||
idstack.reserve(MAX_ARGUMENTS);
|
||||
}
|
||||
|
||||
hook_func thread_state::set_hook(hook_func f) {
|
||||
auto hk = std::move(call_hook);
|
||||
call_hook = std::move(f);
|
||||
return hk;
|
||||
}
|
||||
|
||||
alias_stack &thread_state::get_astack(alias const *a) {
|
||||
auto it = astacks.try_emplace(a->index());
|
||||
if (it.second) {
|
||||
auto *imp = const_cast<alias_impl *>(
|
||||
static_cast<alias_impl const *>(a)
|
||||
);
|
||||
it.first->second.node = &imp->p_initial;
|
||||
it.first->second.flags = imp->p_flags;
|
||||
}
|
||||
return it.first->second;
|
||||
}
|
||||
|
||||
char *thread_state::request_errbuf(std::size_t bufs, char *&sp) {
|
||||
errbuf.clear();
|
||||
std::size_t sz = 0;
|
||||
if (current_line) {
|
||||
/* we can attach line number */
|
||||
sz = source.size() + 32;
|
||||
for (;;) {
|
||||
/* we are using so the buffer tracks the elements and therefore
|
||||
* does not wipe them when we attempt to reserve more capacity
|
||||
*/
|
||||
errbuf.resize(sz);
|
||||
int nsz;
|
||||
if (!source.empty()) {
|
||||
nsz = std::snprintf(
|
||||
errbuf.data(), sz, "%.*s:%zu: ",
|
||||
int(source.size()), source.data(),
|
||||
*current_line
|
||||
);
|
||||
} else {
|
||||
nsz = std::snprintf(
|
||||
errbuf.data(), sz, "%zu: ", *current_line
|
||||
);
|
||||
}
|
||||
if (nsz <= 0) {
|
||||
abort(); /* should be unreachable */
|
||||
} else if (std::size_t(nsz) < sz) {
|
||||
sz = std::size_t(nsz);
|
||||
break;
|
||||
}
|
||||
sz = std::size_t(nsz + 1);
|
||||
}
|
||||
}
|
||||
errbuf.resize(sz + bufs + 1);
|
||||
sp = errbuf.data();
|
||||
return &errbuf[sz];
|
||||
}
|
||||
|
||||
} /* namespace cubescript */
|
|
@ -1,69 +0,0 @@
|
|||
#ifndef LIBCUBESCRIPT_THREAD_HH
|
||||
#define LIBCUBESCRIPT_THREAD_HH
|
||||
|
||||
#include <cubescript/cubescript.hh>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "cs_std.hh"
|
||||
#include "cs_state.hh"
|
||||
#include "cs_ident.hh"
|
||||
|
||||
namespace cubescript {
|
||||
|
||||
struct ident_level {
|
||||
ident &id;
|
||||
argset usedargs{};
|
||||
|
||||
ident_level(ident &i): id{i} {};
|
||||
};
|
||||
|
||||
struct thread_state {
|
||||
using astack_allocator = std_allocator<std::pair<int const, alias_stack>>;
|
||||
/* the shared state pointer */
|
||||
internal_state *istate{};
|
||||
/* the public state interface */
|
||||
state *pstate{};
|
||||
/* VM stack */
|
||||
valbuf<any_value> vmstack;
|
||||
/* ident stack */
|
||||
valbuf<ident_stack> idstack;
|
||||
/* call stack */
|
||||
valbuf<ident_level> callstack;
|
||||
/* per-alias stack pointer */
|
||||
std::unordered_map<
|
||||
int, alias_stack, std::hash<int>, std::equal_to<int>, astack_allocator
|
||||
> astacks;
|
||||
/* per-thread storage buffer for error messages */
|
||||
charbuf errbuf;
|
||||
/* we can attach a hook to vm events */
|
||||
hook_func call_hook{};
|
||||
/* whether we own the internal state (i.e. not a side thread */
|
||||
bool owner = false;
|
||||
/* thread ident flags */
|
||||
int ident_flags = 0;
|
||||
/* call depth limit */
|
||||
std::size_t max_call_depth = 1024;
|
||||
/* current call depth */
|
||||
std::size_t call_depth = 0;
|
||||
/* loop nesting level */
|
||||
std::size_t loop_level = 0;
|
||||
/* debug info */
|
||||
std::string_view source{};
|
||||
std::size_t *current_line = nullptr;
|
||||
|
||||
thread_state(internal_state *cs);
|
||||
|
||||
hook_func set_hook(hook_func f);
|
||||
|
||||
hook_func &get_hook() { return call_hook; }
|
||||
hook_func const &get_hook() const { return call_hook; }
|
||||
|
||||
alias_stack &get_astack(alias const *a);
|
||||
|
||||
char *request_errbuf(std::size_t bufs, char *&sp);
|
||||
};
|
||||
|
||||
} /* namespace cubescript */
|
||||
|
||||
#endif /* LIBCUBESCRIPT_THREAD_HH */
|
|
@ -0,0 +1,385 @@
|
|||
#include <cubescript/cubescript.hh>
|
||||
#include "cs_util.hh"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <math.h>
|
||||
|
||||
namespace cscript {
|
||||
|
||||
static inline void p_skip_white(ostd::string_range &v) {
|
||||
while (!v.empty() && isspace(*v)) {
|
||||
++v;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void p_set_end(
|
||||
const ostd::string_range &v, ostd::string_range *end
|
||||
) {
|
||||
if (!end) {
|
||||
return;
|
||||
}
|
||||
*end = v;
|
||||
}
|
||||
|
||||
/* this function assumes the input is definitely a hex digit */
|
||||
static inline cs_int p_hexd_to_int(char c) {
|
||||
if (c >= 97) { /* a-f */
|
||||
return (c - 'a') + 10;
|
||||
} else if (c >= 65) { /* A-F */
|
||||
return (c - 'A') + 10;
|
||||
}
|
||||
/* 0-9 */
|
||||
return c - '0';
|
||||
}
|
||||
|
||||
static inline bool p_check_neg(ostd::string_range &input) {
|
||||
bool neg = (*input == '-');
|
||||
if (neg || (*input == '+')) {
|
||||
++input;
|
||||
}
|
||||
return neg;
|
||||
}
|
||||
|
||||
cs_int cs_parse_int(ostd::string_range input, ostd::string_range *end) {
|
||||
ostd::string_range orig = input;
|
||||
p_skip_white(input);
|
||||
if (input.empty()) {
|
||||
p_set_end(orig, end);
|
||||
return cs_int(0);
|
||||
}
|
||||
bool neg = p_check_neg(input);
|
||||
cs_int ret = 0;
|
||||
ostd::string_range past = input;
|
||||
if (input.size() >= 2) {
|
||||
ostd::string_range pfx = input.slice(0, 2);
|
||||
if ((pfx == "0x") || (pfx == "0X")) {
|
||||
input = input.slice(2, input.size());
|
||||
past = input;
|
||||
while (!past.empty() && isxdigit(*past)) {
|
||||
ret = ret * 16 + p_hexd_to_int(*past);
|
||||
++past;
|
||||
}
|
||||
goto done;
|
||||
} else if ((pfx == "0b") || (pfx == "0B")) {
|
||||
input = input.slice(2, input.size());
|
||||
past = input;
|
||||
while (!past.empty() && ((*past == '0') || (*past == '1'))) {
|
||||
ret = ret * 2 + (*past - '0');
|
||||
++past;
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
while (!past.empty() && isdigit(*past)) {
|
||||
ret = ret * 10 + (*past - '0');
|
||||
++past;
|
||||
}
|
||||
done:
|
||||
if (&past[0] == &input[0]) {
|
||||
p_set_end(orig, end);
|
||||
} else {
|
||||
p_set_end(past, end);
|
||||
}
|
||||
if (neg) {
|
||||
return -ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<bool Hex, char e1 = Hex ? 'p' : 'e', char e2 = Hex ? 'P' : 'E'>
|
||||
static inline bool p_read_exp(ostd::string_range &input, cs_int &fn) {
|
||||
if (input.empty()) {
|
||||
return true;
|
||||
}
|
||||
if ((*input != e1) && (*input != e2)) {
|
||||
return true;
|
||||
}
|
||||
++input;
|
||||
if (input.empty()) {
|
||||
return false;
|
||||
}
|
||||
bool neg = p_check_neg(input);
|
||||
if (input.empty() || !isdigit(*input)) {
|
||||
return false;
|
||||
}
|
||||
cs_int exp = 0;
|
||||
while (!input.empty() && isdigit(*input)) {
|
||||
exp = exp * 10 + (*input - '0');
|
||||
++input;
|
||||
}
|
||||
if (neg) {
|
||||
exp = -exp;
|
||||
}
|
||||
fn += exp;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<bool Hex>
|
||||
static inline bool parse_gen_float(
|
||||
ostd::string_range input, ostd::string_range *end, cs_float &ret
|
||||
) {
|
||||
auto read_digits = [&input](double r, cs_int &n) {
|
||||
while (!input.empty() && (Hex ? isxdigit(*input) : isdigit(*input))) {
|
||||
if (Hex) {
|
||||
r = r * 16.0 + double(p_hexd_to_int(*input));
|
||||
} else {
|
||||
r = r * 10.0 + double(*input - '0');
|
||||
}
|
||||
++n;
|
||||
++input;
|
||||
}
|
||||
return r;
|
||||
};
|
||||
cs_int wn = 0, fn = 0;
|
||||
double r = read_digits(0.0, wn);
|
||||
if (!input.empty() && (*input == '.')) {
|
||||
++input;
|
||||
r = read_digits(r, fn);
|
||||
}
|
||||
if (!wn && !fn) {
|
||||
return false;
|
||||
}
|
||||
fn = -fn;
|
||||
p_set_end(input, end); /* we have a valid number until here */
|
||||
if (p_read_exp<Hex>(input, fn)) {
|
||||
p_set_end(input, end);
|
||||
}
|
||||
if (Hex) {
|
||||
ret = cs_float(ldexp(r, fn * 4));
|
||||
} else {
|
||||
ret = cs_float(r * pow(10, fn));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
cs_float cs_parse_float(ostd::string_range input, ostd::string_range *end) {
|
||||
ostd::string_range orig = input;
|
||||
p_skip_white(input);
|
||||
if (input.empty()) {
|
||||
p_set_end(orig, end);
|
||||
return cs_float(0);
|
||||
}
|
||||
bool neg = p_check_neg(input);
|
||||
cs_float ret = cs_float(0);
|
||||
if (input.size() >= 2) {
|
||||
ostd::string_range pfx = input.slice(0, 2);
|
||||
if ((pfx == "0x") || (pfx == "0X")) {
|
||||
input = input.slice(2, input.size());
|
||||
if (!parse_gen_float<true>(input, end, ret)) {
|
||||
p_set_end(orig, end);
|
||||
return ret;
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
if (!parse_gen_float<false>(input, end, ret)) {
|
||||
p_set_end(orig, end);
|
||||
return ret;
|
||||
}
|
||||
done:
|
||||
if (neg) {
|
||||
return -ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
namespace util {
|
||||
OSTD_EXPORT ostd::string_range parse_string(
|
||||
cs_state &cs, ostd::string_range str, size_t &nlines
|
||||
) {
|
||||
size_t nl = 0;
|
||||
nlines = nl;
|
||||
if (str.empty() || (*str != '\"')) {
|
||||
return str;
|
||||
}
|
||||
ostd::string_range orig = str;
|
||||
++str;
|
||||
++nl;
|
||||
while (!str.empty()) {
|
||||
switch (*str) {
|
||||
case '\r':
|
||||
case '\n':
|
||||
case '\"':
|
||||
goto end;
|
||||
case '^':
|
||||
case '\\': {
|
||||
bool needn = (*str == '\\');
|
||||
++str;
|
||||
if (str.empty()) {
|
||||
goto end;
|
||||
}
|
||||
if ((*str == '\r') || (*str == '\n')) {
|
||||
char c = *str;
|
||||
++str;
|
||||
++nl;
|
||||
if (!str.empty() && (c == '\r') && (*str == '\n')) {
|
||||
++str;
|
||||
}
|
||||
} else if (needn) {
|
||||
goto end;
|
||||
} else {
|
||||
++str;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
++str;
|
||||
}
|
||||
end:
|
||||
nlines = nl;
|
||||
if (str.empty() || (*str != '\"')) {
|
||||
throw cs_error(
|
||||
cs, "unfinished string '%s'", orig.slice(0, &str[0] - &orig[0])
|
||||
);
|
||||
}
|
||||
str.pop_front();
|
||||
return str;
|
||||
}
|
||||
|
||||
OSTD_EXPORT ostd::string_range parse_word(
|
||||
cs_state &cs, ostd::string_range str
|
||||
) {
|
||||
for (;;) {
|
||||
str = ostd::find_one_of(str, ostd::string_range("\"/;()[] \t\r\n"));
|
||||
if (str.empty()) {
|
||||
return str;
|
||||
}
|
||||
switch (*str) {
|
||||
case '"':
|
||||
case ';':
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\r':
|
||||
case '\n':
|
||||
return str;
|
||||
case '/':
|
||||
if ((str.size() > 1) && (str[1] == '/')) {
|
||||
return str;
|
||||
}
|
||||
break;
|
||||
case '[':
|
||||
str.pop_front();
|
||||
str = parse_word(cs, str);
|
||||
if (str.empty() || (*str != ']')) {
|
||||
throw cs_error(cs, "missing \"]\"");
|
||||
}
|
||||
break;
|
||||
case '(':
|
||||
str.pop_front();
|
||||
str = parse_word(cs, str);
|
||||
if (str.empty() || (*str != ')')) {
|
||||
throw cs_error(cs, "missing \")\"");
|
||||
}
|
||||
break;
|
||||
case ']':
|
||||
case ')':
|
||||
return str;
|
||||
}
|
||||
++str;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
void list_parser::skip() {
|
||||
for (;;) {
|
||||
while (!p_input.empty()) {
|
||||
char c = *p_input;
|
||||
if ((c == ' ') || (c == '\t') || (c == '\r') || (c == '\n')) {
|
||||
++p_input;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((p_input.size() < 2) || (p_input[0] != '/') || (p_input[1] != '/')) {
|
||||
break;
|
||||
}
|
||||
p_input = ostd::find(p_input, '\n');
|
||||
}
|
||||
}
|
||||
|
||||
bool list_parser::parse() {
|
||||
skip();
|
||||
if (p_input.empty()) {
|
||||
return false;
|
||||
}
|
||||
switch (*p_input) {
|
||||
case '"':
|
||||
p_quote = p_input;
|
||||
p_input = parse_string(p_state, p_input);
|
||||
p_quote = p_quote.slice(0, &p_input[0] - &p_quote[0]);
|
||||
p_item = p_quote.slice(1, p_quote.size() - 1);
|
||||
break;
|
||||
case '(':
|
||||
case '[': {
|
||||
p_quote = p_input;
|
||||
++p_input;
|
||||
p_item = p_input;
|
||||
char btype = *p_quote;
|
||||
int brak = 1;
|
||||
for (;;) {
|
||||
p_input = ostd::find_one_of(
|
||||
p_input, ostd::string_range("\"/;()[]")
|
||||
);
|
||||
if (p_input.empty()) {
|
||||
return true;
|
||||
}
|
||||
char c = *p_input;
|
||||
++p_input;
|
||||
switch (c) {
|
||||
case '"':
|
||||
p_input = parse_string(p_state, p_input);
|
||||
break;
|
||||
case '/':
|
||||
if (!p_input.empty() && (*p_input == '/')) {
|
||||
p_input = ostd::find(p_input, '\n');
|
||||
}
|
||||
break;
|
||||
case '(':
|
||||
case '[':
|
||||
brak += (c == btype);
|
||||
break;
|
||||
case ')':
|
||||
if ((btype == '(') && (--brak <= 0)) {
|
||||
goto endblock;
|
||||
}
|
||||
break;
|
||||
case ']':
|
||||
if ((btype == '[') && (--brak <= 0)) {
|
||||
goto endblock;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
endblock:
|
||||
p_item = p_item.slice(0, &p_input[0] - &p_item[0]);
|
||||
p_item.pop_back();
|
||||
p_quote = p_quote.slice(0, &p_input[0] - &p_quote[0]);
|
||||
break;
|
||||
}
|
||||
case ')':
|
||||
case ']':
|
||||
return false;
|
||||
default: {
|
||||
ostd::string_range e = parse_word(p_state, p_input);
|
||||
p_quote = p_item = p_input.slice(0, &e[0] - &p_input[0]);
|
||||
p_input = e;
|
||||
break;
|
||||
}
|
||||
}
|
||||
skip();
|
||||
if (!p_input.empty() && (*p_input == ';')) {
|
||||
++p_input;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t list_parser::count() {
|
||||
size_t ret = 0;
|
||||
while (parse()) {
|
||||
++ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
} /* namespace util */
|
||||
|
||||
} /* namespace cscript */
|
|
@ -0,0 +1,43 @@
|
|||
#ifndef LIBCUBESCRIPT_CS_UTIL_HH
|
||||
#define LIBCUBESCRIPT_CS_UTIL_HH
|
||||
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <ostd/string.hh>
|
||||
|
||||
namespace cscript {
|
||||
|
||||
template<typename K, typename V>
|
||||
using cs_map = std::unordered_map<K, V>;
|
||||
|
||||
template<typename T>
|
||||
using cs_vector = std::vector<T>;
|
||||
|
||||
cs_int cs_parse_int(
|
||||
ostd::string_range input, ostd::string_range *end = nullptr
|
||||
);
|
||||
|
||||
cs_float cs_parse_float(
|
||||
ostd::string_range input, ostd::string_range *end = nullptr
|
||||
);
|
||||
|
||||
template<typename F>
|
||||
struct CsScopeExit {
|
||||
template<typename FF>
|
||||
CsScopeExit(FF &&f): func(std::forward<FF>(f)) {}
|
||||
~CsScopeExit() {
|
||||
func();
|
||||
}
|
||||
std::decay_t<F> func;
|
||||
};
|
||||
|
||||
template<typename F1, typename F2>
|
||||
inline void cs_do_and_cleanup(F1 &&dof, F2 &&clf) {
|
||||
CsScopeExit<F2> cleanup(std::forward<F2>(clf));
|
||||
dof();
|
||||
}
|
||||
|
||||
} /* namespace cscript */
|
||||
|
||||
#endif /* LIBCUBESCRIPT_CS_UTIL_HH */
|
651
src/cs_val.cc
651
src/cs_val.cc
|
@ -1,69 +1,26 @@
|
|||
#include <cubescript/cubescript.hh>
|
||||
#include "cs_std.hh"
|
||||
#include "cs_parser.hh"
|
||||
#include "cs_state.hh"
|
||||
#include "cs_strman.hh"
|
||||
#include "cs_vm.hh"
|
||||
#include "cs_util.hh"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <iterator>
|
||||
namespace cscript {
|
||||
|
||||
namespace cubescript {
|
||||
|
||||
static std::string_view intstr(integer_type v, charbuf &buf) {
|
||||
buf.reserve(32);
|
||||
int n = snprintf(buf.data(), 32, INTEGER_FORMAT, v);
|
||||
if (n > 32) {
|
||||
buf.reserve(n + 1);
|
||||
int nn = snprintf(buf.data(), n + 1, INTEGER_FORMAT, v);
|
||||
if ((nn > n) || (nn <= 0)) {
|
||||
n = -1;
|
||||
} else {
|
||||
n = nn;
|
||||
}
|
||||
}
|
||||
if (n <= 0) {
|
||||
abort(); /* unreachable, provided a well-formed format string */
|
||||
}
|
||||
return std::string_view{buf.data(), std::size_t(n)};
|
||||
}
|
||||
|
||||
static std::string_view floatstr(float_type v, charbuf &buf) {
|
||||
buf.reserve(32);
|
||||
int n;
|
||||
if (v == std::floor(v)) {
|
||||
n = snprintf(buf.data(), 32, ROUND_FLOAT_FORMAT, v);
|
||||
} else {
|
||||
n = snprintf(buf.data(), 32, FLOAT_FORMAT, v);
|
||||
}
|
||||
if (n > 32) {
|
||||
buf.reserve(n + 1);
|
||||
int nn;
|
||||
if (v == std::floor(v)) {
|
||||
nn = snprintf(buf.data(), n + 1, ROUND_FLOAT_FORMAT, v);
|
||||
} else {
|
||||
nn = snprintf(buf.data(), n + 1, FLOAT_FORMAT, v);
|
||||
}
|
||||
if ((nn > n) || (nn <= 0)) {
|
||||
n = -1;
|
||||
} else {
|
||||
n = nn;
|
||||
}
|
||||
}
|
||||
if (n <= 0) {
|
||||
abort(); /* unreachable, provided a well-formed format string */
|
||||
}
|
||||
return std::string_view{buf.data(), std::size_t(n)};
|
||||
template<typename T, typename U>
|
||||
static inline T &csv_get(U &stor) {
|
||||
/* ugly, but internal and unlikely to cause bugs */
|
||||
return const_cast<T &>(reinterpret_cast<T const &>(stor));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static inline void csv_cleanup(value_type tv, T *stor) {
|
||||
static inline void csv_cleanup(cs_value_type tv, T &stor) {
|
||||
switch (tv) {
|
||||
case value_type::STRING:
|
||||
str_managed_unref(stor->s);
|
||||
case cs_value_type::String:
|
||||
delete[] csv_get<char *>(stor);
|
||||
break;
|
||||
case value_type::CODE: {
|
||||
bcode_unref(stor->b->raw());
|
||||
case cs_value_type::Code: {
|
||||
uint32_t *bcode = csv_get<uint32_t *>(stor);
|
||||
if (bcode[-1] == CsCodeStart) {
|
||||
delete[] &bcode[-1];
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -71,77 +28,40 @@ static inline void csv_cleanup(value_type tv, T *stor) {
|
|||
}
|
||||
}
|
||||
|
||||
any_value::any_value():
|
||||
p_stor{}, p_type{value_type::NONE}
|
||||
cs_value::cs_value():
|
||||
p_stor(), p_len(0), p_type(cs_value_type::Null)
|
||||
{}
|
||||
|
||||
any_value::any_value(integer_type val):
|
||||
p_stor{}, p_type{value_type::INTEGER}
|
||||
{
|
||||
p_stor.i = val;
|
||||
cs_value::~cs_value() {
|
||||
csv_cleanup(p_type, p_stor);
|
||||
}
|
||||
|
||||
any_value::any_value(float_type val):
|
||||
p_stor{}, p_type{value_type::FLOAT}
|
||||
{
|
||||
p_stor.f = val;
|
||||
}
|
||||
|
||||
any_value::any_value(std::string_view val, state &cs):
|
||||
p_stor{}, p_type{value_type::STRING}
|
||||
{
|
||||
p_stor.s = state_p{cs}.ts().istate->strman->add(val);
|
||||
}
|
||||
|
||||
any_value::any_value(string_ref const &val):
|
||||
p_stor{}, p_type{value_type::STRING}
|
||||
{
|
||||
p_stor.s = str_managed_ref(val.p_str);
|
||||
}
|
||||
|
||||
any_value::any_value(bcode_ref const &val):
|
||||
p_stor{}, p_type{value_type::CODE}
|
||||
{
|
||||
bcode *p = bcode_p{val}.get();
|
||||
bcode_addref(p->raw());
|
||||
p_stor.b = p;
|
||||
}
|
||||
|
||||
any_value::any_value(ident &val):
|
||||
p_stor{}, p_type{value_type::IDENT}
|
||||
{
|
||||
p_stor.v = &val;
|
||||
}
|
||||
|
||||
any_value::~any_value() {
|
||||
csv_cleanup(p_type, &p_stor);
|
||||
}
|
||||
|
||||
any_value::any_value(any_value const &v): any_value{} {
|
||||
cs_value::cs_value(cs_value const &v): cs_value() {
|
||||
*this = v;
|
||||
}
|
||||
|
||||
any_value::any_value(any_value &&v): any_value{} {
|
||||
cs_value::cs_value(cs_value &&v): cs_value() {
|
||||
*this = std::move(v);
|
||||
}
|
||||
|
||||
any_value &any_value::operator=(any_value const &v) {
|
||||
csv_cleanup(p_type, &p_stor);
|
||||
p_type = value_type::NONE;
|
||||
switch (v.type()) {
|
||||
case value_type::INTEGER:
|
||||
case value_type::FLOAT:
|
||||
case value_type::IDENT:
|
||||
cs_value &cs_value::operator=(cs_value const &v) {
|
||||
csv_cleanup(p_type, p_stor);
|
||||
p_type = cs_value_type::Null;
|
||||
switch (v.get_type()) {
|
||||
case cs_value_type::Int:
|
||||
case cs_value_type::Float:
|
||||
case cs_value_type::Ident:
|
||||
p_len = v.p_len;
|
||||
p_type = v.p_type;
|
||||
std::memcpy(&p_stor, &v.p_stor, sizeof(p_stor));
|
||||
p_stor = v.p_stor;
|
||||
break;
|
||||
case value_type::STRING:
|
||||
p_type = value_type::STRING;
|
||||
p_stor.s = v.p_stor.s;
|
||||
str_managed_ref(p_stor.s);
|
||||
case cs_value_type::String:
|
||||
case cs_value_type::Cstring:
|
||||
case cs_value_type::Macro:
|
||||
set_str(cs_string{csv_get<char const *>(v.p_stor), v.p_len});
|
||||
break;
|
||||
case value_type::CODE:
|
||||
set_code(v.get_code());
|
||||
case cs_value_type::Code:
|
||||
set_code(cs_copy_code(v.get_code()));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -149,114 +69,94 @@ any_value &any_value::operator=(any_value const &v) {
|
|||
return *this;
|
||||
}
|
||||
|
||||
any_value &any_value::operator=(any_value &&v) {
|
||||
*this = v;
|
||||
v.set_none();
|
||||
cs_value &cs_value::operator=(cs_value &&v) {
|
||||
csv_cleanup(p_type, p_stor);
|
||||
p_stor = v.p_stor;
|
||||
p_type = v.p_type;
|
||||
p_len = v.p_len;
|
||||
v.p_type = cs_value_type::Null;
|
||||
return *this;
|
||||
}
|
||||
|
||||
any_value &any_value::operator=(integer_type val) {
|
||||
set_integer(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
any_value &any_value::operator=(float_type val) {
|
||||
set_float(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
any_value &any_value::operator=(string_ref const &val) {
|
||||
set_string(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
any_value &any_value::operator=(bcode_ref const &val) {
|
||||
set_code(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
any_value &any_value::operator=(ident &val) {
|
||||
set_ident(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
value_type any_value::type() const {
|
||||
cs_value_type cs_value::get_type() const {
|
||||
return p_type;
|
||||
}
|
||||
|
||||
void any_value::set_integer(integer_type val) {
|
||||
csv_cleanup(p_type, &p_stor);
|
||||
p_type = value_type::INTEGER;
|
||||
p_stor.i = val;
|
||||
void cs_value::set_int(cs_int val) {
|
||||
csv_cleanup(p_type, p_stor);
|
||||
p_type = cs_value_type::Int;
|
||||
csv_get<cs_int>(p_stor) = val;
|
||||
}
|
||||
|
||||
void any_value::set_float(float_type val) {
|
||||
csv_cleanup(p_type, &p_stor);
|
||||
p_type = value_type::FLOAT;
|
||||
p_stor.f = val;
|
||||
void cs_value::set_float(cs_float val) {
|
||||
csv_cleanup(p_type, p_stor);
|
||||
p_type = cs_value_type::Float;
|
||||
csv_get<cs_float>(p_stor) = val;
|
||||
}
|
||||
|
||||
void any_value::set_string(std::string_view val, state &cs) {
|
||||
csv_cleanup(p_type, &p_stor);
|
||||
p_stor.s = state_p{cs}.ts().istate->strman->add(val);
|
||||
p_type = value_type::STRING;
|
||||
void cs_value::set_str(cs_string val) {
|
||||
csv_cleanup(p_type, p_stor);
|
||||
p_type = cs_value_type::String;
|
||||
p_len = val.size();
|
||||
char *buf = new char[p_len + 1];
|
||||
memcpy(buf, val.data(), p_len + 1);
|
||||
csv_get<char *>(p_stor) = buf;
|
||||
}
|
||||
|
||||
void any_value::set_string(string_ref const &val) {
|
||||
csv_cleanup(p_type, &p_stor);
|
||||
p_stor.s = str_managed_ref(val.p_str);
|
||||
p_type = value_type::STRING;
|
||||
void cs_value::set_null() {
|
||||
csv_cleanup(p_type, p_stor);
|
||||
p_type = cs_value_type::Null;
|
||||
}
|
||||
|
||||
void any_value::set_none() {
|
||||
csv_cleanup(p_type, &p_stor);
|
||||
p_type = value_type::NONE;
|
||||
void cs_value::set_code(cs_bcode *val) {
|
||||
csv_cleanup(p_type, p_stor);
|
||||
p_type = cs_value_type::Code;
|
||||
csv_get<cs_bcode *>(p_stor) = val;
|
||||
}
|
||||
|
||||
void any_value::set_code(bcode_ref const &val) {
|
||||
bcode *p = bcode_p{val}.get();
|
||||
csv_cleanup(p_type, &p_stor);
|
||||
p_type = value_type::CODE;
|
||||
bcode_addref(p->raw());
|
||||
p_stor.b = p;
|
||||
void cs_value::set_cstr(ostd::string_range val) {
|
||||
csv_cleanup(p_type, p_stor);
|
||||
p_type = cs_value_type::Cstring;
|
||||
p_len = val.size();
|
||||
csv_get<char const *>(p_stor) = val.data();
|
||||
}
|
||||
|
||||
void any_value::set_ident(ident &val) {
|
||||
csv_cleanup(p_type, &p_stor);
|
||||
p_type = value_type::IDENT;
|
||||
p_stor.v = &val;
|
||||
void cs_value::set_ident(cs_ident *val) {
|
||||
csv_cleanup(p_type, p_stor);
|
||||
p_type = cs_value_type::Ident;
|
||||
csv_get<cs_ident *>(p_stor) = val;
|
||||
}
|
||||
|
||||
void any_value::force_none() {
|
||||
if (type() == value_type::NONE) {
|
||||
void cs_value::set_macro(ostd::string_range val) {
|
||||
csv_cleanup(p_type, p_stor);
|
||||
p_type = cs_value_type::Macro;
|
||||
p_len = val.size();
|
||||
csv_get<char const *>(p_stor) = val.data();
|
||||
}
|
||||
|
||||
void cs_value::force_null() {
|
||||
if (get_type() == cs_value_type::Null) {
|
||||
return;
|
||||
}
|
||||
set_none();
|
||||
set_null();
|
||||
}
|
||||
|
||||
void any_value::force_plain() {
|
||||
switch (type()) {
|
||||
case value_type::FLOAT:
|
||||
case value_type::INTEGER:
|
||||
case value_type::STRING:
|
||||
return;
|
||||
default:
|
||||
cs_float cs_value::force_float() {
|
||||
cs_float rf = 0.0f;
|
||||
switch (get_type()) {
|
||||
case cs_value_type::Int:
|
||||
rf = csv_get<cs_int>(p_stor);
|
||||
break;
|
||||
}
|
||||
force_none();
|
||||
}
|
||||
|
||||
float_type any_value::force_float() {
|
||||
float_type rf = 0.0f;
|
||||
switch (type()) {
|
||||
case value_type::INTEGER:
|
||||
rf = float_type(p_stor.i);
|
||||
case cs_value_type::String:
|
||||
case cs_value_type::Macro:
|
||||
case cs_value_type::Cstring:
|
||||
rf = cs_parse_float(ostd::string_range(
|
||||
csv_get<char const *>(p_stor),
|
||||
csv_get<char const *>(p_stor) + p_len
|
||||
));
|
||||
break;
|
||||
case value_type::STRING:
|
||||
rf = parse_float(str_managed_view(p_stor.s));
|
||||
break;
|
||||
case value_type::FLOAT:
|
||||
return p_stor.f;
|
||||
case cs_value_type::Float:
|
||||
return csv_get<cs_float>(p_stor);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -264,197 +164,270 @@ float_type any_value::force_float() {
|
|||
return rf;
|
||||
}
|
||||
|
||||
integer_type any_value::force_integer() {
|
||||
integer_type ri = 0;
|
||||
switch (type()) {
|
||||
case value_type::FLOAT:
|
||||
ri = integer_type(std::floor(p_stor.f));
|
||||
cs_int cs_value::force_int() {
|
||||
cs_int ri = 0;
|
||||
switch (get_type()) {
|
||||
case cs_value_type::Float:
|
||||
ri = csv_get<cs_float>(p_stor);
|
||||
break;
|
||||
case value_type::STRING:
|
||||
ri = parse_int(str_managed_view(p_stor.s));
|
||||
case cs_value_type::String:
|
||||
case cs_value_type::Macro:
|
||||
case cs_value_type::Cstring:
|
||||
ri = cs_parse_int(ostd::string_range(
|
||||
csv_get<char const *>(p_stor),
|
||||
csv_get<char const *>(p_stor) + p_len
|
||||
));
|
||||
break;
|
||||
case value_type::INTEGER:
|
||||
return p_stor.i;
|
||||
case cs_value_type::Int:
|
||||
return csv_get<cs_int>(p_stor);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
set_integer(ri);
|
||||
set_int(ri);
|
||||
return ri;
|
||||
}
|
||||
|
||||
std::string_view any_value::force_string(state &cs) {
|
||||
charbuf rs{cs};
|
||||
std::string_view str;
|
||||
switch (type()) {
|
||||
case value_type::FLOAT:
|
||||
str = floatstr(p_stor.f, rs);
|
||||
ostd::string_range cs_value::force_str() {
|
||||
cs_string rs;
|
||||
switch (get_type()) {
|
||||
case cs_value_type::Float:
|
||||
rs = floatstr(csv_get<cs_float>(p_stor));
|
||||
break;
|
||||
case value_type::INTEGER:
|
||||
str = intstr(p_stor.i, rs);
|
||||
case cs_value_type::Int:
|
||||
rs = intstr(csv_get<cs_int>(p_stor));
|
||||
break;
|
||||
case value_type::STRING:
|
||||
return str_managed_view(p_stor.s);
|
||||
default:
|
||||
str = rs.str();
|
||||
case cs_value_type::Macro:
|
||||
case cs_value_type::Cstring:
|
||||
rs = ostd::string_range(
|
||||
csv_get<char const *>(p_stor),
|
||||
csv_get<char const *>(p_stor) + p_len
|
||||
);
|
||||
break;
|
||||
}
|
||||
set_string(str, cs);
|
||||
return str_managed_view(p_stor.s);
|
||||
}
|
||||
|
||||
bcode_ref any_value::force_code(state &cs, std::string_view source) {
|
||||
switch (type()) {
|
||||
case value_type::CODE:
|
||||
return bcode_p::make_ref(p_stor.b);
|
||||
case cs_value_type::String:
|
||||
return ostd::string_range(
|
||||
csv_get<char const *>(p_stor),
|
||||
csv_get<char const *>(p_stor) + p_len
|
||||
);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
gen_state gs{state_p{cs}.ts()};
|
||||
gs.gen_main(get_string(cs), source);
|
||||
auto bc = gs.steal_ref();
|
||||
set_code(bc);
|
||||
return bc;
|
||||
}
|
||||
|
||||
ident &any_value::force_ident(state &cs) {
|
||||
switch (type()) {
|
||||
case value_type::IDENT:
|
||||
return *p_stor.v;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
auto &id = state_p{cs}.ts().istate->new_ident(
|
||||
cs, get_string(cs), IDENT_FLAG_UNKNOWN
|
||||
set_str(std::move(rs));
|
||||
return ostd::string_range(
|
||||
csv_get<char const *>(p_stor),
|
||||
csv_get<char const *>(p_stor) + p_len
|
||||
);
|
||||
set_ident(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
integer_type any_value::get_integer() const {
|
||||
switch (type()) {
|
||||
case value_type::FLOAT:
|
||||
return integer_type(std::floor(p_stor.f));
|
||||
case value_type::INTEGER:
|
||||
return p_stor.i;
|
||||
case value_type::STRING:
|
||||
return parse_int(str_managed_view(p_stor.s));
|
||||
cs_int cs_value::get_int() const {
|
||||
switch (get_type()) {
|
||||
case cs_value_type::Float:
|
||||
return cs_int(csv_get<cs_float>(p_stor));
|
||||
case cs_value_type::Int:
|
||||
return csv_get<cs_int>(p_stor);
|
||||
case cs_value_type::String:
|
||||
case cs_value_type::Macro:
|
||||
case cs_value_type::Cstring:
|
||||
return cs_parse_int(ostd::string_range(
|
||||
csv_get<char const *>(p_stor),
|
||||
csv_get<char const *>(p_stor) + p_len
|
||||
));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
float_type any_value::get_float() const {
|
||||
switch (type()) {
|
||||
case value_type::FLOAT:
|
||||
return p_stor.f;
|
||||
case value_type::INTEGER:
|
||||
return float_type(p_stor.i);
|
||||
case value_type::STRING:
|
||||
return parse_float(str_managed_view(p_stor.s));
|
||||
cs_float cs_value::get_float() const {
|
||||
switch (get_type()) {
|
||||
case cs_value_type::Float:
|
||||
return csv_get<cs_float>(p_stor);
|
||||
case cs_value_type::Int:
|
||||
return cs_float(csv_get<cs_int>(p_stor));
|
||||
case cs_value_type::String:
|
||||
case cs_value_type::Macro:
|
||||
case cs_value_type::Cstring:
|
||||
return cs_parse_float(ostd::string_range(
|
||||
csv_get<char const *>(p_stor),
|
||||
csv_get<char const *>(p_stor) + p_len
|
||||
));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
bcode_ref any_value::get_code() const {
|
||||
if (type() != value_type::CODE) {
|
||||
return bcode_ref{};
|
||||
cs_bcode *cs_value::get_code() const {
|
||||
if (get_type() != cs_value_type::Code) {
|
||||
return nullptr;
|
||||
}
|
||||
return bcode_p::make_ref(p_stor.b);
|
||||
return csv_get<cs_bcode *>(p_stor);
|
||||
}
|
||||
|
||||
ident &any_value::get_ident(state &cs) const {
|
||||
if (type() != value_type::IDENT) {
|
||||
return *state_p{cs}.ts().istate->id_dummy;
|
||||
cs_ident *cs_value::get_ident() const {
|
||||
if (get_type() != cs_value_type::Ident) {
|
||||
return nullptr;
|
||||
}
|
||||
return *p_stor.v;
|
||||
return csv_get<cs_ident *>(p_stor);
|
||||
}
|
||||
|
||||
string_ref any_value::get_string(state &cs) const {
|
||||
switch (type()) {
|
||||
case value_type::STRING:
|
||||
return string_ref{p_stor.s};
|
||||
case value_type::INTEGER: {
|
||||
charbuf rs{cs};
|
||||
return string_ref{cs, intstr(p_stor.i, rs)};
|
||||
}
|
||||
case value_type::FLOAT: {
|
||||
charbuf rs{cs};
|
||||
return string_ref{cs, floatstr(p_stor.f, rs)};
|
||||
}
|
||||
cs_string cs_value::get_str() const {
|
||||
switch (get_type()) {
|
||||
case cs_value_type::String:
|
||||
case cs_value_type::Macro:
|
||||
case cs_value_type::Cstring:
|
||||
return cs_string{csv_get<char const *>(p_stor), p_len};
|
||||
case cs_value_type::Int:
|
||||
return intstr(csv_get<cs_int>(p_stor));
|
||||
case cs_value_type::Float:
|
||||
return floatstr(csv_get<cs_float>(p_stor));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return string_ref{cs, ""};
|
||||
return cs_string("");
|
||||
}
|
||||
|
||||
any_value any_value::get_plain() const {
|
||||
switch (type()) {
|
||||
case value_type::STRING:
|
||||
case value_type::INTEGER:
|
||||
case value_type::FLOAT:
|
||||
return *this;
|
||||
ostd::string_range cs_value::get_strr() const {
|
||||
switch (get_type()) {
|
||||
case cs_value_type::String:
|
||||
case cs_value_type::Macro:
|
||||
case cs_value_type::Cstring:
|
||||
return ostd::string_range(
|
||||
csv_get<char const *>(p_stor),
|
||||
csv_get<char const *>(p_stor)+ p_len
|
||||
);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return any_value{};
|
||||
return ostd::string_range();
|
||||
}
|
||||
|
||||
bool any_value::get_bool() const {
|
||||
switch (type()) {
|
||||
case value_type::FLOAT:
|
||||
return p_stor.f != 0;
|
||||
case value_type::INTEGER:
|
||||
return p_stor.i != 0;
|
||||
case value_type::STRING: {
|
||||
std::string_view s = str_managed_view(p_stor.s);
|
||||
if (s.empty()) {
|
||||
return false;
|
||||
}
|
||||
std::string_view end = s;
|
||||
integer_type ival = parse_int(end, &end);
|
||||
if (end.empty()) {
|
||||
return !!ival;
|
||||
}
|
||||
end = s;
|
||||
float_type fval = parse_float(end, &end);
|
||||
if (end.empty()) {
|
||||
return !!fval;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void cs_value::get_val(cs_value &r) const {
|
||||
switch (get_type()) {
|
||||
case cs_value_type::String:
|
||||
case cs_value_type::Macro:
|
||||
case cs_value_type::Cstring:
|
||||
r.set_str(
|
||||
cs_string{csv_get<char const *>(p_stor), p_len}
|
||||
);
|
||||
break;
|
||||
case cs_value_type::Int:
|
||||
r.set_int(csv_get<cs_int>(p_stor));
|
||||
break;
|
||||
case cs_value_type::Float:
|
||||
r.set_float(csv_get<cs_float>(p_stor));
|
||||
break;
|
||||
default:
|
||||
r.set_null();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
OSTD_EXPORT bool cs_code_is_empty(cs_bcode *code) {
|
||||
if (!code) {
|
||||
return true;
|
||||
}
|
||||
return (
|
||||
*reinterpret_cast<uint32_t *>(code) & CsCodeOpMask
|
||||
) == CsCodeExit;
|
||||
}
|
||||
|
||||
bool cs_value::code_is_empty() const {
|
||||
if (get_type() != cs_value_type::Code) {
|
||||
return true;
|
||||
}
|
||||
return cscript::cs_code_is_empty(csv_get<cs_bcode *>(p_stor));
|
||||
}
|
||||
|
||||
static inline bool cs_get_bool(ostd::string_range s) {
|
||||
if (s.empty()) {
|
||||
return false;
|
||||
}
|
||||
ostd::string_range end = s;
|
||||
cs_int ival = cs_parse_int(end, &end);
|
||||
if (end.empty()) {
|
||||
return !!ival;
|
||||
}
|
||||
end = s;
|
||||
cs_float fval = cs_parse_float(end, &end);
|
||||
if (end.empty()) {
|
||||
return !!fval;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cs_value::get_bool() const {
|
||||
switch (get_type()) {
|
||||
case cs_value_type::Float:
|
||||
return csv_get<cs_float>(p_stor) != 0;
|
||||
case cs_value_type::Int:
|
||||
return csv_get<cs_int>(p_stor) != 0;
|
||||
case cs_value_type::String:
|
||||
case cs_value_type::Macro:
|
||||
case cs_value_type::Cstring:
|
||||
return cs_get_bool(ostd::string_range(
|
||||
csv_get<char const *>(p_stor),
|
||||
csv_get<char const *>(p_stor) + p_len
|
||||
));
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* public utilities */
|
||||
/* stacked value for easy stack management */
|
||||
|
||||
LIBCUBESCRIPT_EXPORT string_ref concat_values(
|
||||
state &cs, span_type<any_value> vals, std::string_view sep
|
||||
) {
|
||||
charbuf buf{cs};
|
||||
for (std::size_t i = 0; i < vals.size(); ++i) {
|
||||
switch (vals[i].type()) {
|
||||
case value_type::INTEGER:
|
||||
case value_type::FLOAT:
|
||||
case value_type::STRING: {
|
||||
auto val = any_value{vals[i]};
|
||||
auto str = val.force_string(cs);
|
||||
std::copy(str.begin(), str.end(), std::back_inserter(buf));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (i == (vals.size() - 1)) {
|
||||
break;
|
||||
}
|
||||
std::copy(sep.begin(), sep.end(), std::back_inserter(buf));
|
||||
}
|
||||
return string_ref{cs, buf.str()};
|
||||
cs_stacked_value::cs_stacked_value(cs_ident *id):
|
||||
cs_value(), p_a(nullptr), p_stack(), p_pushed(false)
|
||||
{
|
||||
set_alias(id);
|
||||
}
|
||||
|
||||
} /* namespace cubescript */
|
||||
cs_stacked_value::~cs_stacked_value() {
|
||||
pop();
|
||||
static_cast<cs_value *>(this)->~cs_value();
|
||||
}
|
||||
|
||||
cs_stacked_value &cs_stacked_value::operator=(cs_value const &v) {
|
||||
*static_cast<cs_value *>(this) = v;
|
||||
return *this;
|
||||
}
|
||||
|
||||
cs_stacked_value &cs_stacked_value::operator=(cs_value &&v) {
|
||||
*static_cast<cs_value *>(this) = std::move(v);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool cs_stacked_value::set_alias(cs_ident *id) {
|
||||
if (!id || !id->is_alias()) {
|
||||
return false;
|
||||
}
|
||||
p_a = static_cast<cs_alias *>(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
cs_alias *cs_stacked_value::get_alias() const {
|
||||
return p_a;
|
||||
}
|
||||
|
||||
bool cs_stacked_value::has_alias() const {
|
||||
return p_a != nullptr;
|
||||
}
|
||||
|
||||
bool cs_stacked_value::push() {
|
||||
if (!p_a) {
|
||||
return false;
|
||||
}
|
||||
cs_alias_internal::push_arg(p_a, *this, p_stack);
|
||||
p_pushed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cs_stacked_value::pop() {
|
||||
if (!p_pushed || !p_a) {
|
||||
return false;
|
||||
}
|
||||
cs_alias_internal::pop_arg(p_a);
|
||||
p_pushed = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
} /* namespace cscript */
|
||||
|
|
2388
src/cs_vm.cc
2388
src/cs_vm.cc
File diff suppressed because it is too large
Load Diff
457
src/cs_vm.hh
457
src/cs_vm.hh
|
@ -1,38 +1,447 @@
|
|||
#ifndef LIBCUBESCRIPT_VM_HH
|
||||
#define LIBCUBESCRIPT_VM_HH
|
||||
#ifndef LIBCUBESCRIPT_CS_VM_HH
|
||||
#define LIBCUBESCRIPT_CS_VM_HH
|
||||
|
||||
#include <cubescript/cubescript.hh>
|
||||
#include "cubescript/cubescript.hh"
|
||||
|
||||
#include "cs_std.hh"
|
||||
#include "cs_ident.hh"
|
||||
#include "cs_thread.hh"
|
||||
#include <cstdlib>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include <utility>
|
||||
#include "cs_util.hh"
|
||||
|
||||
namespace cubescript {
|
||||
namespace cscript {
|
||||
|
||||
struct break_exception {
|
||||
static constexpr int MaxArguments = 25;
|
||||
static constexpr int MaxResults = 7;
|
||||
|
||||
static constexpr int DummyIdx = MaxArguments;
|
||||
static constexpr int NumargsIdx = MaxArguments + 1;
|
||||
static constexpr int DbgaliasIdx = MaxArguments + 2;
|
||||
|
||||
enum {
|
||||
CsIdUnknown = -1, CsIdIvar, CsIdFvar, CsIdSvar, CsIdCommand, CsIdAlias,
|
||||
CsIdLocal, CsIdDo, CsIdDoArgs, CsIdIf, CsIdBreak, CsIdContinue, CsIdResult,
|
||||
CsIdNot, CsIdAnd, CsIdOr
|
||||
};
|
||||
|
||||
struct continue_exception {
|
||||
struct cs_identLink {
|
||||
cs_ident *id;
|
||||
cs_identLink *next;
|
||||
int usedargs;
|
||||
cs_ident_stack *argstack;
|
||||
};
|
||||
|
||||
void exec_command(
|
||||
thread_state &ts, command_impl *id, ident *self, any_value *args,
|
||||
any_value &res, std::size_t nargs, bool lookup = false
|
||||
);
|
||||
enum {
|
||||
CsValNull = 0, CsValInt, CsValFloat, CsValString,
|
||||
CsValAny, CsValCode, CsValMacro, CsValIdent, CsValCstring,
|
||||
CsValCany, CsValWord, CsValPop, CsValCond
|
||||
};
|
||||
|
||||
any_value exec_alias(
|
||||
thread_state &ts, alias *a, any_value *args,
|
||||
std::size_t callargs, alias_stack &astack
|
||||
);
|
||||
static const int cs_valtypet[] = {
|
||||
CsValNull, CsValInt, CsValFloat, CsValString,
|
||||
CsValCstring, CsValCode, CsValMacro, CsValIdent
|
||||
};
|
||||
|
||||
any_value exec_code_with_args(thread_state &ts, bcode_ref const &body);
|
||||
static inline int cs_vtype_to_int(cs_value_type v) {
|
||||
return cs_valtypet[int(v)];
|
||||
}
|
||||
|
||||
std::uint32_t *vm_exec(
|
||||
thread_state &ts, std::uint32_t *code, any_value &result
|
||||
);
|
||||
/* instruction: uint32 [length 24][retflag 2][opcode 6] */
|
||||
enum {
|
||||
CsCodeStart = 0,
|
||||
CsCodeOffset,
|
||||
CsCodeNull, CsCodeTrue, CsCodeFalse, CsCodeNot,
|
||||
CsCodePop,
|
||||
CsCodeEnter, CsCodeEnterResult,
|
||||
CsCodeExit, CsCodeResultArg,
|
||||
CsCodeVal, CsCodeValInt,
|
||||
CsCodeDup,
|
||||
CsCodeMacro,
|
||||
CsCodeBool,
|
||||
CsCodeBlock, CsCodeEmpty,
|
||||
CsCodeCompile, CsCodeCond,
|
||||
CsCodeForce,
|
||||
CsCodeResult,
|
||||
CsCodeIdent, CsCodeIdentU, CsCodeIdentArg,
|
||||
CsCodeCom, CsCodeComC, CsCodeComV,
|
||||
CsCodeConc, CsCodeConcW, CsCodeConcM,
|
||||
CsCodeSvar, CsCodeSvarM, CsCodeSvar1,
|
||||
CsCodeIvar, CsCodeIvar1, CsCodeIvar2, CsCodeIvar3,
|
||||
CsCodeFvar, CsCodeFvar1,
|
||||
CsCodeLookup, CsCodeLookupU, CsCodeLookupArg,
|
||||
CsCodeLookupM, CsCodeLookupMu, CsCodeLookupMarg,
|
||||
CsCodeAlias, CsCodeAliasU, CsCodeAliasArg,
|
||||
CsCodeCall, CsCodeCallU, CsCodeCallArg,
|
||||
CsCodePrint,
|
||||
CsCodeLocal,
|
||||
CsCodeDo, CsCodeDoArgs,
|
||||
CsCodeJump, CsCodeJumpB, CsCodeJumpResult,
|
||||
CsCodeBreak,
|
||||
|
||||
} /* namespace cubescript */
|
||||
CsCodeOpMask = 0x3F,
|
||||
CsCodeRet = 6,
|
||||
CsCodeRetMask = 0xC0,
|
||||
|
||||
#endif /* LIBCUBESCRIPT_VM_HH */
|
||||
/* return type flags */
|
||||
CsRetNull = CsValNull << CsCodeRet,
|
||||
CsRetString = CsValString << CsCodeRet,
|
||||
CsRetInt = CsValInt << CsCodeRet,
|
||||
CsRetFloat = CsValFloat << CsCodeRet,
|
||||
|
||||
/* CsCodeJumpB, CsCodeJumpResult */
|
||||
CsCodeFlagTrue = 1 << CsCodeRet,
|
||||
CsCodeFlagFalse = 0 << CsCodeRet
|
||||
};
|
||||
|
||||
struct cs_shared_state {
|
||||
cs_map<ostd::string_range, cs_ident *> idents;
|
||||
cs_vector<cs_ident *> identmap;
|
||||
cs_alloc_cb allocf;
|
||||
void *aptr;
|
||||
|
||||
void *alloc(void *ptr, size_t os, size_t ns) {
|
||||
return allocf(aptr, ptr, os, ns);
|
||||
}
|
||||
|
||||
template<typename T, typename ...A>
|
||||
T *create(A &&...args) {
|
||||
T *ret = static_cast<T *>(alloc(nullptr, 0, sizeof(T)));
|
||||
new (ret) T(std::forward<A>(args)...);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T *create_array(size_t len) {
|
||||
T *ret = static_cast<T *>(alloc(nullptr, 0, len * sizeof(T)));
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
new (&ret[i]) T();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void destroy(T *v) noexcept {
|
||||
v->~T();
|
||||
alloc(v, sizeof(T), 0);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void destroy_array(T *v, size_t len) noexcept {
|
||||
v->~T();
|
||||
alloc(v, len * sizeof(T), 0);
|
||||
}
|
||||
};
|
||||
|
||||
struct CsBreakException {
|
||||
};
|
||||
|
||||
struct CsContinueException {
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
constexpr size_t CsTypeStorageSize =
|
||||
(sizeof(T) - 1) / sizeof(uint32_t) + 1;
|
||||
|
||||
struct cs_gen_state {
|
||||
cs_state &cs;
|
||||
cs_gen_state *prevps;
|
||||
bool parsing = true;
|
||||
cs_vector<uint32_t> code;
|
||||
ostd::string_range source;
|
||||
size_t current_line;
|
||||
ostd::string_range src_name;
|
||||
|
||||
cs_gen_state() = delete;
|
||||
cs_gen_state(cs_state &csr):
|
||||
cs(csr), prevps(csr.p_pstate), code(),
|
||||
source(nullptr), current_line(1), src_name()
|
||||
{
|
||||
csr.p_pstate = this;
|
||||
}
|
||||
|
||||
~cs_gen_state() {
|
||||
done();
|
||||
}
|
||||
|
||||
void done() {
|
||||
if (!parsing) {
|
||||
return;
|
||||
}
|
||||
cs.p_pstate = prevps;
|
||||
parsing = false;
|
||||
}
|
||||
|
||||
ostd::string_range get_str();
|
||||
cs_string get_str_dup(bool unescape = true);
|
||||
|
||||
ostd::string_range get_word();
|
||||
|
||||
void gen_str(ostd::string_range word, bool macro = false) {
|
||||
if (word.size() <= 3 && !macro) {
|
||||
uint32_t op = CsCodeValInt | CsRetString;
|
||||
for (size_t i = 0; i < word.size(); ++i) {
|
||||
op |= uint32_t(
|
||||
static_cast<unsigned char>(word[i])
|
||||
) << ((i + 1) * 8);
|
||||
}
|
||||
code.push_back(op);
|
||||
return;
|
||||
}
|
||||
code.push_back(
|
||||
(macro ? CsCodeMacro : (CsCodeVal | CsRetString)) | (word.size() << 8)
|
||||
);
|
||||
auto it = reinterpret_cast<uint32_t const *>(word.data());
|
||||
code.insert(
|
||||
code.end(), it, it + (word.size() / sizeof(uint32_t))
|
||||
);
|
||||
size_t esz = word.size() % sizeof(uint32_t);
|
||||
union {
|
||||
char c[sizeof(uint32_t)];
|
||||
uint32_t u;
|
||||
} end;
|
||||
end.u = 0;
|
||||
memcpy(end.c, word.data() + word.size() - esz, esz);
|
||||
code.push_back(end.u);
|
||||
}
|
||||
|
||||
void gen_str() {
|
||||
code.push_back(CsCodeValInt | CsRetString);
|
||||
}
|
||||
|
||||
void gen_null() {
|
||||
code.push_back(CsCodeValInt | CsRetNull);
|
||||
}
|
||||
|
||||
void gen_int(cs_int i = 0) {
|
||||
if (i >= -0x800000 && i <= 0x7FFFFF) {
|
||||
code.push_back(CsCodeValInt | CsRetInt | (i << 8));
|
||||
} else {
|
||||
union {
|
||||
cs_int i;
|
||||
uint32_t u[CsTypeStorageSize<cs_int>];
|
||||
} c;
|
||||
c.i = i;
|
||||
code.push_back(CsCodeVal | CsRetInt);
|
||||
code.insert(code.end(), c.u, c.u + CsTypeStorageSize<cs_int>);
|
||||
}
|
||||
}
|
||||
|
||||
void gen_int(ostd::string_range word);
|
||||
|
||||
void gen_float(cs_float f = 0.0f) {
|
||||
if (cs_int(f) == f && f >= -0x800000 && f <= 0x7FFFFF) {
|
||||
code.push_back(CsCodeValInt | CsRetFloat | (cs_int(f) << 8));
|
||||
} else {
|
||||
union {
|
||||
cs_float f;
|
||||
uint32_t u[CsTypeStorageSize<cs_float>];
|
||||
} c;
|
||||
c.f = f;
|
||||
code.push_back(CsCodeVal | CsRetFloat);
|
||||
code.insert(code.end(), c.u, c.u + CsTypeStorageSize<cs_float>);
|
||||
}
|
||||
}
|
||||
|
||||
void gen_float(ostd::string_range word);
|
||||
|
||||
void gen_ident(cs_ident *id) {
|
||||
code.push_back(
|
||||
((id->get_index() < MaxArguments)
|
||||
? CsCodeIdentArg
|
||||
: CsCodeIdent
|
||||
) | (id->get_index() << 8)
|
||||
);
|
||||
}
|
||||
|
||||
void gen_ident() {
|
||||
gen_ident(cs.p_state->identmap[DummyIdx]);
|
||||
}
|
||||
|
||||
void gen_ident(ostd::string_range word) {
|
||||
gen_ident(cs.new_ident(word));
|
||||
}
|
||||
|
||||
void gen_value(
|
||||
int wordtype, ostd::string_range word = ostd::string_range(),
|
||||
int line = 0
|
||||
);
|
||||
|
||||
void gen_main(ostd::string_range s, int ret_type = CsValAny);
|
||||
|
||||
void next_char() {
|
||||
if (source.empty()) {
|
||||
return;
|
||||
}
|
||||
if (*source == '\n') {
|
||||
++current_line;
|
||||
}
|
||||
source.pop_front();
|
||||
}
|
||||
|
||||
char current(size_t ahead = 0) {
|
||||
if (source.size() <= ahead) {
|
||||
return '\0';
|
||||
}
|
||||
return source[ahead];
|
||||
}
|
||||
|
||||
ostd::string_range read_macro_name();
|
||||
|
||||
char skip_until(ostd::string_range chars);
|
||||
char skip_until(char cf);
|
||||
|
||||
void skip_comments();
|
||||
};
|
||||
|
||||
cs_string intstr(cs_int v);
|
||||
cs_string floatstr(cs_float v);
|
||||
|
||||
bool cs_check_num(ostd::string_range s);
|
||||
|
||||
static inline void bcode_incr(uint32_t *bc) {
|
||||
*bc += 0x100;
|
||||
}
|
||||
|
||||
static inline void bcode_decr(uint32_t *bc) {
|
||||
*bc -= 0x100;
|
||||
if (std::int32_t(*bc) < 0x100) {
|
||||
delete[] bc;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool cs_is_arg_used(cs_state &cs, cs_ident *id) {
|
||||
if (!cs.p_callstack) {
|
||||
return true;
|
||||
}
|
||||
return cs.p_callstack->usedargs & (1 << id->get_index());
|
||||
}
|
||||
|
||||
struct cs_alias_internal {
|
||||
static void push_arg(
|
||||
cs_alias *a, cs_value &v, cs_ident_stack &st, bool um = true
|
||||
) {
|
||||
if (a->p_astack == &st) {
|
||||
/* prevent cycles and unnecessary code elsewhere */
|
||||
a->p_val = std::move(v);
|
||||
clean_code(a);
|
||||
return;
|
||||
}
|
||||
st.val_s = std::move(a->p_val);
|
||||
st.next = a->p_astack;
|
||||
a->p_astack = &st;
|
||||
a->p_val = std::move(v);
|
||||
clean_code(a);
|
||||
if (um) {
|
||||
a->p_flags &= ~CS_IDF_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static void pop_arg(cs_alias *a) {
|
||||
if (!a->p_astack) {
|
||||
return;
|
||||
}
|
||||
cs_ident_stack *st = a->p_astack;
|
||||
a->p_val = std::move(a->p_astack->val_s);
|
||||
clean_code(a);
|
||||
a->p_astack = st->next;
|
||||
}
|
||||
|
||||
static void undo_arg(cs_alias *a, cs_ident_stack &st) {
|
||||
cs_ident_stack *prev = a->p_astack;
|
||||
st.val_s = std::move(a->p_val);
|
||||
st.next = prev;
|
||||
a->p_astack = prev->next;
|
||||
a->p_val = std::move(prev->val_s);
|
||||
clean_code(a);
|
||||
}
|
||||
|
||||
static void redo_arg(cs_alias *a, cs_ident_stack &st) {
|
||||
cs_ident_stack *prev = st.next;
|
||||
prev->val_s = std::move(a->p_val);
|
||||
a->p_astack = prev;
|
||||
a->p_val = std::move(st.val_s);
|
||||
clean_code(a);
|
||||
}
|
||||
|
||||
static void set_arg(cs_alias *a, cs_state &cs, cs_value &v) {
|
||||
if (cs_is_arg_used(cs, a)) {
|
||||
a->p_val = std::move(v);
|
||||
clean_code(a);
|
||||
} else {
|
||||
push_arg(a, v, cs.p_callstack->argstack[a->get_index()], false);
|
||||
cs.p_callstack->usedargs |= 1 << a->get_index();
|
||||
}
|
||||
}
|
||||
|
||||
static void set_alias(cs_alias *a, cs_state &cs, cs_value &v) {
|
||||
a->p_val = std::move(v);
|
||||
clean_code(a);
|
||||
a->p_flags = (a->p_flags & cs.identflags) | cs.identflags;
|
||||
}
|
||||
|
||||
static void clean_code(cs_alias *a) {
|
||||
uint32_t *bcode = reinterpret_cast<uint32_t *>(a->p_acode);
|
||||
if (bcode) {
|
||||
bcode_decr(bcode);
|
||||
a->p_acode = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static cs_bcode *compile_code(cs_alias *a, cs_state &cs) {
|
||||
if (!a->p_acode) {
|
||||
cs_gen_state gs(cs);
|
||||
gs.code.reserve(64);
|
||||
gs.gen_main(a->get_value().get_str());
|
||||
/* i wish i could steal the memory somehow */
|
||||
uint32_t *code = new uint32_t[gs.code.size()];
|
||||
memcpy(code, gs.code.data(), gs.code.size() * sizeof(uint32_t));
|
||||
bcode_incr(code);
|
||||
a->p_acode = reinterpret_cast<cs_bcode *>(code);
|
||||
}
|
||||
return a->p_acode;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename F>
|
||||
static void cs_do_args(cs_state &cs, F body) {
|
||||
if (!cs.p_callstack) {
|
||||
body();
|
||||
return;
|
||||
}
|
||||
cs_ident_stack argstack[MaxArguments];
|
||||
int argmask1 = cs.p_callstack->usedargs;
|
||||
for (int i = 0; argmask1; argmask1 >>= 1, ++i) {
|
||||
if (argmask1 & 1) {
|
||||
cs_alias_internal::undo_arg(
|
||||
static_cast<cs_alias *>(cs.p_state->identmap[i]), argstack[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
cs_identLink *prevstack = cs.p_callstack->next;
|
||||
cs_identLink aliaslink = {
|
||||
cs.p_callstack->id, cs.p_callstack,
|
||||
prevstack ? prevstack->usedargs : ((1 << MaxArguments) - 1),
|
||||
prevstack ? prevstack->argstack : nullptr
|
||||
};
|
||||
cs.p_callstack = &aliaslink;
|
||||
cs_do_and_cleanup(std::move(body), [&]() {
|
||||
if (prevstack) {
|
||||
prevstack->usedargs = aliaslink.usedargs;
|
||||
}
|
||||
cs.p_callstack = aliaslink.next;
|
||||
int argmask2 = cs.p_callstack->usedargs;
|
||||
for (int i = 0; argmask2; argmask2 >>= 1, ++i) {
|
||||
if (argmask2 & 1) {
|
||||
cs_alias_internal::redo_arg(
|
||||
static_cast<cs_alias *>(cs.p_state->identmap[i]), argstack[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cs_bcode *cs_copy_code(cs_bcode *c);
|
||||
|
||||
} /* namespace cscript */
|
||||
|
||||
#endif /* LIBCUBESCRIPT_CS_VM_HH */
|
||||
|
|
File diff suppressed because it is too large
Load Diff
382
src/lib_base.cc
382
src/lib_base.cc
|
@ -1,382 +0,0 @@
|
|||
#include <cubescript/cubescript.hh>
|
||||
|
||||
#include <iterator>
|
||||
|
||||
#include "cs_std.hh"
|
||||
#include "cs_ident.hh"
|
||||
#include "cs_thread.hh"
|
||||
#include "cs_error.hh"
|
||||
|
||||
namespace cubescript {
|
||||
|
||||
static inline void do_loop(
|
||||
state &cs, ident &id, integer_type offset, integer_type n, integer_type step,
|
||||
bcode_ref &&cond, bcode_ref &&body
|
||||
) {
|
||||
if (n <= 0) {
|
||||
return;
|
||||
}
|
||||
alias_local st{cs, id};
|
||||
any_value idv{};
|
||||
for (integer_type i = 0; i < n; ++i) {
|
||||
idv.set_integer(offset + i * step);
|
||||
st.set(idv);
|
||||
if (cond && !cond.call(cs).get_bool()) {
|
||||
break;
|
||||
}
|
||||
switch (body.call_loop(cs)) {
|
||||
case loop_state::BREAK:
|
||||
return;
|
||||
default: /* continue and normal */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void do_loop_conc(
|
||||
state &cs, any_value &res, ident &id, integer_type offset, integer_type n,
|
||||
integer_type step, bcode_ref &&body, bool space
|
||||
) {
|
||||
if (n <= 0) {
|
||||
return;
|
||||
}
|
||||
alias_local st{cs, id};
|
||||
charbuf s{cs};
|
||||
any_value idv{};
|
||||
for (integer_type i = 0; i < n; ++i) {
|
||||
idv.set_integer(offset + i * step);
|
||||
st.set(idv);
|
||||
any_value v{};
|
||||
switch (body.call_loop(cs, v)) {
|
||||
case loop_state::BREAK:
|
||||
goto end;
|
||||
case loop_state::CONTINUE:
|
||||
continue;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (space && i) {
|
||||
s.push_back(' ');
|
||||
}
|
||||
s.append(v.get_string(cs));
|
||||
}
|
||||
end:
|
||||
res.set_string(s.str(), cs);
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT void std_init_base(state &gcs) {
|
||||
new_cmd_quiet(gcs, "error", "s", [](auto &cs, auto args, auto &) {
|
||||
throw error{cs, args[0].get_string(cs)};
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "pcall", "bvvvb", [](auto &cs, auto args, auto &ret) {
|
||||
auto &ts = state_p{cs}.ts();
|
||||
auto &cret = args[1].get_ident(cs);
|
||||
if (cret.type() != ident_type::ALIAS) {
|
||||
throw error_p::make(cs, "'%s' is not an alias", cret.name().data());
|
||||
}
|
||||
auto *ra = static_cast<alias *>(&cret);
|
||||
any_value result{};
|
||||
try {
|
||||
result = args[0].get_code().call(cs);
|
||||
} catch (error const &e) {
|
||||
auto val = any_value{e.what(), cs};
|
||||
auto tb = any_value{};
|
||||
val.set_string(e.what(), cs);
|
||||
ts.get_astack(ra).set_alias(ra, ts, val);
|
||||
if (auto nds = e.stack(); !nds.empty()) {
|
||||
auto bc = args[4].get_code();
|
||||
if (!bc.empty()) {
|
||||
alias_local ist{cs, args[2].get_ident(cs)};
|
||||
alias_local vst{cs, args[3].get_ident(cs)};
|
||||
any_value idv{};
|
||||
for (auto &nd: nds) {
|
||||
idv.set_integer(integer_type(nd.index));
|
||||
ist.set(idv);
|
||||
idv.set_string(nd.id.name().data(), cs);
|
||||
vst.set(idv);
|
||||
bc.call(cs);
|
||||
}
|
||||
}
|
||||
}
|
||||
ret.set_integer(0);
|
||||
return;
|
||||
}
|
||||
ret.set_integer(1);
|
||||
ts.get_astack(ra).set_alias(ra, ts, result);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "assert", "ss#", [](auto &s, auto args, auto &ret) {
|
||||
auto val = args[0];
|
||||
val.force_code(s);
|
||||
if (!val.get_code().call(s).get_bool()) {
|
||||
if (args[2].get_integer() > 1) {
|
||||
throw error_p::make(
|
||||
s, "assertion failed: [%s] (%s)",
|
||||
args[0].get_string(s).data(), args[1].get_string(s).data()
|
||||
);
|
||||
} else {
|
||||
throw error_p::make(
|
||||
s, "assertion failed: [%s]",
|
||||
args[0].get_string(s).data()
|
||||
);
|
||||
}
|
||||
}
|
||||
ret = std::move(args[0]);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "?", "aaa", [](auto &, auto args, auto &res) {
|
||||
if (args[0].get_bool()) {
|
||||
res = std::move(args[1]);
|
||||
} else {
|
||||
res = std::move(args[2]);
|
||||
}
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "cond", "bb2...", [](auto &cs, auto args, auto &res) {
|
||||
for (size_t i = 0; i < args.size(); i += 2) {
|
||||
if ((i + 1) < args.size()) {
|
||||
if (args[i].get_code().call(cs).get_bool()) {
|
||||
res = args[i + 1].get_code().call(cs);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
res = args[i].get_code().call(cs);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "case", "iab2...", [](auto &cs, auto args, auto &res) {
|
||||
integer_type val = args[0].get_integer();
|
||||
for (size_t i = 1; (i + 1) < args.size(); i += 2) {
|
||||
if (
|
||||
(args[i].type() == value_type::NONE) ||
|
||||
(args[i].get_integer() == val)
|
||||
) {
|
||||
res = args[i + 1].get_code().call(cs);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "casef", "fab2...", [](auto &cs, auto args, auto &res) {
|
||||
float_type val = args[0].get_float();
|
||||
for (size_t i = 1; (i + 1) < args.size(); i += 2) {
|
||||
if (
|
||||
(args[i].type() == value_type::NONE) ||
|
||||
(args[i].get_float() == val)
|
||||
) {
|
||||
res = args[i + 1].get_code().call(cs);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "cases", "sab2...", [](auto &cs, auto args, auto &res) {
|
||||
string_ref val = args[0].get_string(cs);
|
||||
for (size_t i = 1; (i + 1) < args.size(); i += 2) {
|
||||
if (
|
||||
(args[i].type() == value_type::NONE) ||
|
||||
(args[i].get_string(cs) == val)
|
||||
) {
|
||||
res = args[i + 1].get_code().call(cs);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "pushif", "vab", [](auto &cs, auto args, auto &res) {
|
||||
alias_local st{cs, args[0]};
|
||||
if (st.get_alias().is_arg()) {
|
||||
throw error{cs, "cannot push an argument"};
|
||||
}
|
||||
if (args[1].get_bool()) {
|
||||
st.set(args[1]);
|
||||
res = args[2].get_code().call(cs);
|
||||
}
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "loop", "vab", [](auto &cs, auto args, auto &) {
|
||||
do_loop(
|
||||
cs, args[0].get_ident(cs), 0, args[1].get_integer(), 1,
|
||||
bcode_ref{}, args[2].get_code()
|
||||
);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "loop+", "viib", [](auto &cs, auto args, auto &) {
|
||||
do_loop(
|
||||
cs, args[0].get_ident(cs), args[1].get_integer(),
|
||||
args[2].get_integer(), 1, bcode_ref{}, args[3].get_code()
|
||||
);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "loop*", "viib", [](auto &cs, auto args, auto &) {
|
||||
do_loop(
|
||||
cs, args[0].get_ident(cs), 0, args[1].get_integer(),
|
||||
args[2].get_integer(), bcode_ref{}, args[3].get_code()
|
||||
);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "loop+*", "viiib", [](auto &cs, auto args, auto &) {
|
||||
do_loop(
|
||||
cs, args[0].get_ident(cs), args[1].get_integer(),
|
||||
args[3].get_integer(), args[2].get_integer(),
|
||||
bcode_ref{}, args[4].get_code()
|
||||
);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "loopwhile", "vibb", [](auto &cs, auto args, auto &) {
|
||||
do_loop(
|
||||
cs, args[0].get_ident(cs), 0, args[1].get_integer(), 1,
|
||||
args[2].get_code(), args[3].get_code()
|
||||
);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "loopwhile+", "viibb", [](auto &cs, auto args, auto &) {
|
||||
do_loop(
|
||||
cs, args[0].get_ident(cs), args[1].get_integer(),
|
||||
args[2].get_integer(), 1, args[3].get_code(), args[4].get_code()
|
||||
);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "loopwhile*", "viibb", [](auto &cs, auto args, auto &) {
|
||||
do_loop(
|
||||
cs, args[0].get_ident(cs), 0, args[2].get_integer(),
|
||||
args[1].get_integer(), args[3].get_code(), args[4].get_code()
|
||||
);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "loopwhile+*", "viiibb", [](
|
||||
auto &cs, auto args, auto &
|
||||
) {
|
||||
do_loop(
|
||||
cs, args[0].get_ident(cs), args[1].get_integer(),
|
||||
args[3].get_integer(), args[2].get_integer(), args[4].get_code(),
|
||||
args[5].get_code()
|
||||
);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "while", "bb", [](auto &cs, auto args, auto &) {
|
||||
auto cond = args[0].get_code();
|
||||
auto body = args[1].get_code();
|
||||
while (cond.call(cs).get_bool()) {
|
||||
switch (body.call_loop(cs)) {
|
||||
case loop_state::BREAK:
|
||||
goto end;
|
||||
default: /* continue and normal */
|
||||
break;
|
||||
}
|
||||
}
|
||||
end:
|
||||
return;
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "loopconcat", "vib", [](
|
||||
auto &cs, auto args, auto &res
|
||||
) {
|
||||
do_loop_conc(
|
||||
cs, res, args[0].get_ident(cs), 0, args[1].get_integer(), 1,
|
||||
args[2].get_code(), true
|
||||
);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "loopconcat+", "viib", [](
|
||||
auto &cs, auto args, auto &res
|
||||
) {
|
||||
do_loop_conc(
|
||||
cs, res, args[0].get_ident(cs), args[1].get_integer(),
|
||||
args[2].get_integer(), 1, args[3].get_code(), true
|
||||
);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "loopconcat*", "viib", [](
|
||||
auto &cs, auto args, auto &res
|
||||
) {
|
||||
do_loop_conc(
|
||||
cs, res, args[0].get_ident(cs), 0, args[2].get_integer(),
|
||||
args[1].get_integer(), args[3].get_code(), true
|
||||
);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "loopconcat+*", "viiib", [](
|
||||
auto &cs, auto args, auto &res
|
||||
) {
|
||||
do_loop_conc(
|
||||
cs, res, args[0].get_ident(cs), args[1].get_integer(),
|
||||
args[3].get_integer(), args[2].get_integer(),
|
||||
args[4].get_code(), true
|
||||
);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "loopconcatword", "vib", [](
|
||||
auto &cs, auto args, auto &res
|
||||
) {
|
||||
do_loop_conc(
|
||||
cs, res, args[0].get_ident(cs), 0, args[1].get_integer(), 1,
|
||||
args[2].get_code(), false
|
||||
);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "loopconcatword+", "viib", [](
|
||||
auto &cs, auto args, auto &res
|
||||
) {
|
||||
do_loop_conc(
|
||||
cs, res, args[0].get_ident(cs), args[1].get_integer(),
|
||||
args[2].get_integer(), 1, args[3].get_code(), false
|
||||
);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "loopconcatword*", "viib", [](
|
||||
auto &cs, auto args, auto &res
|
||||
) {
|
||||
do_loop_conc(
|
||||
cs, res, args[0].get_ident(cs), 0, args[2].get_integer(),
|
||||
args[1].get_integer(), args[3].get_code(), false
|
||||
);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "loopconcatword+*", "viiib", [](
|
||||
auto &cs, auto args, auto &res
|
||||
) {
|
||||
do_loop_conc(
|
||||
cs, res, args[0].get_ident(cs), args[1].get_integer(),
|
||||
args[3].get_integer(), args[2].get_integer(),
|
||||
args[4].get_code(), false
|
||||
);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "push", "vab", [](auto &cs, auto args, auto &res) {
|
||||
alias_local st{cs, args[0]};
|
||||
if (st.get_alias().is_arg()) {
|
||||
throw error{cs, "cannot push an argument"};
|
||||
}
|
||||
st.set(args[1]);
|
||||
res = args[2].get_code().call(cs);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "resetvar", "s", [](auto &cs, auto args, auto &) {
|
||||
cs.reset_value(args[0].get_string(cs));
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "alias", "sa", [](auto &cs, auto args, auto &) {
|
||||
cs.assign_value(args[0].get_string(cs), args[1]);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "identexists", "s", [](auto &cs, auto args, auto &res) {
|
||||
res.set_integer(cs.get_ident(args[0].get_string(cs)) != std::nullopt);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "getalias", "s", [](auto &cs, auto args, auto &res) {
|
||||
auto &id = cs.new_ident(args[0].get_string(cs));
|
||||
if (id.type() != ident_type::ALIAS) {
|
||||
throw error_p::make(cs, "'%s' is not an alias", id.name().data());
|
||||
}
|
||||
if (ident_p{id}.impl().p_flags & IDENT_FLAG_UNKNOWN) {
|
||||
return;
|
||||
}
|
||||
res = static_cast<alias &>(id).value(cs);
|
||||
});
|
||||
}
|
||||
|
||||
} /* namespace cubescript */
|
595
src/lib_list.cc
595
src/lib_list.cc
|
@ -1,46 +1,43 @@
|
|||
#include <functional>
|
||||
#include <iterator>
|
||||
|
||||
#include <cubescript/cubescript.hh>
|
||||
#include "cs_std.hh"
|
||||
#include "cs_parser.hh"
|
||||
#include "cs_thread.hh"
|
||||
#include "cs_util.hh"
|
||||
|
||||
namespace cubescript {
|
||||
namespace cscript {
|
||||
|
||||
template<typename T>
|
||||
struct arg_val;
|
||||
struct cs_arg_val;
|
||||
|
||||
template<>
|
||||
struct arg_val<integer_type> {
|
||||
static integer_type get(any_value &tv, state &) {
|
||||
return tv.get_integer();
|
||||
struct cs_arg_val<cs_int> {
|
||||
static cs_int get(cs_value &tv) {
|
||||
return tv.get_int();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct arg_val<float_type> {
|
||||
static float_type get(any_value &tv, state &) {
|
||||
struct cs_arg_val<cs_float> {
|
||||
static cs_float get(cs_value &tv) {
|
||||
return tv.get_float();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct arg_val<std::string_view> {
|
||||
static std::string_view get(any_value &tv, state &cs) {
|
||||
return tv.get_string(cs);
|
||||
struct cs_arg_val<ostd::string_range> {
|
||||
static ostd::string_range get(cs_value &tv) {
|
||||
return tv.get_strr();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename F>
|
||||
static inline void list_find(
|
||||
state &cs, span_type<any_value> args, any_value &res, F cmp
|
||||
static inline void cs_list_find(
|
||||
cs_state &cs, cs_value_r args, cs_value &res, F cmp
|
||||
) {
|
||||
integer_type n = 0, skip = args[2].get_integer();
|
||||
T val = arg_val<T>::get(args[1], cs);
|
||||
for (list_parser p{cs, args[0].get_string(cs)}; p.parse(); ++n) {
|
||||
cs_int n = 0, skip = args[2].get_int();
|
||||
T val = cs_arg_val<T>::get(args[1]);
|
||||
for (util::list_parser p(cs, args[0].get_strr()); p.parse(); ++n) {
|
||||
if (cmp(p, val)) {
|
||||
res.set_integer(n);
|
||||
res.set_int(n);
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < skip; ++i) {
|
||||
|
@ -51,18 +48,18 @@ static inline void list_find(
|
|||
}
|
||||
}
|
||||
notfound:
|
||||
res.set_integer(-1);
|
||||
res.set_int(-1);
|
||||
}
|
||||
|
||||
template<typename T, typename F>
|
||||
static inline void list_assoc(
|
||||
state &cs, span_type<any_value> args, any_value &res, F cmp
|
||||
static inline void cs_list_assoc(
|
||||
cs_state &cs, cs_value_r args, cs_value &res, F cmp
|
||||
) {
|
||||
T val = arg_val<T>::get(args[1], cs);
|
||||
for (list_parser p{cs, args[0].get_string(cs)}; p.parse();) {
|
||||
T val = cs_arg_val<T>::get(args[1]);
|
||||
for (util::list_parser p(cs, args[0].get_strr()); p.parse();) {
|
||||
if (cmp(p, val)) {
|
||||
if (p.parse()) {
|
||||
res.set_string(p.get_item());
|
||||
res.set_str(p.get_item());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -72,41 +69,43 @@ static inline void list_assoc(
|
|||
}
|
||||
}
|
||||
|
||||
static void loop_list_conc(
|
||||
state &cs, any_value &res, ident &id, std::string_view list,
|
||||
bcode_ref &&body, bool space
|
||||
static void cs_loop_list_conc(
|
||||
cs_state &cs, cs_value &res, cs_ident *id, ostd::string_range list,
|
||||
cs_bcode *body, bool space
|
||||
) {
|
||||
alias_local st{cs, id};
|
||||
any_value idv{};
|
||||
charbuf r{cs};
|
||||
cs_stacked_value idv{id};
|
||||
if (!idv.has_alias()) {
|
||||
return;
|
||||
}
|
||||
cs_string r;
|
||||
int n = 0;
|
||||
for (list_parser p{cs, list}; p.parse(); ++n) {
|
||||
idv.set_string(p.get_item());
|
||||
st.set(std::move(idv));
|
||||
for (util::list_parser p(cs, list); p.parse(); ++n) {
|
||||
idv.set_str(p.get_item());
|
||||
idv.push();
|
||||
if (n && space) {
|
||||
r.push_back(' ');
|
||||
r += ' ';
|
||||
}
|
||||
any_value v{};
|
||||
switch (body.call_loop(cs, v)) {
|
||||
case loop_state::BREAK:
|
||||
cs_value v;
|
||||
switch (cs.run_loop(body, v)) {
|
||||
case CsLoopState::Break:
|
||||
goto end;
|
||||
case loop_state::CONTINUE:
|
||||
case CsLoopState::Continue:
|
||||
continue;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
r.append(v.get_string(cs));
|
||||
r += v.get_str();
|
||||
}
|
||||
end:
|
||||
res.set_string(r.str(), cs);
|
||||
res.set_str(std::move(r));
|
||||
}
|
||||
|
||||
int list_includes(
|
||||
state &cs, std::string_view list, std::string_view needle
|
||||
int cs_list_includes(
|
||||
cs_state &cs, ostd::string_range list, ostd::string_range needle
|
||||
) {
|
||||
int offset = 0;
|
||||
for (list_parser p{cs, list}; p.parse();) {
|
||||
if (p.raw_item() == needle) {
|
||||
for (util::list_parser p(cs, list); p.parse();) {
|
||||
if (p.get_raw_item() == needle) {
|
||||
return offset;
|
||||
}
|
||||
++offset;
|
||||
|
@ -115,124 +114,122 @@ int list_includes(
|
|||
}
|
||||
|
||||
template<bool PushList, bool Swap, typename F>
|
||||
static inline void list_merge(
|
||||
state &cs, span_type<any_value> args, any_value &res, F cmp
|
||||
static inline void cs_list_merge(
|
||||
cs_state &cs, cs_value_r args, cs_value &res, F cmp
|
||||
) {
|
||||
std::string_view list = args[0].get_string(cs);
|
||||
std::string_view elems = args[1].get_string(cs);
|
||||
charbuf buf{cs};
|
||||
ostd::string_range list = args[0].get_strr();
|
||||
ostd::string_range elems = args[1].get_strr();
|
||||
cs_string buf;
|
||||
if (PushList) {
|
||||
buf.append(list);
|
||||
buf += list;
|
||||
}
|
||||
if (Swap) {
|
||||
std::swap(list, elems);
|
||||
}
|
||||
for (list_parser p{cs, list}; p.parse();) {
|
||||
if (cmp(list_includes(cs, elems, p.raw_item()), 0)) {
|
||||
for (util::list_parser p(cs, list); p.parse();) {
|
||||
if (cmp(cs_list_includes(cs, elems, p.get_raw_item()), 0)) {
|
||||
if (!buf.empty()) {
|
||||
buf.push_back(' ');
|
||||
buf += ' ';
|
||||
}
|
||||
buf.append(p.quoted_item());
|
||||
buf += p.get_raw_item(true);
|
||||
}
|
||||
}
|
||||
res.set_string(buf.str(), cs);
|
||||
res.set_str(std::move(buf));
|
||||
}
|
||||
|
||||
static void init_lib_list_sort(state &cs);
|
||||
static void cs_init_lib_list_sort(cs_state &cs);
|
||||
|
||||
LIBCUBESCRIPT_EXPORT void std_init_list(state &gcs) {
|
||||
new_cmd_quiet(gcs, "listlen", "s", [](auto &cs, auto args, auto &res) {
|
||||
res.set_integer(
|
||||
integer_type(list_parser{cs, args[0].get_string(cs)}.count())
|
||||
);
|
||||
void cs_init_lib_list(cs_state &gcs) {
|
||||
gcs.new_command("listlen", "s", [](auto &cs, auto args, auto &res) {
|
||||
res.set_int(cs_int(util::list_parser(cs, args[0].get_strr()).count()));
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "at", "si1...", [](auto &cs, auto args, auto &res) {
|
||||
gcs.new_command("at", "si1V", [](auto &cs, auto args, auto &res) {
|
||||
if (args.empty()) {
|
||||
return;
|
||||
}
|
||||
if (args.size() <= 1) {
|
||||
res = args[0];
|
||||
return;
|
||||
}
|
||||
auto str = args[0].get_string(cs);
|
||||
list_parser p{cs, str};
|
||||
cs_string str = std::move(args[0].get_str());
|
||||
util::list_parser p(cs, str);
|
||||
p.get_raw_item() = str;
|
||||
for (size_t i = 1; i < args.size(); ++i) {
|
||||
p.set_input(str);
|
||||
integer_type pos = args[i].get_integer();
|
||||
p.get_input() = str;
|
||||
cs_int pos = args[i].get_int();
|
||||
for (; pos > 0; --pos) {
|
||||
if (!p.parse()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pos > 0 || !p.parse()) {
|
||||
p.set_input("");
|
||||
p.get_raw_item() = p.get_raw_item(true) = ostd::string_range();
|
||||
}
|
||||
}
|
||||
res.set_string(p.get_item());
|
||||
res.set_str(p.get_item());
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "sublist", "sii#", [](auto &cs, auto args, auto &res) {
|
||||
integer_type skip = args[1].get_integer(),
|
||||
count = args[2].get_integer(),
|
||||
numargs = args[3].get_integer();
|
||||
gcs.new_command("sublist", "siiN", [](auto &cs, auto args, auto &res) {
|
||||
cs_int skip = args[1].get_int(),
|
||||
count = args[2].get_int(),
|
||||
numargs = args[3].get_int();
|
||||
|
||||
integer_type offset = std::max(skip, integer_type(0)),
|
||||
len = (numargs >= 3) ? std::max(count, integer_type(0)) : -1;
|
||||
cs_int offset = std::max(skip, cs_int(0)),
|
||||
len = (numargs >= 3) ? std::max(count, cs_int(0)) : -1;
|
||||
|
||||
list_parser p{cs, args[0].get_string(cs)};
|
||||
for (integer_type i = 0; i < offset; ++i) {
|
||||
util::list_parser p(cs, args[0].get_strr());
|
||||
for (cs_int i = 0; i < offset; ++i) {
|
||||
if (!p.parse()) break;
|
||||
}
|
||||
if (len < 0) {
|
||||
if (offset > 0) {
|
||||
p.skip_until_item();
|
||||
p.skip();
|
||||
}
|
||||
res.set_string(p.input(), cs);
|
||||
res.set_str(cs_string{p.get_input()});
|
||||
return;
|
||||
}
|
||||
|
||||
char const *list = p.input().data();
|
||||
char const *list = p.get_input().data();
|
||||
p.get_raw_item(true) = ostd::string_range();
|
||||
if (len > 0 && p.parse()) {
|
||||
while (--len > 0 && p.parse());
|
||||
} else {
|
||||
res.set_string("", cs);
|
||||
return;
|
||||
}
|
||||
auto quote = p.quoted_item();
|
||||
auto *qend = "e[quote.size()];
|
||||
res.set_string(make_str_view(list, qend), cs);
|
||||
ostd::string_range quote = p.get_raw_item(true);
|
||||
char const *qend = !quote.empty() ? "e[quote.size()] : list;
|
||||
res.set_str(cs_string{list, size_t(qend - list)});
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "listfind", "vsb", [](auto &cs, auto args, auto &res) {
|
||||
alias_local st{cs, args[0]};
|
||||
any_value idv{};
|
||||
gcs.new_command("listfind", "rse", [](auto &cs, auto args, auto &res) {
|
||||
cs_stacked_value idv{args[0].get_ident()};
|
||||
if (!idv.has_alias()) {
|
||||
res.set_int(-1);
|
||||
return;
|
||||
}
|
||||
auto body = args[2].get_code();
|
||||
int n = -1;
|
||||
for (list_parser p{cs, args[1].get_string(cs)}; p.parse();) {
|
||||
for (util::list_parser p(cs, args[1].get_strr()); p.parse();) {
|
||||
++n;
|
||||
idv.set_string(p.raw_item(), cs);
|
||||
st.set(std::move(idv));
|
||||
if (body.call(cs).get_bool()) {
|
||||
res.set_integer(integer_type(n));
|
||||
idv.set_str(cs_string{p.get_raw_item()});
|
||||
idv.push();
|
||||
if (cs.run_bool(body)) {
|
||||
res.set_int(cs_int(n));
|
||||
return;
|
||||
}
|
||||
}
|
||||
res.set_integer(-1);
|
||||
res.set_int(-1);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "listassoc", "vsb", [](auto &cs, auto args, auto &res) {
|
||||
alias_local st{cs, args[0]};
|
||||
any_value idv{};
|
||||
gcs.new_command("listassoc", "rse", [](auto &cs, auto args, auto &res) {
|
||||
cs_stacked_value idv{args[0].get_ident()};
|
||||
if (!idv.has_alias()) {
|
||||
return;
|
||||
}
|
||||
auto body = args[2].get_code();
|
||||
int n = -1;
|
||||
for (list_parser p{cs, args[1].get_string(cs)}; p.parse();) {
|
||||
for (util::list_parser p(cs, args[1].get_strr()); p.parse();) {
|
||||
++n;
|
||||
idv.set_string(p.raw_item(), cs);
|
||||
st.set(std::move(idv));
|
||||
if (body.call(cs).get_bool()) {
|
||||
idv.set_str(cs_string{p.get_raw_item()});
|
||||
idv.push();
|
||||
if (cs.run_bool(body)) {
|
||||
if (p.parse()) {
|
||||
res.set_string(p.get_item());
|
||||
res.set_str(p.get_item());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -242,330 +239,341 @@ LIBCUBESCRIPT_EXPORT void std_init_list(state &gcs) {
|
|||
}
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "listfind=", "i", [](auto &cs, auto args, auto &res) {
|
||||
list_find<integer_type>(
|
||||
cs, args, res, [](list_parser const &p, integer_type val) {
|
||||
return parse_int(p.raw_item()) == val;
|
||||
gcs.new_command("listfind=", "i", [](auto &cs, auto args, auto &res) {
|
||||
cs_list_find<cs_int>(
|
||||
cs, args, res, [](const util::list_parser &p, cs_int val) {
|
||||
return cs_parse_int(p.get_raw_item()) == val;
|
||||
}
|
||||
);
|
||||
});
|
||||
new_cmd_quiet(gcs, "listfind=f", "f", [](auto &cs, auto args, auto &res) {
|
||||
list_find<float_type>(
|
||||
cs, args, res, [](list_parser const &p, float_type val) {
|
||||
return parse_float(p.raw_item()) == val;
|
||||
gcs.new_command("listfind=f", "f", [](auto &cs, auto args, auto &res) {
|
||||
cs_list_find<cs_float>(
|
||||
cs, args, res, [](const util::list_parser &p, cs_float val) {
|
||||
return cs_parse_float(p.get_raw_item()) == val;
|
||||
}
|
||||
);
|
||||
});
|
||||
new_cmd_quiet(gcs, "listfind=s", "s", [](auto &cs, auto args, auto &res) {
|
||||
list_find<std::string_view>(
|
||||
cs, args, res, [](list_parser const &p, std::string_view val) {
|
||||
return p.raw_item() == val;
|
||||
gcs.new_command("listfind=s", "s", [](auto &cs, auto args, auto &res) {
|
||||
cs_list_find<ostd::string_range>(
|
||||
cs, args, res, [](const util::list_parser &p, ostd::string_range val) {
|
||||
return p.get_raw_item() == val;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "listassoc=", "i", [](auto &cs, auto args, auto &res) {
|
||||
list_assoc<integer_type>(
|
||||
cs, args, res, [](list_parser const &p, integer_type val) {
|
||||
return parse_int(p.raw_item()) == val;
|
||||
gcs.new_command("listassoc=", "i", [](auto &cs, auto args, auto &res) {
|
||||
cs_list_assoc<cs_int>(
|
||||
cs, args, res, [](const util::list_parser &p, cs_int val) {
|
||||
return cs_parse_int(p.get_raw_item()) == val;
|
||||
}
|
||||
);
|
||||
});
|
||||
new_cmd_quiet(gcs, "listassoc=f", "f", [](auto &cs, auto args, auto &res) {
|
||||
list_assoc<float_type>(
|
||||
cs, args, res, [](list_parser const &p, float_type val) {
|
||||
return parse_float(p.raw_item()) == val;
|
||||
gcs.new_command("listassoc=f", "f", [](auto &cs, auto args, auto &res) {
|
||||
cs_list_assoc<cs_float>(
|
||||
cs, args, res, [](const util::list_parser &p, cs_float val) {
|
||||
return cs_parse_float(p.get_raw_item()) == val;
|
||||
}
|
||||
);
|
||||
});
|
||||
new_cmd_quiet(gcs, "listassoc=s", "s", [](auto &cs, auto args, auto &res) {
|
||||
list_assoc<std::string_view>(
|
||||
cs, args, res, [](list_parser const &p, std::string_view val) {
|
||||
return p.raw_item() == val;
|
||||
gcs.new_command("listassoc=s", "s", [](auto &cs, auto args, auto &res) {
|
||||
cs_list_assoc<ostd::string_range>(
|
||||
cs, args, res, [](const util::list_parser &p, ostd::string_range val) {
|
||||
return p.get_raw_item() == val;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "looplist", "vsb", [](auto &cs, auto args, auto &) {
|
||||
alias_local st{cs, args[0]};
|
||||
any_value idv{};
|
||||
gcs.new_command("looplist", "rse", [](auto &cs, auto args, auto &) {
|
||||
cs_stacked_value idv{args[0].get_ident()};
|
||||
if (!idv.has_alias()) {
|
||||
return;
|
||||
}
|
||||
auto body = args[2].get_code();
|
||||
int n = 0;
|
||||
for (list_parser p{cs, args[1].get_string(cs)}; p.parse(); ++n) {
|
||||
idv.set_string(p.get_item());
|
||||
st.set(std::move(idv));
|
||||
switch (body.call_loop(cs)) {
|
||||
case loop_state::BREAK:
|
||||
return;
|
||||
for (util::list_parser p(cs, args[1].get_strr()); p.parse(); ++n) {
|
||||
idv.set_str(p.get_item());
|
||||
idv.push();
|
||||
switch (cs.run_loop(body)) {
|
||||
case CsLoopState::Break:
|
||||
goto end;
|
||||
default: /* continue and normal */
|
||||
break;
|
||||
}
|
||||
}
|
||||
end:
|
||||
return;
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "looplist2", "vvsb", [](auto &cs, auto args, auto &) {
|
||||
alias_local st1{cs, args[0]};
|
||||
alias_local st2{cs, args[1]};
|
||||
any_value idv{};
|
||||
gcs.new_command("looplist2", "rrse", [](auto &cs, auto args, auto &) {
|
||||
cs_stacked_value idv1{args[0].get_ident()}, idv2{args[1].get_ident()};
|
||||
if (!idv1.has_alias() || !idv2.has_alias()) {
|
||||
return;
|
||||
}
|
||||
auto body = args[3].get_code();
|
||||
int n = 0;
|
||||
for (list_parser p{cs, args[2].get_string(cs)}; p.parse(); n += 2) {
|
||||
idv.set_string(p.get_item());
|
||||
st1.set(std::move(idv));
|
||||
for (util::list_parser p(cs, args[2].get_strr()); p.parse(); n += 2) {
|
||||
idv1.set_str(p.get_item());
|
||||
if (p.parse()) {
|
||||
idv.set_string(p.get_item());
|
||||
idv2.set_str(p.get_item());
|
||||
} else {
|
||||
idv.set_string("", cs);
|
||||
idv2.set_str("");
|
||||
}
|
||||
st2.set(std::move(idv));
|
||||
switch (body.call_loop(cs)) {
|
||||
case loop_state::BREAK:
|
||||
return;
|
||||
idv1.push();
|
||||
idv2.push();
|
||||
switch (cs.run_loop(body)) {
|
||||
case CsLoopState::Break:
|
||||
goto end;
|
||||
default: /* continue and normal */
|
||||
break;
|
||||
}
|
||||
}
|
||||
end:
|
||||
return;
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "looplist3", "vvvsb", [](auto &cs, auto args, auto &) {
|
||||
alias_local st1{cs, args[0]};
|
||||
alias_local st2{cs, args[1]};
|
||||
alias_local st3{cs, args[2]};
|
||||
any_value idv{};
|
||||
gcs.new_command("looplist3", "rrrse", [](auto &cs, auto args, auto &) {
|
||||
cs_stacked_value idv1{args[0].get_ident()};
|
||||
cs_stacked_value idv2{args[1].get_ident()};
|
||||
cs_stacked_value idv3{args[2].get_ident()};
|
||||
if (!idv1.has_alias() || !idv2.has_alias() || !idv3.has_alias()) {
|
||||
return;
|
||||
}
|
||||
auto body = args[4].get_code();
|
||||
int n = 0;
|
||||
for (list_parser p{cs, args[3].get_string(cs)}; p.parse(); n += 3) {
|
||||
idv.set_string(p.get_item());
|
||||
st1.set(std::move(idv));
|
||||
for (util::list_parser p(cs, args[3].get_strr()); p.parse(); n += 3) {
|
||||
idv1.set_str(p.get_item());
|
||||
if (p.parse()) {
|
||||
idv.set_string(p.get_item());
|
||||
idv2.set_str(p.get_item());
|
||||
} else {
|
||||
idv.set_string("", cs);
|
||||
idv2.set_str("");
|
||||
}
|
||||
st2.set(std::move(idv));
|
||||
if (p.parse()) {
|
||||
idv.set_string(p.get_item());
|
||||
idv3.set_str(p.get_item());
|
||||
} else {
|
||||
idv.set_string("", cs);
|
||||
idv3.set_str("");
|
||||
}
|
||||
st3.set(std::move(idv));
|
||||
switch (body.call_loop(cs)) {
|
||||
case loop_state::BREAK:
|
||||
return;
|
||||
idv1.push();
|
||||
idv2.push();
|
||||
idv3.push();
|
||||
switch (cs.run_loop(body)) {
|
||||
case CsLoopState::Break:
|
||||
goto end;
|
||||
default: /* continue and normal */
|
||||
break;
|
||||
}
|
||||
}
|
||||
end:
|
||||
return;
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "looplistconcat", "vsb", [](
|
||||
auto &cs, auto args, auto &res
|
||||
) {
|
||||
loop_list_conc(
|
||||
cs, res, args[0].get_ident(cs), args[1].get_string(cs),
|
||||
gcs.new_command("looplistconcat", "rse", [](auto &cs, auto args, auto &res) {
|
||||
cs_loop_list_conc(
|
||||
cs, res, args[0].get_ident(), args[1].get_strr(),
|
||||
args[2].get_code(), true
|
||||
);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "looplistconcatword", "vsb", [](
|
||||
gcs.new_command("looplistconcatword", "rse", [](
|
||||
auto &cs, auto args, auto &res
|
||||
) {
|
||||
loop_list_conc(
|
||||
cs, res, args[0].get_ident(cs), args[1].get_string(cs),
|
||||
cs_loop_list_conc(
|
||||
cs, res, args[0].get_ident(), args[1].get_strr(),
|
||||
args[2].get_code(), false
|
||||
);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "listfilter", "vsb", [](
|
||||
auto &cs, auto args, auto &res
|
||||
) {
|
||||
alias_local st{cs, args[0]};
|
||||
any_value idv{};
|
||||
gcs.new_command("listfilter", "rse", [](auto &cs, auto args, auto &res) {
|
||||
cs_stacked_value idv{args[0].get_ident()};
|
||||
if (!idv.has_alias()) {
|
||||
return;
|
||||
}
|
||||
auto body = args[2].get_code();
|
||||
charbuf r{cs};
|
||||
cs_string r;
|
||||
int n = 0;
|
||||
for (list_parser p{cs, args[1].get_string(cs)}; p.parse(); ++n) {
|
||||
idv.set_string(p.raw_item(), cs);
|
||||
st.set(std::move(idv));
|
||||
if (body.call(cs).get_bool()) {
|
||||
for (util::list_parser p(cs, args[1].get_strr()); p.parse(); ++n) {
|
||||
idv.set_str(cs_string{p.get_raw_item()});
|
||||
idv.push();
|
||||
if (cs.run_bool(body)) {
|
||||
if (r.size()) {
|
||||
r.push_back(' ');
|
||||
r += ' ';
|
||||
}
|
||||
r.append(p.quoted_item());
|
||||
r += p.get_raw_item(true);
|
||||
}
|
||||
}
|
||||
res.set_string(r.str(), cs);
|
||||
res.set_str(std::move(r));
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "listcount", "vsb", [](auto &cs, auto args, auto &res) {
|
||||
alias_local st{cs, args[0]};
|
||||
any_value idv{};
|
||||
gcs.new_command("listcount", "rse", [](auto &cs, auto args, auto &res) {
|
||||
cs_stacked_value idv{args[0].get_ident()};
|
||||
if (!idv.has_alias()) {
|
||||
return;
|
||||
}
|
||||
auto body = args[2].get_code();
|
||||
int n = 0, r = 0;
|
||||
for (list_parser p{cs, args[1].get_string(cs)}; p.parse(); ++n) {
|
||||
idv.set_string(p.raw_item(), cs);
|
||||
st.set(std::move(idv));
|
||||
if (body.call(cs).get_bool()) {
|
||||
for (util::list_parser p(cs, args[1].get_strr()); p.parse(); ++n) {
|
||||
idv.set_str(cs_string{p.get_raw_item()});
|
||||
idv.push();
|
||||
if (cs.run_bool(body)) {
|
||||
r++;
|
||||
}
|
||||
}
|
||||
res.set_integer(r);
|
||||
res.set_int(r);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "prettylist", "ss", [](auto &cs, auto args, auto &res) {
|
||||
charbuf buf{cs};
|
||||
std::string_view s = args[0].get_string(cs);
|
||||
std::string_view conj = args[1].get_string(cs);
|
||||
list_parser p{cs, s};
|
||||
size_t len = p.count();
|
||||
gcs.new_command("prettylist", "ss", [](auto &cs, auto args, auto &res) {
|
||||
auto buf = ostd::appender<cs_string>();
|
||||
ostd::string_range s = args[0].get_strr();
|
||||
ostd::string_range conj = args[1].get_strr();
|
||||
size_t len = util::list_parser(cs, s).count();
|
||||
size_t n = 0;
|
||||
for (p.set_input(s); p.parse(); ++n) {
|
||||
auto qi = p.quoted_item();
|
||||
if (!qi.empty() && (qi.front() == '"')) {
|
||||
unescape_string(std::back_inserter(buf), p.raw_item());
|
||||
for (util::list_parser p(cs, s); p.parse(); ++n) {
|
||||
if (!p.get_raw_item(true).empty() &&
|
||||
(p.get_raw_item(true).front() == '"')) {
|
||||
util::unescape_string(buf, p.get_raw_item());
|
||||
} else {
|
||||
buf.append(p.raw_item());
|
||||
ostd::range_put_all(buf, p.get_raw_item());
|
||||
}
|
||||
if ((n + 1) < len) {
|
||||
if ((len > 2) || conj.empty()) {
|
||||
buf.push_back(',');
|
||||
buf.put(',');
|
||||
}
|
||||
if ((n + 2 == len) && !conj.empty()) {
|
||||
buf.push_back(' ');
|
||||
buf.append(conj);
|
||||
buf.put(' ');
|
||||
ostd::range_put_all(buf, conj);
|
||||
}
|
||||
buf.push_back(' ');
|
||||
buf.put(' ');
|
||||
}
|
||||
}
|
||||
res.set_string(buf.str(), cs);
|
||||
res.set_str(std::move(buf.get()));
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "indexof", "ss", [](auto &cs, auto args, auto &res) {
|
||||
res.set_integer(
|
||||
list_includes(cs, args[0].get_string(cs), args[1].get_string(cs))
|
||||
gcs.new_command("indexof", "ss", [](auto &cs, auto args, auto &res) {
|
||||
res.set_int(
|
||||
cs_list_includes(cs, args[0].get_strr(), args[1].get_strr())
|
||||
);
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "listdel", "ss", [](auto &cs, auto args, auto &res) {
|
||||
list_merge<false, false>(cs, args, res, std::less<int>());
|
||||
gcs.new_command("listdel", "ss", [](auto &cs, auto args, auto &res) {
|
||||
cs_list_merge<false, false>(cs, args, res, std::less<int>());
|
||||
});
|
||||
new_cmd_quiet(gcs, "listintersect", "ss", [](
|
||||
auto &cs, auto args, auto &res
|
||||
) {
|
||||
list_merge<false, false>(cs, args, res, std::greater_equal<int>());
|
||||
gcs.new_command("listintersect", "ss", [](auto &cs, auto args, auto &res) {
|
||||
cs_list_merge<false, false>(cs, args, res, std::greater_equal<int>());
|
||||
});
|
||||
new_cmd_quiet(gcs, "listunion", "ss", [](auto &cs, auto args, auto &res) {
|
||||
list_merge<true, true>(cs, args, res, std::less<int>());
|
||||
gcs.new_command("listunion", "ss", [](auto &cs, auto args, auto &res) {
|
||||
cs_list_merge<true, true>(cs, args, res, std::less<int>());
|
||||
});
|
||||
|
||||
new_cmd_quiet(gcs, "listsplice", "ssii", [](
|
||||
auto &cs, auto args, auto &res
|
||||
) {
|
||||
integer_type offset = std::max(args[2].get_integer(), integer_type(0));
|
||||
integer_type len = std::max(args[3].get_integer(), integer_type(0));
|
||||
std::string_view s = args[0].get_string(cs);
|
||||
std::string_view vals = args[1].get_string(cs);
|
||||
gcs.new_command("listsplice", "ssii", [](auto &cs, auto args, auto &res) {
|
||||
cs_int offset = std::max(args[2].get_int(), cs_int(0));
|
||||
cs_int len = std::max(args[3].get_int(), cs_int(0));
|
||||
ostd::string_range s = args[0].get_strr();
|
||||
ostd::string_range vals = args[1].get_strr();
|
||||
char const *list = s.data();
|
||||
list_parser p{cs, s};
|
||||
for (integer_type i = 0; i < offset; ++i) {
|
||||
util::list_parser p(cs, s);
|
||||
for (cs_int i = 0; i < offset; ++i) {
|
||||
if (!p.parse()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::string_view quote = p.quoted_item();
|
||||
ostd::string_range quote = p.get_raw_item(true);
|
||||
char const *qend = !quote.empty() ? "e[quote.size()] : list;
|
||||
charbuf buf{cs};
|
||||
cs_string buf;
|
||||
if (qend > list) {
|
||||
buf.append(list, qend);
|
||||
buf += ostd::string_range(list, qend);
|
||||
}
|
||||
if (!vals.empty()) {
|
||||
if (!buf.empty()) {
|
||||
buf.push_back(' ');
|
||||
buf += ' ';
|
||||
}
|
||||
buf.append(vals);
|
||||
buf += vals;
|
||||
}
|
||||
for (integer_type i = 0; i < len; ++i) {
|
||||
for (cs_int i = 0; i < len; ++i) {
|
||||
if (!p.parse()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
p.skip_until_item();
|
||||
if (!p.input().empty()) {
|
||||
switch (p.input().front()) {
|
||||
p.skip();
|
||||
if (!p.get_input().empty()) {
|
||||
switch (p.get_input().front()) {
|
||||
case ')':
|
||||
case ']':
|
||||
break;
|
||||
default:
|
||||
if (!buf.empty()) {
|
||||
buf.push_back(' ');
|
||||
buf += ' ';
|
||||
}
|
||||
buf.append(p.input());
|
||||
buf += p.get_input();
|
||||
break;
|
||||
}
|
||||
}
|
||||
res.set_string(buf.str(), cs);
|
||||
res.set_str(std::move(buf));
|
||||
});
|
||||
|
||||
init_lib_list_sort(gcs);
|
||||
cs_init_lib_list_sort(gcs);
|
||||
}
|
||||
|
||||
struct ListSortItem {
|
||||
std::string_view str;
|
||||
std::string_view quote;
|
||||
ostd::string_range str;
|
||||
ostd::string_range quote;
|
||||
};
|
||||
|
||||
struct ListSortFun {
|
||||
state &cs;
|
||||
alias_local &xst, &yst;
|
||||
bcode_ref const *body;
|
||||
cs_state &cs;
|
||||
cs_stacked_value &xv, &yv;
|
||||
cs_bcode *body;
|
||||
|
||||
bool operator()(ListSortItem const &xval, ListSortItem const &yval) {
|
||||
any_value v{};
|
||||
v.set_string(xval.str, cs);
|
||||
xst.set(std::move(v));
|
||||
v.set_string(yval.str, cs);
|
||||
yst.set(std::move(v));
|
||||
return body->call(cs).get_bool();
|
||||
xv.set_cstr(xval.str);
|
||||
yv.set_cstr(yval.str);
|
||||
xv.push();
|
||||
yv.push();
|
||||
return cs.run_bool(body);
|
||||
}
|
||||
};
|
||||
|
||||
static void list_sort(
|
||||
state &cs, any_value &res, std::string_view list,
|
||||
ident &x, ident &y, bcode_ref &&body, bcode_ref &&unique
|
||||
static void cs_list_sort(
|
||||
cs_state &cs, cs_value &res, ostd::string_range list,
|
||||
cs_ident *x, cs_ident *y, cs_bcode *body, cs_bcode *unique
|
||||
) {
|
||||
if (x == y) {
|
||||
if (x == y || !x->is_alias() || !y->is_alias()) {
|
||||
return;
|
||||
}
|
||||
|
||||
alias_local xst{cs, x}, yst{cs, y};
|
||||
cs_alias *xa = static_cast<cs_alias *>(x), *ya = static_cast<cs_alias *>(y);
|
||||
|
||||
valbuf<ListSortItem> items{state_p{cs}.ts().istate};
|
||||
cs_vector<ListSortItem> items;
|
||||
size_t total = 0;
|
||||
|
||||
for (list_parser p{cs, list}; p.parse();) {
|
||||
ListSortItem item = { p.raw_item(), p.quoted_item() };
|
||||
for (util::list_parser p(cs, list); p.parse();) {
|
||||
ListSortItem item = { p.get_raw_item(), p.get_raw_item(true) };
|
||||
items.push_back(item);
|
||||
total += item.quote.size();
|
||||
}
|
||||
|
||||
if (items.empty()) {
|
||||
res.set_string(list, cs);
|
||||
res.set_str(cs_string{list});
|
||||
return;
|
||||
}
|
||||
|
||||
cs_stacked_value xval{xa}, yval{ya};
|
||||
xval.set_null();
|
||||
yval.set_null();
|
||||
xval.push();
|
||||
yval.push();
|
||||
|
||||
size_t totaluniq = total;
|
||||
size_t nuniq = items.size();
|
||||
if (body) {
|
||||
ListSortFun f = { cs, xst, yst, &body };
|
||||
std::sort(items.buf.begin(), items.buf.end(), f);
|
||||
if (!unique.empty()) {
|
||||
f.body = &unique;
|
||||
ListSortFun f = { cs, xval, yval, body };
|
||||
ostd::sort_cmp(ostd::iter(items), f);
|
||||
if (!cs_code_is_empty(unique)) {
|
||||
f.body = unique;
|
||||
totaluniq = items[0].quote.size();
|
||||
nuniq = 1;
|
||||
for (size_t i = 1; i < items.size(); i++) {
|
||||
ListSortItem &item = items[i];
|
||||
if (f(items[i - 1], item)) {
|
||||
item.quote = std::string_view{};
|
||||
item.quote = nullptr;
|
||||
} else {
|
||||
totaluniq += item.quote.size();
|
||||
++nuniq;
|
||||
|
@ -573,7 +581,7 @@ static void list_sort(
|
|||
}
|
||||
}
|
||||
} else {
|
||||
ListSortFun f = { cs, xst, yst, &unique };
|
||||
ListSortFun f = { cs, xval, yval, unique };
|
||||
totaluniq = items[0].quote.size();
|
||||
nuniq = 1;
|
||||
for (size_t i = 1; i < items.size(); i++) {
|
||||
|
@ -581,7 +589,7 @@ static void list_sort(
|
|||
for (size_t j = 0; j < i; ++j) {
|
||||
ListSortItem &prev = items[j];
|
||||
if (!prev.quote.empty() && f(item, prev)) {
|
||||
item.quote = std::string_view{};
|
||||
item.quote = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -592,7 +600,10 @@ static void list_sort(
|
|||
}
|
||||
}
|
||||
|
||||
charbuf sorted{cs};
|
||||
xval.pop();
|
||||
yval.pop();
|
||||
|
||||
cs_string sorted;
|
||||
sorted.reserve(totaluniq + std::max(nuniq - 1, size_t(0)));
|
||||
for (size_t i = 0; i < items.size(); ++i) {
|
||||
ListSortItem &item = items[i];
|
||||
|
@ -600,30 +611,26 @@ static void list_sort(
|
|||
continue;
|
||||
}
|
||||
if (i) {
|
||||
sorted.push_back(' ');
|
||||
sorted += ' ';
|
||||
}
|
||||
sorted.append(item.quote);
|
||||
sorted += item.quote;
|
||||
}
|
||||
res.set_string(sorted.str(), cs);
|
||||
res.set_str(std::move(sorted));
|
||||
}
|
||||
|
||||
static void init_lib_list_sort(state &gcs) {
|
||||
new_cmd_quiet(gcs, "sortlist", "svvbb", [](
|
||||
auto &cs, auto args, auto &res
|
||||
) {
|
||||
list_sort(
|
||||
cs, res, args[0].get_string(cs), args[1].get_ident(cs),
|
||||
args[2].get_ident(cs), args[3].get_code(), args[4].get_code()
|
||||
static void cs_init_lib_list_sort(cs_state &gcs) {
|
||||
gcs.new_command("sortlist", "srree", [](auto &cs, auto args, auto &res) {
|
||||
cs_list_sort(
|
||||
cs, res, args[0].get_strr(), args[1].get_ident(),
|
||||
args[2].get_ident(), args[3].get_code(), args[4].get_code()
|
||||
);
|
||||
});
|
||||
new_cmd_quiet(gcs, "uniquelist", "svvb", [](
|
||||
auto &cs, auto args, auto &res
|
||||
) {
|
||||
list_sort(
|
||||
cs, res, args[0].get_string(cs), args[1].get_ident(cs),
|
||||
args[2].get_ident(cs), bcode_ref{}, args[3].get_code()
|
||||
gcs.new_command("uniquelist", "srre", [](auto &cs, auto args, auto &res) {
|
||||
cs_list_sort(
|
||||
cs, res, args[0].get_strr(), args[1].get_ident(),
|
||||
args[2].get_ident(), nullptr, args[3].get_code()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
} /* namespace cubescript */
|
||||
} /* namespace cscript */
|
||||
|
|
343
src/lib_math.cc
343
src/lib_math.cc
|
@ -6,365 +6,362 @@
|
|||
|
||||
#include <cubescript/cubescript.hh>
|
||||
|
||||
#include "cs_state.hh"
|
||||
namespace cscript {
|
||||
|
||||
namespace cubescript {
|
||||
|
||||
static constexpr float_type PI = float_type(3.14159265358979323846);
|
||||
static constexpr float_type LN2 = float_type(0.693147180559945309417);
|
||||
static constexpr float_type RAD = PI / float_type(180.0);
|
||||
static constexpr cs_float PI = 3.14159265358979f;
|
||||
static constexpr cs_float RAD = PI / 180.0f;
|
||||
|
||||
template<typename T>
|
||||
struct math_val;
|
||||
struct cs_math_val;
|
||||
|
||||
template<>
|
||||
struct math_val<integer_type> {
|
||||
static integer_type get(any_value &tv) {
|
||||
return tv.get_integer();
|
||||
struct cs_math_val<cs_int> {
|
||||
static cs_int get(cs_value &tv) {
|
||||
return tv.get_int();
|
||||
}
|
||||
static void set(any_value &res, integer_type val) {
|
||||
res.set_integer(val);
|
||||
static void set(cs_value &res, cs_int val) {
|
||||
res.set_int(val);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct math_val<float_type> {
|
||||
static float_type get(any_value &tv) {
|
||||
struct cs_math_val<cs_float> {
|
||||
static cs_float get(cs_value &tv) {
|
||||
return tv.get_float();
|
||||
}
|
||||
static void set(any_value &res, float_type val) {
|
||||
static void set(cs_value &res, cs_float val) {
|
||||
res.set_float(val);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct math_noop {
|
||||
struct cs_math_noop {
|
||||
T operator()(T arg) {
|
||||
return arg;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename F1, typename F2>
|
||||
static inline void math_op(
|
||||
span_type<any_value> args, any_value &res, T initval,
|
||||
static inline void cs_mathop(
|
||||
cs_value_r args, cs_value &res, T initval,
|
||||
F1 binop, F2 unop
|
||||
) {
|
||||
T val;
|
||||
if (args.size() >= 2) {
|
||||
val = binop(math_val<T>::get(args[0]), math_val<T>::get(args[1]));
|
||||
val = binop(cs_math_val<T>::get(args[0]), cs_math_val<T>::get(args[1]));
|
||||
for (size_t i = 2; i < args.size(); ++i) {
|
||||
val = binop(val, math_val<T>::get(args[i]));
|
||||
val = binop(val, cs_math_val<T>::get(args[i]));
|
||||
}
|
||||
} else {
|
||||
val = unop(!args.empty() ? math_val<T>::get(args[0]) : initval);
|
||||
val = unop(!args.empty() ? cs_math_val<T>::get(args[0]) : initval);
|
||||
}
|
||||
math_val<T>::set(res, val);
|
||||
cs_math_val<T>::set(res, val);
|
||||
}
|
||||
|
||||
template<typename T, typename F>
|
||||
static inline void cmp_op(span_type<any_value> args, any_value &res, F cmp) {
|
||||
static inline void cs_cmpop(cs_value_r args, cs_value &res, F cmp) {
|
||||
bool val;
|
||||
if (args.size() >= 2) {
|
||||
val = cmp(math_val<T>::get(args[0]), math_val<T>::get(args[1]));
|
||||
val = cmp(cs_math_val<T>::get(args[0]), cs_math_val<T>::get(args[1]));
|
||||
for (size_t i = 2; (i < args.size()) && val; ++i) {
|
||||
val = cmp(math_val<T>::get(args[i - 1]), math_val<T>::get(args[i]));
|
||||
val = cmp(cs_math_val<T>::get(args[i - 1]), cs_math_val<T>::get(args[i]));
|
||||
}
|
||||
} else {
|
||||
val = cmp(!args.empty() ? math_val<T>::get(args[0]) : T(0), T(0));
|
||||
val = cmp(!args.empty() ? cs_math_val<T>::get(args[0]) : T(0), T(0));
|
||||
}
|
||||
res.set_integer(integer_type(val));
|
||||
res.set_int(cs_int(val));
|
||||
}
|
||||
|
||||
LIBCUBESCRIPT_EXPORT void std_init_math(state &cs) {
|
||||
new_cmd_quiet(cs, "sin", "f", [](auto &, auto args, auto &res) {
|
||||
void cs_init_lib_math(cs_state &cs) {
|
||||
cs.new_command("sin", "f", [](auto &, auto args, auto &res) {
|
||||
res.set_float(std::sin(args[0].get_float() * RAD));
|
||||
});
|
||||
new_cmd_quiet(cs, "cos", "f", [](auto &, auto args, auto &res) {
|
||||
cs.new_command("cos", "f", [](auto &, auto args, auto &res) {
|
||||
res.set_float(std::cos(args[0].get_float() * RAD));
|
||||
});
|
||||
new_cmd_quiet(cs, "tan", "f", [](auto &, auto args, auto &res) {
|
||||
cs.new_command("tan", "f", [](auto &, auto args, auto &res) {
|
||||
res.set_float(std::tan(args[0].get_float() * RAD));
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "asin", "f", [](auto &, auto args, auto &res) {
|
||||
cs.new_command("asin", "f", [](auto &, auto args, auto &res) {
|
||||
res.set_float(std::asin(args[0].get_float()) / RAD);
|
||||
});
|
||||
new_cmd_quiet(cs, "acos", "f", [](auto &, auto args, auto &res) {
|
||||
cs.new_command("acos", "f", [](auto &, auto args, auto &res) {
|
||||
res.set_float(std::acos(args[0].get_float()) / RAD);
|
||||
});
|
||||
new_cmd_quiet(cs, "atan", "f", [](auto &, auto args, auto &res) {
|
||||
cs.new_command("atan", "f", [](auto &, auto args, auto &res) {
|
||||
res.set_float(std::atan(args[0].get_float()) / RAD);
|
||||
});
|
||||
new_cmd_quiet(cs, "atan2", "ff", [](auto &, auto args, auto &res) {
|
||||
cs.new_command("atan2", "ff", [](auto &, auto args, auto &res) {
|
||||
res.set_float(std::atan2(args[0].get_float(), args[1].get_float()) / RAD);
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "sqrt", "f", [](auto &, auto args, auto &res) {
|
||||
cs.new_command("sqrt", "f", [](auto &, auto args, auto &res) {
|
||||
res.set_float(std::sqrt(args[0].get_float()));
|
||||
});
|
||||
new_cmd_quiet(cs, "loge", "f", [](auto &, auto args, auto &res) {
|
||||
cs.new_command("loge", "f", [](auto &, auto args, auto &res) {
|
||||
res.set_float(std::log(args[0].get_float()));
|
||||
});
|
||||
new_cmd_quiet(cs, "log2", "f", [](auto &, auto args, auto &res) {
|
||||
res.set_float(std::log(args[0].get_float()) / LN2);
|
||||
cs.new_command("log2", "f", [](auto &, auto args, auto &res) {
|
||||
res.set_float(std::log(args[0].get_float()) / M_LN2);
|
||||
});
|
||||
new_cmd_quiet(cs, "log10", "f", [](auto &, auto args, auto &res) {
|
||||
cs.new_command("log10", "f", [](auto &, auto args, auto &res) {
|
||||
res.set_float(std::log10(args[0].get_float()));
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "exp", "f", [](auto &, auto args, auto &res) {
|
||||
cs.new_command("exp", "f", [](auto &, auto args, auto &res) {
|
||||
res.set_float(std::exp(args[0].get_float()));
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "min", "i1...", [](auto &, auto args, auto &res) {
|
||||
integer_type v = (!args.empty() ? args[0].get_integer() : 0);
|
||||
cs.new_command("min", "i1V", [](auto &, auto args, auto &res) {
|
||||
cs_int v = (!args.empty() ? args[0].get_int() : 0);
|
||||
for (size_t i = 1; i < args.size(); ++i) {
|
||||
v = std::min(v, args[i].get_integer());
|
||||
v = std::min(v, args[i].get_int());
|
||||
}
|
||||
res.set_integer(v);
|
||||
res.set_int(v);
|
||||
});
|
||||
new_cmd_quiet(cs, "max", "i1...", [](auto &, auto args, auto &res) {
|
||||
integer_type v = (!args.empty() ? args[0].get_integer() : 0);
|
||||
cs.new_command("max", "i1V", [](auto &, auto args, auto &res) {
|
||||
cs_int v = (!args.empty() ? args[0].get_int() : 0);
|
||||
for (size_t i = 1; i < args.size(); ++i) {
|
||||
v = std::max(v, args[i].get_integer());
|
||||
v = std::max(v, args[i].get_int());
|
||||
}
|
||||
res.set_integer(v);
|
||||
res.set_int(v);
|
||||
});
|
||||
new_cmd_quiet(cs, "minf", "f1...", [](auto &, auto args, auto &res) {
|
||||
float_type v = (!args.empty() ? args[0].get_float() : 0);
|
||||
cs.new_command("minf", "f1V", [](auto &, auto args, auto &res) {
|
||||
cs_float v = (!args.empty() ? args[0].get_float() : 0);
|
||||
for (size_t i = 1; i < args.size(); ++i) {
|
||||
v = std::min(v, args[i].get_float());
|
||||
}
|
||||
res.set_float(v);
|
||||
});
|
||||
new_cmd_quiet(cs, "maxf", "f1...", [](auto &, auto args, auto &res) {
|
||||
float_type v = (!args.empty() ? args[0].get_float() : 0);
|
||||
cs.new_command("maxf", "f1V", [](auto &, auto args, auto &res) {
|
||||
cs_float v = (!args.empty() ? args[0].get_float() : 0);
|
||||
for (size_t i = 1; i < args.size(); ++i) {
|
||||
v = std::max(v, args[i].get_float());
|
||||
}
|
||||
res.set_float(v);
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "abs", "i", [](auto &, auto args, auto &res) {
|
||||
res.set_integer(std::abs(args[0].get_integer()));
|
||||
cs.new_command("abs", "i", [](auto &, auto args, auto &res) {
|
||||
res.set_int(std::abs(args[0].get_int()));
|
||||
});
|
||||
new_cmd_quiet(cs, "absf", "f", [](auto &, auto args, auto &res) {
|
||||
cs.new_command("absf", "f", [](auto &, auto args, auto &res) {
|
||||
res.set_float(std::abs(args[0].get_float()));
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "floor", "f", [](auto &, auto args, auto &res) {
|
||||
cs.new_command("floor", "f", [](auto &, auto args, auto &res) {
|
||||
res.set_float(std::floor(args[0].get_float()));
|
||||
});
|
||||
new_cmd_quiet(cs, "ceil", "f", [](auto &, auto args, auto &res) {
|
||||
cs.new_command("ceil", "f", [](auto &, auto args, auto &res) {
|
||||
res.set_float(std::ceil(args[0].get_float()));
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "round", "ff", [](auto &, auto args, auto &res) {
|
||||
float_type step = args[1].get_float();
|
||||
float_type r = args[0].get_float();
|
||||
cs.new_command("round", "ff", [](auto &, auto args, auto &res) {
|
||||
cs_float step = args[1].get_float();
|
||||
cs_float r = args[0].get_float();
|
||||
if (step > 0) {
|
||||
r += float_type(step * ((r < 0) ? -0.5 : 0.5));
|
||||
r -= float_type(std::fmod(r, step));
|
||||
r += step * ((r < 0) ? -0.5 : 0.5);
|
||||
r -= std::fmod(r, step);
|
||||
} else {
|
||||
r = float_type((r < 0) ? std::ceil(r - 0.5) : std::floor(r + 0.5));
|
||||
r = (r < 0) ? std::ceil(r - 0.5) : std::floor(r + 0.5);
|
||||
}
|
||||
res.set_float(r);
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "+", "i1...", [](auto &, auto args, auto &res) {
|
||||
math_op<integer_type>(args, res, 0, std::plus<integer_type>(), math_noop<integer_type>());
|
||||
cs.new_command("+", "i1V", [](auto &, auto args, auto &res) {
|
||||
cs_mathop<cs_int>(args, res, 0, std::plus<cs_int>(), cs_math_noop<cs_int>());
|
||||
});
|
||||
new_cmd_quiet(cs, "*", "i1...", [](auto &, auto args, auto &res) {
|
||||
math_op<integer_type>(
|
||||
args, res, 1, std::multiplies<integer_type>(), math_noop<integer_type>()
|
||||
cs.new_command("*", "i1V", [](auto &, auto args, auto &res) {
|
||||
cs_mathop<cs_int>(
|
||||
args, res, 1, std::multiplies<cs_int>(), cs_math_noop<cs_int>()
|
||||
);
|
||||
});
|
||||
new_cmd_quiet(cs, "-", "i1...", [](auto &, auto args, auto &res) {
|
||||
math_op<integer_type>(
|
||||
args, res, 0, std::minus<integer_type>(), std::negate<integer_type>()
|
||||
cs.new_command("-", "i1V", [](auto &, auto args, auto &res) {
|
||||
cs_mathop<cs_int>(
|
||||
args, res, 0, std::minus<cs_int>(), std::negate<cs_int>()
|
||||
);
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "^", "i1...", [](auto &, auto args, auto &res) {
|
||||
math_op<integer_type>(
|
||||
args, res, 0, std::bit_xor<integer_type>(), [](integer_type val) { return ~val; }
|
||||
cs.new_command("^", "i1V", [](auto &, auto args, auto &res) {
|
||||
cs_mathop<cs_int>(
|
||||
args, res, 0, std::bit_xor<cs_int>(), [](cs_int val) { return ~val; }
|
||||
);
|
||||
});
|
||||
new_cmd_quiet(cs, "~", "i1...", [](auto &, auto args, auto &res) {
|
||||
math_op<integer_type>(
|
||||
args, res, 0, std::bit_xor<integer_type>(), [](integer_type val) { return ~val; }
|
||||
cs.new_command("~", "i1V", [](auto &, auto args, auto &res) {
|
||||
cs_mathop<cs_int>(
|
||||
args, res, 0, std::bit_xor<cs_int>(), [](cs_int val) { return ~val; }
|
||||
);
|
||||
});
|
||||
new_cmd_quiet(cs, "&", "i1...", [](auto &, auto args, auto &res) {
|
||||
math_op<integer_type>(
|
||||
args, res, 0, std::bit_and<integer_type>(), math_noop<integer_type>()
|
||||
cs.new_command("&", "i1V", [](auto &, auto args, auto &res) {
|
||||
cs_mathop<cs_int>(
|
||||
args, res, 0, std::bit_and<cs_int>(), cs_math_noop<cs_int>()
|
||||
);
|
||||
});
|
||||
new_cmd_quiet(cs, "|", "i1...", [](auto &, auto args, auto &res) {
|
||||
math_op<integer_type>(
|
||||
args, res, 0, std::bit_or<integer_type>(), math_noop<integer_type>()
|
||||
cs.new_command("|", "i1V", [](auto &, auto args, auto &res) {
|
||||
cs_mathop<cs_int>(
|
||||
args, res, 0, std::bit_or<cs_int>(), cs_math_noop<cs_int>()
|
||||
);
|
||||
});
|
||||
|
||||
/* special combined cases */
|
||||
new_cmd_quiet(cs, "^~", "i1...", [](auto &, auto args, auto &res) {
|
||||
integer_type val;
|
||||
cs.new_command("^~", "i1V", [](auto &, auto args, auto &res) {
|
||||
cs_int val;
|
||||
if (args.size() >= 2) {
|
||||
val = args[0].get_integer() ^ ~args[1].get_integer();
|
||||
val = args[0].get_int() ^ ~args[1].get_int();
|
||||
for (size_t i = 2; i < args.size(); ++i) {
|
||||
val ^= ~args[i].get_integer();
|
||||
val ^= ~args[i].get_int();
|
||||
}
|
||||
} else {
|
||||
val = !args.empty() ? args[0].get_integer() : 0;
|
||||
val = !args.empty() ? args[0].get_int() : 0;
|
||||
}
|
||||
res.set_integer(val);
|
||||
res.set_int(val);
|
||||
});
|
||||
new_cmd_quiet(cs, "&~", "i1...", [](auto &, auto args, auto &res) {
|
||||
integer_type val;
|
||||
cs.new_command("&~", "i1V", [](auto &, auto args, auto &res) {
|
||||
cs_int val;
|
||||
if (args.size() >= 2) {
|
||||
val = args[0].get_integer() & ~args[1].get_integer();
|
||||
val = args[0].get_int() & ~args[1].get_int();
|
||||
for (size_t i = 2; i < args.size(); ++i) {
|
||||
val &= ~args[i].get_integer();
|
||||
val &= ~args[i].get_int();
|
||||
}
|
||||
} else {
|
||||
val = !args.empty() ? args[0].get_integer() : 0;
|
||||
val = !args.empty() ? args[0].get_int() : 0;
|
||||
}
|
||||
res.set_integer(val);
|
||||
res.set_int(val);
|
||||
});
|
||||
new_cmd_quiet(cs, "|~", "i1...", [](auto &, auto args, auto &res) {
|
||||
integer_type val;
|
||||
cs.new_command("|~", "i1V", [](auto &, auto args, auto &res) {
|
||||
cs_int val;
|
||||
if (args.size() >= 2) {
|
||||
val = args[0].get_integer() | ~args[1].get_integer();
|
||||
val = args[0].get_int() | ~args[1].get_int();
|
||||
for (size_t i = 2; i < args.size(); ++i) {
|
||||
val |= ~args[i].get_integer();
|
||||
val |= ~args[i].get_int();
|
||||
}
|
||||
} else {
|
||||
val = !args.empty() ? args[0].get_integer() : 0;
|
||||
val = !args.empty() ? args[0].get_int() : 0;
|
||||
}
|
||||
res.set_integer(val);
|
||||
res.set_int(val);
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "<<", "i1...", [](auto &, auto args, auto &res) {
|
||||
math_op<integer_type>(
|
||||
args, res, 0, [](integer_type val1, integer_type val2) {
|
||||
return (val2 < integer_type(sizeof(integer_type) * CHAR_BIT))
|
||||
? (val1 << std::max(val2, integer_type(0)))
|
||||
cs.new_command("<<", "i1V", [](auto &, auto args, auto &res) {
|
||||
cs_mathop<cs_int>(
|
||||
args, res, 0, [](cs_int val1, cs_int val2) {
|
||||
return (val2 < cs_int(sizeof(cs_int) * CHAR_BIT))
|
||||
? (val1 << std::max(val2, cs_int(0)))
|
||||
: 0;
|
||||
}, math_noop<integer_type>()
|
||||
}, cs_math_noop<cs_int>()
|
||||
);
|
||||
});
|
||||
new_cmd_quiet(cs, ">>", "i1...", [](auto &, auto args, auto &res) {
|
||||
math_op<integer_type>(
|
||||
args, res, 0, [](integer_type val1, integer_type val2) {
|
||||
cs.new_command(">>", "i1V", [](auto &, auto args, auto &res) {
|
||||
cs_mathop<cs_int>(
|
||||
args, res, 0, [](cs_int val1, cs_int val2) {
|
||||
return val1 >> std::clamp(
|
||||
val2, integer_type(0), integer_type(sizeof(integer_type) * CHAR_BIT)
|
||||
val2, cs_int(0), cs_int(sizeof(cs_int) * CHAR_BIT)
|
||||
);
|
||||
}, math_noop<integer_type>()
|
||||
}, cs_math_noop<cs_int>()
|
||||
);
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "+f", "f1...", [](auto &, auto args, auto &res) {
|
||||
math_op<float_type>(
|
||||
args, res, 0, std::plus<float_type>(), math_noop<float_type>()
|
||||
cs.new_command("+f", "f1V", [](auto &, auto args, auto &res) {
|
||||
cs_mathop<cs_float>(
|
||||
args, res, 0, std::plus<cs_float>(), cs_math_noop<cs_float>()
|
||||
);
|
||||
});
|
||||
new_cmd_quiet(cs, "*f", "f1...", [](auto &, auto args, auto &res) {
|
||||
math_op<float_type>(
|
||||
args, res, 1, std::multiplies<float_type>(), math_noop<float_type>()
|
||||
cs.new_command("*f", "f1V", [](auto &, auto args, auto &res) {
|
||||
cs_mathop<cs_float>(
|
||||
args, res, 1, std::multiplies<cs_float>(), cs_math_noop<cs_float>()
|
||||
);
|
||||
});
|
||||
new_cmd_quiet(cs, "-f", "f1...", [](auto &, auto args, auto &res) {
|
||||
math_op<float_type>(
|
||||
args, res, 0, std::minus<float_type>(), std::negate<float_type>()
|
||||
cs.new_command("-f", "f1V", [](auto &, auto args, auto &res) {
|
||||
cs_mathop<cs_float>(
|
||||
args, res, 0, std::minus<cs_float>(), std::negate<cs_float>()
|
||||
);
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "div", "i1...", [](auto &, auto args, auto &res) {
|
||||
math_op<integer_type>(
|
||||
args, res, 0, [](integer_type val1, integer_type val2) {
|
||||
cs.new_command("div", "i1V", [](auto &, auto args, auto &res) {
|
||||
cs_mathop<cs_int>(
|
||||
args, res, 0, [](cs_int val1, cs_int val2) {
|
||||
if (val2) {
|
||||
return val1 / val2;
|
||||
}
|
||||
return integer_type(0);
|
||||
}, math_noop<integer_type>()
|
||||
return cs_int(0);
|
||||
}, cs_math_noop<cs_int>()
|
||||
);
|
||||
});
|
||||
new_cmd_quiet(cs, "mod", "i1...", [](auto &, auto args, auto &res) {
|
||||
math_op<integer_type>(
|
||||
args, res, 0, [](integer_type val1, integer_type val2) {
|
||||
cs.new_command("mod", "i1V", [](auto &, auto args, auto &res) {
|
||||
cs_mathop<cs_int>(
|
||||
args, res, 0, [](cs_int val1, cs_int val2) {
|
||||
if (val2) {
|
||||
return val1 % val2;
|
||||
}
|
||||
return integer_type(0);
|
||||
}, math_noop<integer_type>()
|
||||
return cs_int(0);
|
||||
}, cs_math_noop<cs_int>()
|
||||
);
|
||||
});
|
||||
new_cmd_quiet(cs, "divf", "f1...", [](auto &, auto args, auto &res) {
|
||||
math_op<float_type>(
|
||||
args, res, 0, [](float_type val1, float_type val2) {
|
||||
cs.new_command("divf", "f1V", [](auto &, auto args, auto &res) {
|
||||
cs_mathop<cs_float>(
|
||||
args, res, 0, [](cs_float val1, cs_float val2) {
|
||||
if (val2) {
|
||||
return val1 / val2;
|
||||
}
|
||||
return float_type(0);
|
||||
}, math_noop<float_type>()
|
||||
return cs_float(0);
|
||||
}, cs_math_noop<cs_float>()
|
||||
);
|
||||
});
|
||||
new_cmd_quiet(cs, "modf", "f1...", [](auto &, auto args, auto &res) {
|
||||
math_op<float_type>(
|
||||
args, res, 0, [](float_type val1, float_type val2) {
|
||||
cs.new_command("modf", "f1V", [](auto &, auto args, auto &res) {
|
||||
cs_mathop<cs_float>(
|
||||
args, res, 0, [](cs_float val1, cs_float val2) {
|
||||
if (val2) {
|
||||
return float_type(fmod(val1, val2));
|
||||
return cs_float(fmod(val1, val2));
|
||||
}
|
||||
return float_type(0);
|
||||
}, math_noop<float_type>()
|
||||
return cs_float(0);
|
||||
}, cs_math_noop<cs_float>()
|
||||
);
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "pow", "f1...", [](auto &, auto args, auto &res) {
|
||||
math_op<float_type>(
|
||||
args, res, 0, [](float_type val1, float_type val2) {
|
||||
return float_type(pow(val1, val2));
|
||||
}, math_noop<float_type>()
|
||||
cs.new_command("pow", "f1V", [](auto &, auto args, auto &res) {
|
||||
cs_mathop<cs_float>(
|
||||
args, res, 0, [](cs_float val1, cs_float val2) {
|
||||
return cs_float(pow(val1, val2));
|
||||
}, cs_math_noop<cs_float>()
|
||||
);
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "=", "i1...", [](auto &, auto args, auto &res) {
|
||||
cmp_op<integer_type>(args, res, std::equal_to<integer_type>());
|
||||
cs.new_command("=", "i1V", [](auto &, auto args, auto &res) {
|
||||
cs_cmpop<cs_int>(args, res, std::equal_to<cs_int>());
|
||||
});
|
||||
new_cmd_quiet(cs, "!=", "i1...", [](auto &, auto args, auto &res) {
|
||||
cmp_op<integer_type>(args, res, std::not_equal_to<integer_type>());
|
||||
cs.new_command("!=", "i1V", [](auto &, auto args, auto &res) {
|
||||
cs_cmpop<cs_int>(args, res, std::not_equal_to<cs_int>());
|
||||
});
|
||||
new_cmd_quiet(cs, "<", "i1...", [](auto &, auto args, auto &res) {
|
||||
cmp_op<integer_type>(args, res, std::less<integer_type>());
|
||||
cs.new_command("<", "i1V", [](auto &, auto args, auto &res) {
|
||||
cs_cmpop<cs_int>(args, res, std::less<cs_int>());
|
||||
});
|
||||
new_cmd_quiet(cs, ">", "i1...", [](auto &, auto args, auto &res) {
|
||||
cmp_op<integer_type>(args, res, std::greater<integer_type>());
|
||||
cs.new_command(">", "i1V", [](auto &, auto args, auto &res) {
|
||||
cs_cmpop<cs_int>(args, res, std::greater<cs_int>());
|
||||
});
|
||||
new_cmd_quiet(cs, "<=", "i1...", [](auto &, auto args, auto &res) {
|
||||
cmp_op<integer_type>(args, res, std::less_equal<integer_type>());
|
||||
cs.new_command("<=", "i1V", [](auto &, auto args, auto &res) {
|
||||
cs_cmpop<cs_int>(args, res, std::less_equal<cs_int>());
|
||||
});
|
||||
new_cmd_quiet(cs, ">=", "i1...", [](auto &, auto args, auto &res) {
|
||||
cmp_op<integer_type>(args, res, std::greater_equal<integer_type>());
|
||||
cs.new_command(">=", "i1V", [](auto &, auto args, auto &res) {
|
||||
cs_cmpop<cs_int>(args, res, std::greater_equal<cs_int>());
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "=f", "f1...", [](auto &, auto args, auto &res) {
|
||||
cmp_op<float_type>(args, res, std::equal_to<float_type>());
|
||||
cs.new_command("=f", "f1V", [](auto &, auto args, auto &res) {
|
||||
cs_cmpop<cs_float>(args, res, std::equal_to<cs_float>());
|
||||
});
|
||||
new_cmd_quiet(cs, "!=f", "f1...", [](auto &, auto args, auto &res) {
|
||||
cmp_op<float_type>(args, res, std::not_equal_to<float_type>());
|
||||
cs.new_command("!=f", "f1V", [](auto &, auto args, auto &res) {
|
||||
cs_cmpop<cs_float>(args, res, std::not_equal_to<cs_float>());
|
||||
});
|
||||
new_cmd_quiet(cs, "<f", "f1...", [](auto &, auto args, auto &res) {
|
||||
cmp_op<float_type>(args, res, std::less<float_type>());
|
||||
cs.new_command("<f", "f1V", [](auto &, auto args, auto &res) {
|
||||
cs_cmpop<cs_float>(args, res, std::less<cs_float>());
|
||||
});
|
||||
new_cmd_quiet(cs, ">f", "f1...", [](auto &, auto args, auto &res) {
|
||||
cmp_op<float_type>(args, res, std::greater<float_type>());
|
||||
cs.new_command(">f", "f1V", [](auto &, auto args, auto &res) {
|
||||
cs_cmpop<cs_float>(args, res, std::greater<cs_float>());
|
||||
});
|
||||
new_cmd_quiet(cs, "<=f", "f1...", [](auto &, auto args, auto &res) {
|
||||
cmp_op<float_type>(args, res, std::less_equal<float_type>());
|
||||
cs.new_command("<=f", "f1V", [](auto &, auto args, auto &res) {
|
||||
cs_cmpop<cs_float>(args, res, std::less_equal<cs_float>());
|
||||
});
|
||||
new_cmd_quiet(cs, ">=f", "f1...", [](auto &, auto args, auto &res) {
|
||||
cmp_op<float_type>(args, res, std::greater_equal<float_type>());
|
||||
cs.new_command(">=f", "f1V", [](auto &, auto args, auto &res) {
|
||||
cs_cmpop<cs_float>(args, res, std::greater_equal<cs_float>());
|
||||
});
|
||||
}
|
||||
|
||||
} /* namespace cubescript */
|
||||
} /* namespace cscript */
|
||||
|
|
315
src/lib_str.cc
315
src/lib_str.cc
|
@ -1,245 +1,228 @@
|
|||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
|
||||
#include <cubescript/cubescript.hh>
|
||||
|
||||
#include "cs_std.hh"
|
||||
#include "cs_strman.hh"
|
||||
#include "cs_thread.hh"
|
||||
|
||||
namespace cubescript {
|
||||
namespace cscript {
|
||||
|
||||
template<typename F>
|
||||
static inline void str_cmp_by(
|
||||
state &cs, span_type<any_value> args, any_value &res, F cfunc
|
||||
) {
|
||||
static inline void cs_strgcmp(cs_value_r args, cs_value &res, F cfunc) {
|
||||
bool val;
|
||||
if (args.size() >= 2) {
|
||||
val = cfunc(args[0].get_string(cs), args[1].get_string(cs));
|
||||
val = cfunc(args[0].get_strr(), args[1].get_strr());
|
||||
for (size_t i = 2; (i < args.size()) && val; ++i) {
|
||||
val = cfunc(args[i - 1].get_string(cs), args[i].get_string(cs));
|
||||
val = cfunc(args[i - 1].get_strr(), args[i].get_strr());
|
||||
}
|
||||
} else {
|
||||
val = cfunc(
|
||||
!args.empty() ? args[0].get_string(cs) : std::string_view(),
|
||||
std::string_view()
|
||||
!args.empty() ? args[0].get_strr() : ostd::string_range(),
|
||||
ostd::string_range()
|
||||
);
|
||||
}
|
||||
res.set_integer(integer_type(val));
|
||||
}
|
||||
res.set_int(cs_int(val));
|
||||
};
|
||||
|
||||
LIBCUBESCRIPT_EXPORT void std_init_string(state &cs) {
|
||||
new_cmd_quiet(cs, "strstr", "ss", [](auto &ccs, auto args, auto &res) {
|
||||
std::string_view a = args[0].get_string(ccs);
|
||||
std::string_view b = args[1].get_string(ccs);
|
||||
auto pos = a.find(b);
|
||||
if (pos == a.npos) {
|
||||
res.set_integer(-1);
|
||||
void cs_init_lib_string(cs_state &cs) {
|
||||
cs.new_command("strstr", "ss", [](auto &, auto args, auto &res) {
|
||||
ostd::string_range a = args[0].get_strr(), b = args[1].get_strr();
|
||||
ostd::string_range s = a;
|
||||
for (cs_int i = 0; b.size() <= s.size(); ++i) {
|
||||
if (b == s.slice(0, b.size())) {
|
||||
res.set_int(i);
|
||||
return;
|
||||
}
|
||||
++s;
|
||||
}
|
||||
res.set_int(-1);
|
||||
});
|
||||
|
||||
cs.new_command("strlen", "s", [](auto &, auto args, auto &res) {
|
||||
res.set_int(cs_int(args[0].get_strr().size()));
|
||||
});
|
||||
|
||||
cs.new_command("strcode", "si", [](auto &, auto args, auto &res) {
|
||||
ostd::string_range str = args[0].get_strr();
|
||||
cs_int i = args[1].get_int();
|
||||
if (i >= cs_int(str.size())) {
|
||||
res.set_int(0);
|
||||
} else {
|
||||
res.set_integer(integer_type(pos));
|
||||
res.set_int(static_cast<unsigned char>(str[i]));
|
||||
}
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "strlen", "s", [](auto &ccs, auto args, auto &res) {
|
||||
res.set_integer(integer_type(args[0].get_string(ccs).size()));
|
||||
cs.new_command("codestr", "i", [](auto &, auto args, auto &res) {
|
||||
res.set_str(cs_string(1, char(args[0].get_int())));
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "strcode", "si", [](auto &ccs, auto args, auto &res) {
|
||||
std::string_view str = args[0].get_string(ccs);
|
||||
integer_type i = args[1].get_integer();
|
||||
if (i >= integer_type(str.size())) {
|
||||
res.set_integer(0);
|
||||
} else {
|
||||
res.set_integer(static_cast<unsigned char>(str[i]));
|
||||
cs.new_command("strlower", "s", [](auto &, auto args, auto &res) {
|
||||
cs_string s = args[0].get_str();
|
||||
for (auto i: ostd::range(s.size())) {
|
||||
s[i] = tolower(s[i]);
|
||||
}
|
||||
res.set_str(std::move(s));
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "codestr", "i", [](auto &ccs, auto args, auto &res) {
|
||||
char const p[2] = { char(args[0].get_integer()), '\0' };
|
||||
res.set_string(std::string_view{static_cast<char const *>(p)}, ccs);
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "strlower", "s", [](auto &ccs, auto args, auto &res) {
|
||||
auto inps = args[0].get_string(ccs);
|
||||
auto *ics = state_p{ccs}.ts().istate;
|
||||
auto *buf = ics->strman->alloc_buf(inps.size());
|
||||
for (std::size_t i = 0; i < inps.size(); ++i) {
|
||||
buf[i] = char(tolower(inps.data()[i]));
|
||||
cs.new_command("strupper", "s", [](auto &, auto args, auto &res) {
|
||||
cs_string s = args[0].get_str();
|
||||
for (auto i: ostd::range(s.size())) {
|
||||
s[i] = toupper(s[i]);
|
||||
}
|
||||
res.set_string(ics->strman->steal(buf));
|
||||
res.set_str(std::move(s));
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "strupper", "s", [](auto &ccs, auto args, auto &res) {
|
||||
auto inps = args[0].get_string(ccs);
|
||||
auto *ics = state_p{ccs}.ts().istate;
|
||||
auto *buf = ics->strman->alloc_buf(inps.size());
|
||||
for (std::size_t i = 0; i < inps.size(); ++i) {
|
||||
buf[i] = char(toupper(inps.data()[i]));
|
||||
}
|
||||
res.set_string(ics->strman->steal(buf));
|
||||
cs.new_command("escape", "s", [](auto &, auto args, auto &res) {
|
||||
auto s = ostd::appender<cs_string>();
|
||||
util::escape_string(s, args[0].get_strr());
|
||||
res.set_str(std::move(s.get()));
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "escape", "s", [](auto &ccs, auto args, auto &res) {
|
||||
charbuf s{ccs};
|
||||
escape_string(std::back_inserter(s), args[0].get_string(ccs));
|
||||
res.set_string(s.str(), ccs);
|
||||
cs.new_command("unescape", "s", [](auto &, auto args, auto &res) {
|
||||
auto s = ostd::appender<cs_string>();
|
||||
util::unescape_string(s, args[0].get_strr());
|
||||
res.set_str(std::move(s.get()));
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "unescape", "s", [](auto &ccs, auto args, auto &res) {
|
||||
charbuf s{ccs};
|
||||
unescape_string(std::back_inserter(s), args[0].get_string(ccs));
|
||||
res.set_string(s.str(), ccs);
|
||||
cs.new_command("concat", "V", [](auto &, auto args, auto &res) {
|
||||
auto s = ostd::appender<cs_string>();
|
||||
cscript::util::tvals_concat(s, args, " ");
|
||||
res.set_str(std::move(s.get()));
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "concat", "...", [](auto &ccs, auto args, auto &res) {
|
||||
res.set_string(concat_values(ccs, args, " "));
|
||||
cs.new_command("concatword", "V", [](auto &, auto args, auto &res) {
|
||||
auto s = ostd::appender<cs_string>();
|
||||
cscript::util::tvals_concat(s, args);
|
||||
res.set_str(std::move(s.get()));
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "concatword", "...", [](auto &ccs, auto args, auto &res) {
|
||||
res.set_string(concat_values(ccs, args));
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "format", "...", [](auto &ccs, auto args, auto &res) {
|
||||
cs.new_command("format", "V", [](auto &, auto args, auto &res) {
|
||||
if (args.empty()) {
|
||||
return;
|
||||
}
|
||||
charbuf s{ccs};
|
||||
string_ref fs = args[0].get_string(ccs);
|
||||
std::string_view f{fs};
|
||||
for (auto it = f.begin(); it != f.end(); ++it) {
|
||||
char c = *it;
|
||||
++it;
|
||||
if ((c == '%') && (it != f.end())) {
|
||||
char ic = *it;
|
||||
++it;
|
||||
if ((ic >= '1') && (ic <= '9')) {
|
||||
cs_string s;
|
||||
cs_string fs = args[0].get_str();
|
||||
ostd::string_range f = ostd::iter(fs);
|
||||
while (!f.empty()) {
|
||||
char c = *f;
|
||||
++f;
|
||||
if ((c == '%') && !f.empty()) {
|
||||
char ic = *f;
|
||||
++f;
|
||||
if (ic >= '1' && ic <= '9') {
|
||||
int i = ic - '0';
|
||||
if (std::size_t(i) < args.size()) {
|
||||
s.append(args[i].get_string(ccs));
|
||||
if (size_t(i) < args.size()) {
|
||||
s += args[i].get_str();
|
||||
}
|
||||
} else {
|
||||
s.push_back(ic);
|
||||
s += ic;
|
||||
}
|
||||
} else {
|
||||
s.push_back(c);
|
||||
s += c;
|
||||
}
|
||||
}
|
||||
res.set_string(s.str(), ccs);
|
||||
res.set_str(std::move(s));
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "tohex", "ii", [](auto &ccs, auto args, auto &res) {
|
||||
char buf[32];
|
||||
/* use long long as the largest signed integer type */
|
||||
auto val = static_cast<long long>(args[0].get_integer());
|
||||
int prec = std::max(int(args[1].get_integer()), 1);
|
||||
int n = snprintf(buf, sizeof(buf), "0x%.*llX", prec, val);
|
||||
if (n >= int(sizeof(buf))) {
|
||||
charbuf s{ccs};
|
||||
s.reserve(n + 1);
|
||||
s.data()[0] = '\0';
|
||||
int nn = snprintf(s.data(), n + 1, "0x%.*llX", prec, val);
|
||||
if ((nn > 0) && (nn <= n)) {
|
||||
res.set_string(
|
||||
std::string_view{s.data(), std::size_t(nn)}, ccs
|
||||
);
|
||||
return;
|
||||
}
|
||||
} else if (n > 0) {
|
||||
res.set_string(static_cast<char const *>(buf), ccs);
|
||||
return;
|
||||
cs.new_command("tohex", "ii", [](auto &, auto args, auto &res) {
|
||||
auto r = ostd::appender<cs_string>();
|
||||
try {
|
||||
ostd::format(
|
||||
r, "0x%.*X", std::max(args[1].get_int(), cs_int(1)),
|
||||
args[0].get_int()
|
||||
);
|
||||
} catch (ostd::format_error const &e) {
|
||||
throw cs_internal_error{e.what()};
|
||||
}
|
||||
/* should be unreachable */
|
||||
abort();
|
||||
res.set_str(std::move(r.get()));
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "substr", "sii#", [](auto &ccs, auto args, auto &res) {
|
||||
std::string_view s = args[0].get_string(ccs);
|
||||
auto start = args[1].get_integer(), count = args[2].get_integer();
|
||||
auto numargs = args[3].get_integer();
|
||||
auto len = integer_type(s.size());
|
||||
auto offset = std::clamp(start, integer_type(0), len);
|
||||
res.set_string(std::string_view{
|
||||
cs.new_command("substr", "siiN", [](auto &, auto args, auto &res) {
|
||||
ostd::string_range s = args[0].get_strr();
|
||||
cs_int start = args[1].get_int(), count = args[2].get_int();
|
||||
cs_int numargs = args[3].get_int();
|
||||
cs_int len = cs_int(s.size()), offset = std::clamp(start, cs_int(0), len);
|
||||
res.set_str(cs_string{
|
||||
&s[offset],
|
||||
((numargs >= 3)
|
||||
? size_t(std::clamp(count, integer_type(0), len - offset))
|
||||
: size_t(len - offset))
|
||||
}, ccs);
|
||||
(numargs >= 3)
|
||||
? size_t(std::clamp(count, cs_int(0), len - offset))
|
||||
: size_t(len - offset)
|
||||
});
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "strcmp", "s1...", [](auto &ccs, auto args, auto &res) {
|
||||
str_cmp_by(ccs, args, res, std::equal_to<std::string_view>());
|
||||
cs.new_command("strcmp", "s1V", [](auto &, auto args, auto &res) {
|
||||
cs_strgcmp(args, res, std::equal_to<ostd::string_range>());
|
||||
});
|
||||
new_cmd_quiet(cs, "=s", "s1...", [](auto &ccs, auto args, auto &res) {
|
||||
str_cmp_by(ccs, args, res, std::equal_to<std::string_view>());
|
||||
cs.new_command("=s", "s1V", [](auto &, auto args, auto &res) {
|
||||
cs_strgcmp(args, res, std::equal_to<ostd::string_range>());
|
||||
});
|
||||
new_cmd_quiet(cs, "!=s", "s1...", [](auto &ccs, auto args, auto &res) {
|
||||
str_cmp_by(ccs, args, res, std::not_equal_to<std::string_view>());
|
||||
cs.new_command("!=s", "s1V", [](auto &, auto args, auto &res) {
|
||||
cs_strgcmp(args, res, std::not_equal_to<ostd::string_range>());
|
||||
});
|
||||
new_cmd_quiet(cs, "<s", "s1...", [](auto &ccs, auto args, auto &res) {
|
||||
str_cmp_by(ccs, args, res, std::less<std::string_view>());
|
||||
cs.new_command("<s", "s1V", [](auto &, auto args, auto &res) {
|
||||
cs_strgcmp(args, res, std::less<ostd::string_range>());
|
||||
});
|
||||
new_cmd_quiet(cs, ">s", "s1...", [](auto &ccs, auto args, auto &res) {
|
||||
str_cmp_by(ccs, args, res, std::greater<std::string_view>());
|
||||
cs.new_command(">s", "s1V", [](auto &, auto args, auto &res) {
|
||||
cs_strgcmp(args, res, std::greater<ostd::string_range>());
|
||||
});
|
||||
new_cmd_quiet(cs, "<=s", "s1...", [](auto &ccs, auto args, auto &res) {
|
||||
str_cmp_by(ccs, args, res, std::less_equal<std::string_view>());
|
||||
cs.new_command("<=s", "s1V", [](auto &, auto args, auto &res) {
|
||||
cs_strgcmp(args, res, std::less_equal<ostd::string_range>());
|
||||
});
|
||||
new_cmd_quiet(cs, ">=s", "s1...", [](auto &ccs, auto args, auto &res) {
|
||||
str_cmp_by(ccs, args, res, std::greater_equal<std::string_view>());
|
||||
cs.new_command(">=s", "s1V", [](auto &, auto args, auto &res) {
|
||||
cs_strgcmp(args, res, std::greater_equal<ostd::string_range>());
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "strreplace", "ssss", [](
|
||||
auto &ccs, auto args, auto &res
|
||||
) {
|
||||
std::string_view s = args[0].get_string(ccs);
|
||||
std::string_view oldval = args[1].get_string(ccs),
|
||||
newval = args[2].get_string(ccs),
|
||||
newval2 = args[3].get_string(ccs);
|
||||
cs.new_command("strreplace", "ssss", [](auto &, auto args, auto &res) {
|
||||
ostd::string_range s = args[0].get_strr();
|
||||
ostd::string_range oldval = args[1].get_strr(),
|
||||
newval = args[2].get_strr(),
|
||||
newval2 = args[3].get_strr();
|
||||
if (newval2.empty()) {
|
||||
newval2 = newval;
|
||||
}
|
||||
if (oldval.empty()) {
|
||||
res.set_string(s, ccs);
|
||||
cs_string buf;
|
||||
if (!oldval.size()) {
|
||||
res.set_str(cs_string{s});
|
||||
return;
|
||||
}
|
||||
charbuf buf{ccs};
|
||||
for (size_t i = 0;; ++i) {
|
||||
auto p = s.find(oldval);
|
||||
if (p == s.npos) {
|
||||
buf.append(s);
|
||||
res.set_string(s, ccs);
|
||||
ostd::string_range found;
|
||||
ostd::string_range trys = s;
|
||||
for (; oldval.size() <= trys.size(); ++trys) {
|
||||
if (trys.slice(0, oldval.size()) == oldval) {
|
||||
found = trys;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found.empty()) {
|
||||
buf += s.slice(0, &found[0] - &s[0]);
|
||||
buf += (i & 1) ? newval2 : newval;
|
||||
s = found.slice(oldval.size(), found.size());
|
||||
} else {
|
||||
buf += s;
|
||||
res.set_str(std::move(buf));
|
||||
return;
|
||||
}
|
||||
buf.append(s.substr(0, p));
|
||||
buf.append((i & 1) ? newval2 : newval);
|
||||
buf.append(s.substr(
|
||||
p + oldval.size(),
|
||||
s.size() - p - oldval.size()
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
new_cmd_quiet(cs, "strsplice", "ssii", [](
|
||||
auto &ccs, auto args, auto &res
|
||||
) {
|
||||
std::string_view s = args[0].get_string(ccs);
|
||||
std::string_view vals = args[1].get_string(ccs);
|
||||
integer_type skip = args[2].get_integer(),
|
||||
count = args[3].get_integer();
|
||||
integer_type offset = std::clamp(skip, integer_type(0), integer_type(s.size())),
|
||||
len = std::clamp(count, integer_type(0), integer_type(s.size()) - offset);
|
||||
charbuf p{ccs};
|
||||
cs.new_command("strsplice", "ssii", [](auto &, auto args, auto &res) {
|
||||
ostd::string_range s = args[0].get_strr();
|
||||
ostd::string_range vals = args[1].get_strr();
|
||||
cs_int skip = args[2].get_int(),
|
||||
count = args[3].get_int();
|
||||
cs_int offset = std::clamp(skip, cs_int(0), cs_int(s.size())),
|
||||
len = std::clamp(count, cs_int(0), cs_int(s.size()) - offset);
|
||||
cs_string p;
|
||||
p.reserve(s.size() - len + vals.size());
|
||||
if (offset) {
|
||||
p.append(s.substr(0, offset));
|
||||
p += s.slice(0, offset);
|
||||
}
|
||||
p.append(vals);
|
||||
if ((offset + len) < integer_type(s.size())) {
|
||||
p.append(s.substr(offset + len, s.size() - offset - len));
|
||||
if (!vals.empty()) {
|
||||
p += vals;
|
||||
}
|
||||
res.set_string(p.str(), ccs);
|
||||
if ((offset + len) < cs_int(s.size())) {
|
||||
p += s.slice(offset + len, s.size());
|
||||
}
|
||||
res.set_str(std::move(p));
|
||||
});
|
||||
}
|
||||
|
||||
} /* namespace cubescript */
|
||||
} /* namespace cscript */
|
||||
|
|
|
@ -1,61 +1,38 @@
|
|||
libcubescript_header_src = [
|
||||
'../include/cubescript/cubescript.hh',
|
||||
'../include/cubescript/cubescript_conf.hh'
|
||||
]
|
||||
|
||||
libcubescript_src = [
|
||||
'cs_bcode.cc',
|
||||
'cs_error.cc',
|
||||
'cs_gen.cc',
|
||||
'cs_ident.cc',
|
||||
'cs_parser.cc',
|
||||
'cs_state.cc',
|
||||
'cs_std.cc',
|
||||
'cs_strman.cc',
|
||||
'cs_thread.cc',
|
||||
'cs_util.cc',
|
||||
'cs_val.cc',
|
||||
'cs_vm.cc',
|
||||
'lib_base.cc',
|
||||
'cubescript.cc',
|
||||
'lib_list.cc',
|
||||
'lib_math.cc',
|
||||
'lib_str.cc'
|
||||
]
|
||||
|
||||
lib_cxxflags = extra_cxxflags + [ '-DLIBCUBESCRIPT_BUILD' ]
|
||||
dyn_cxxflags = lib_cxxflags
|
||||
libostd_dep = dependency('libostd', fallback: ['libostd', 'libostd_static'])
|
||||
|
||||
lib_incdirs = libcubescript_includes + [include_directories('.')]
|
||||
|
||||
host_system = host_machine.system()
|
||||
os_uses_dlls = (host_system == 'windows' or host_system == 'cygwin')
|
||||
|
||||
if os_uses_dlls
|
||||
dyn_cxxflags += '-DLIBCUBESCRIPT_DLL'
|
||||
endif
|
||||
|
||||
if os_uses_dlls and get_option('default_library') == 'both'
|
||||
# we need a bunch of this nonsense on windows as both_libraries()
|
||||
# does not work reliably there; since DLLs work like they do, we
|
||||
# need to compile one set of object files with dllexport and the
|
||||
# other set without that, and there is no way to tell meson to
|
||||
# do that other than making two different targets...
|
||||
libcubescript_static = static_library('cubescript',
|
||||
libcubescript_src, include_directories: lib_incdirs,
|
||||
cpp_args: lib_cxxflags,
|
||||
install: true
|
||||
)
|
||||
libcubescript_dynamic = shared_library('cubescript',
|
||||
libcubescript_src, include_directories: lib_incdirs,
|
||||
cpp_args: dyn_cxxflags,
|
||||
install: true,
|
||||
version: meson.project_version()
|
||||
)
|
||||
libcubescript_target = libcubescript_dynamic
|
||||
else
|
||||
libcubescript_target = library('cubescript',
|
||||
libcubescript_src, include_directories: lib_incdirs,
|
||||
cpp_args: dyn_cxxflags,
|
||||
install: true,
|
||||
version: meson.project_version()
|
||||
)
|
||||
endif
|
||||
libcubescript_lib = both_libraries('cubescript',
|
||||
libcubescript_src,
|
||||
dependencies: libostd_dep,
|
||||
include_directories: libcubescript_includes + [include_directories('.')],
|
||||
cpp_args: extra_cxxflags,
|
||||
install: true,
|
||||
version: meson.project_version()
|
||||
)
|
||||
|
||||
libcubescript = declare_dependency(
|
||||
include_directories: libcubescript_includes,
|
||||
link_with: libcubescript_target
|
||||
link_with: libcubescript_lib.get_shared_lib()
|
||||
)
|
||||
|
||||
libcubescript_static = declare_dependency(
|
||||
include_directories: libcubescript_includes,
|
||||
link_with: libcubescript_lib.get_static_lib()
|
||||
)
|
||||
|
||||
install_headers(libcubescript_header_src, install_dir: dir_package_include)
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
[wrap-git]
|
||||
directory = libostd
|
||||
url = https://git.octaforge.org/OctaForge/libostd.git
|
||||
revision = head
|
|
@ -1,40 +0,0 @@
|
|||
lang_tests = [
|
||||
# test_name test_file expected_fail
|
||||
['simple example', 'simple', false],
|
||||
]
|
||||
|
||||
lib_tests = [
|
||||
]
|
||||
|
||||
test_runner = executable('runner',
|
||||
['runner.cc'],
|
||||
dependencies: libcubescript,
|
||||
include_directories: libcubescript_includes,
|
||||
cpp_args: extra_cxxflags,
|
||||
install: false
|
||||
)
|
||||
|
||||
penv = environment()
|
||||
penv.append('PATH', join_paths(build_root, 'src'))
|
||||
# when running tests for crossbuilds in wine, this is used instead
|
||||
penv.append('WINEPATH', join_paths(build_root, 'src'))
|
||||
|
||||
foreach tcase: lang_tests
|
||||
test(tcase[0],
|
||||
test_runner,
|
||||
args: [join_paths(meson.current_source_dir(), tcase[1] + '.cube')],
|
||||
should_fail: tcase[2],
|
||||
env: penv
|
||||
)
|
||||
endforeach
|
||||
|
||||
foreach tcase: lib_tests
|
||||
test_exe = executable(tcase[0],
|
||||
[tcase[0] + '.cc'],
|
||||
dependencies: libcubescript,
|
||||
include_directories: libcubescript_includes,
|
||||
cpp_args: extra_cxxflags,
|
||||
instalL: false
|
||||
)
|
||||
test(tcase[0], tcase[0], should_fail: tcase[1], env: penv)
|
||||
endforeach
|
|
@ -1,74 +0,0 @@
|
|||
/* a rudimentary test runner for cubescript files */
|
||||
|
||||
#ifdef _MSC_VER
|
||||
/* avoid silly complaints about fopen */
|
||||
# define _CRT_SECURE_NO_WARNINGS 1
|
||||
#endif
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <cubescript/cubescript.hh>
|
||||
|
||||
namespace cs = cubescript;
|
||||
|
||||
struct skip_test {};
|
||||
|
||||
static bool do_exec_file(cs::state &cs, char const *fname) {
|
||||
FILE *f = std::fopen(fname, "rb");
|
||||
if (!f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::fseek(f, 0, SEEK_END);
|
||||
auto len = std::ftell(f);
|
||||
std::fseek(f, 0, SEEK_SET);
|
||||
|
||||
auto buf = std::make_unique<char[]>(len + 1);
|
||||
if (!buf) {
|
||||
std::fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (std::fread(buf.get(), 1, len, f) != std::size_t(len)) {
|
||||
std::fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
buf[len] = '\0';
|
||||
|
||||
cs.compile(std::string_view{buf.get(), std::size_t(len)}, fname).call(cs);
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc != 2) {
|
||||
std::fprintf(stderr, "error: incorrect number of arguments\n");
|
||||
}
|
||||
|
||||
cs::state gcs;
|
||||
cs::std_init_all(gcs);
|
||||
|
||||
gcs.new_command("echo", "...", [](auto &s, auto args, auto &) {
|
||||
std::printf("%s\n", cs::concat_values(s, args, " ").data());
|
||||
});
|
||||
|
||||
gcs.new_command("skip_test", "", [](auto &, auto, auto &) {
|
||||
throw skip_test{};
|
||||
});
|
||||
|
||||
try {
|
||||
do_exec_file(gcs, argv[1]);
|
||||
} catch (skip_test) {
|
||||
return 77;
|
||||
} catch (cs::error const &e) {
|
||||
std::fprintf(stderr, "error: %s", e.what().data());
|
||||
return 1;
|
||||
} catch (...) {
|
||||
std::fprintf(stderr, "error: unknown error");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
// a simple test mainly to serve as an example
|
||||
|
||||
echo "hello world"
|
||||
|
||||
// addition
|
||||
assert [= (+ 5 10) 15]
|
||||
assert [!= (+ 4 5) 11]
|
||||
|
||||
// multiplication
|
||||
assert [= (* 5 2) 10]
|
||||
assert [!= (* 3 4) 13]
|
||||
|
||||
// string equality
|
||||
assert [=s "hello world" "hello world"]
|
||||
assert [!=s "hello world" "dlrow olleh"]
|
||||
|
||||
// simple loop
|
||||
x = 5
|
||||
loop i 3 [x = (* $x 2); echo $x]
|
||||
|
||||
assert [= $x 40]
|
|
@ -2,26 +2,18 @@
|
|||
/* use nothing (no line editing support) */
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
inline void init_lineedit(cs::state &, std::string_view) {
|
||||
#include <ostd/string.hh>
|
||||
|
||||
inline void init_lineedit(cs_state &, ostd::string_range) {
|
||||
}
|
||||
|
||||
inline std::optional<std::string> read_line(cs::state &, cs::string_var &pr) {
|
||||
std::string lbuf;
|
||||
char buf[512];
|
||||
printf("%s", pr.value().data());
|
||||
std::fflush(stdout);
|
||||
while (fgets(buf, sizeof(buf), stdin)) {
|
||||
lbuf += static_cast<char const *>(buf);
|
||||
if (strchr(buf, '\n')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return std::move(lbuf);
|
||||
inline std::optional<std::string> read_line(cs_state &, cs_svar *pr) {
|
||||
ostd::write(pr->get_value());
|
||||
return std::move(ostd::cin.get_line(ostd::appender<std::string>()).get());
|
||||
}
|
||||
|
||||
inline void add_history(cs::state &, std::string_view) {
|
||||
inline void add_history(cs_state &, ostd::string_range) {
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#ifdef CS_REPL_USE_LINENOISE
|
||||
#ifdef OSTD_PLATFORM_POSIX
|
||||
#ifndef CS_REPL_HAS_EDIT
|
||||
#define CS_REPL_HAS_EDIT
|
||||
/* use the bundled linenoise library, default */
|
||||
|
@ -9,52 +10,59 @@
|
|||
|
||||
#include <optional>
|
||||
|
||||
#include <ostd/string.hh>
|
||||
|
||||
#include "linenoise.hh"
|
||||
|
||||
static cs::state *ln_cs = nullptr;
|
||||
static cs_state *ln_cs = nullptr;
|
||||
|
||||
inline void ln_complete(char const *buf, std::vector<std::string> &lc) {
|
||||
std::string_view cmd = get_complete_cmd(buf);
|
||||
for (std::size_t i = 0; i < ln_cs->ident_count(); ++i) {
|
||||
auto &id = ln_cs->get_ident(i);
|
||||
if (id.type() != cs::ident_type::COMMAND) {
|
||||
inline void ln_complete(char const *buf, linenoiseCompletions *lc) {
|
||||
ostd::string_range cmd = get_complete_cmd(buf);
|
||||
for (auto id: ln_cs->get_idents()) {
|
||||
if (!id->is_command()) {
|
||||
continue;
|
||||
}
|
||||
std::string_view idname = id.name();
|
||||
ostd::string_range idname = id->get_name();
|
||||
if (idname.size() <= cmd.size()) {
|
||||
continue;
|
||||
}
|
||||
if (idname.substr(0, cmd.size()) == cmd) {
|
||||
lc.emplace_back(idname);
|
||||
if (idname.slice(0, cmd.size()) == cmd) {
|
||||
linenoiseAddCompletion(lc, idname.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string ln_hint(char const *buf, int &color, int &bold) {
|
||||
cs::command *cmd = get_hint_cmd(*ln_cs, buf);
|
||||
inline char *ln_hint(char const *buf, int *color, int *bold) {
|
||||
cs_command *cmd = get_hint_cmd(*ln_cs, buf);
|
||||
if (!cmd) {
|
||||
return std::string{};
|
||||
return nullptr;
|
||||
}
|
||||
std::string args = " [";
|
||||
fill_cmd_args(args, cmd->args());
|
||||
fill_cmd_args(args, cmd->get_args());
|
||||
args += ']';
|
||||
color = 35;
|
||||
bold = 1;
|
||||
return args;
|
||||
*color = 35;
|
||||
*bold = 1;
|
||||
char *ret = new char[args.size() + 1];
|
||||
memcpy(ret, args.data(), args.size() + 1);
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline void init_lineedit(cs::state &cs, std::string_view) {
|
||||
inline void ln_hint_free(void *hint) {
|
||||
delete[] static_cast<char *>(hint);
|
||||
}
|
||||
|
||||
inline void init_lineedit(cs_state &cs, ostd::string_range) {
|
||||
/* sensible default history size */
|
||||
linenoise::SetHistoryMaxLen(1000);
|
||||
linenoiseHistorySetMaxLen(1000);
|
||||
ln_cs = &cs;
|
||||
linenoise::SetCompletionCallback(ln_complete);
|
||||
linenoise::SetHintsCallback(ln_hint);
|
||||
linenoiseSetCompletionCallback(ln_complete);
|
||||
linenoiseSetHintsCallback(ln_hint);
|
||||
linenoiseSetFreeHintsCallback(ln_hint_free);
|
||||
}
|
||||
|
||||
inline std::optional<std::string> read_line(cs::state &s, cs::builtin_var &pr) {
|
||||
std::string line;
|
||||
auto quit = linenoise::Readline(pr.value().get_string(s).data(), line);
|
||||
if (quit) {
|
||||
inline std::optional<std::string> read_line(cs_state &, cs_svar *pr) {
|
||||
auto line = linenoise(pr->get_value().data());
|
||||
if (!line) {
|
||||
/* linenoise traps ctrl-c, detect it and let the user exit */
|
||||
if (errno == EAGAIN) {
|
||||
raise(SIGINT);
|
||||
|
@ -62,13 +70,16 @@ inline std::optional<std::string> read_line(cs::state &s, cs::builtin_var &pr) {
|
|||
}
|
||||
return std::string{};
|
||||
}
|
||||
return line;
|
||||
std::string ret = line;
|
||||
linenoiseFree(line);
|
||||
return std::move(ret);
|
||||
}
|
||||
|
||||
inline void add_history(cs::state &, std::string_view line) {
|
||||
inline void add_history(cs_state &, ostd::string_range line) {
|
||||
/* backed by std::string so it's terminated */
|
||||
linenoise::AddHistory(line.data());
|
||||
linenoiseHistoryAdd(line.data());
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
#ifdef CS_REPL_USE_READLINE
|
||||
#ifndef CS_REPL_HAS_EDIT
|
||||
#define CS_REPL_HAS_EDIT
|
||||
/* use the GNU readline library */
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <ostd/string.hh>
|
||||
|
||||
#include <readline/readline.h>
|
||||
#include <readline/history.h>
|
||||
|
||||
static cs_state *rd_cs = nullptr;
|
||||
|
||||
inline char *ln_complete_list(char const *buf, int state) {
|
||||
static ostd::string_range cmd;
|
||||
static ostd::iterator_range<cs_ident **> itr;
|
||||
|
||||
if (!state) {
|
||||
cmd = get_complete_cmd(buf);
|
||||
itr = rd_cs->get_idents();
|
||||
}
|
||||
|
||||
for (; !itr.empty(); itr.pop_front()) {
|
||||
cs_ident *id = itr.front();
|
||||
if (!id->is_command()) {
|
||||
continue;
|
||||
}
|
||||
ostd::string_range idname = id->get_name();
|
||||
if (idname.size() <= cmd.size()) {
|
||||
continue;
|
||||
}
|
||||
if (idname.slice(0, cmd.size()) == cmd) {
|
||||
itr.pop_front();
|
||||
return strdup(idname.data());
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline char **ln_complete(char const *buf, int, int) {
|
||||
rl_attempted_completion_over = 1;
|
||||
return rl_completion_matches(buf, ln_complete_list);
|
||||
}
|
||||
|
||||
inline void ln_hint() {
|
||||
cs_command *cmd = get_hint_cmd(*rd_cs, rl_line_buffer);
|
||||
if (!cmd) {
|
||||
rl_redisplay();
|
||||
return;
|
||||
}
|
||||
std::string old = rl_line_buffer;
|
||||
std::string args = old;
|
||||
args += " [";
|
||||
fill_cmd_args(args, cmd->get_args());
|
||||
args += "] ";
|
||||
rl_extend_line_buffer(args.size());
|
||||
rl_replace_line(args.data(), 0);
|
||||
rl_redisplay();
|
||||
rl_replace_line(old.data(), 0);
|
||||
}
|
||||
|
||||
inline void init_lineedit(cs_state &cs, ostd::string_range) {
|
||||
rd_cs = &cs;
|
||||
rl_attempted_completion_function = ln_complete;
|
||||
rl_redisplay_function = ln_hint;
|
||||
}
|
||||
|
||||
inline std::optional<std::string> read_line(cs_state &, cs_svar *pr) {
|
||||
auto line = readline(pr->get_value().data());
|
||||
if (!line) {
|
||||
return std::string();
|
||||
}
|
||||
std::string ret = line;
|
||||
free(line);
|
||||
return std::move(ret);
|
||||
}
|
||||
|
||||
inline void add_history(cs_state &, ostd::string_range line) {
|
||||
/* backed by std::string so it's terminated */
|
||||
add_history(line.data());
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
2474
tools/linenoise.hh
2474
tools/linenoise.hh
File diff suppressed because it is too large
Load Diff
|
@ -2,18 +2,21 @@ repl_src = [
|
|||
'repl.cc'
|
||||
]
|
||||
|
||||
repl_deps = [libcubescript]
|
||||
repl_deps = [libostd_dep, libcubescript]
|
||||
repl_flags = []
|
||||
|
||||
if not get_option('repl').disabled()
|
||||
if not get_option('linenoise').disabled()
|
||||
repl_flags = ['-DCS_REPL_USE_LINENOISE']
|
||||
endif
|
||||
executable('cubescript',
|
||||
repl_src,
|
||||
dependencies: repl_deps,
|
||||
include_directories: libcubescript_includes + [include_directories('.')],
|
||||
cpp_args: extra_cxxflags + repl_flags,
|
||||
install: true
|
||||
)
|
||||
if get_option('readline')
|
||||
repl_deps += [dependency('readline')]
|
||||
repl_flags = ['-DCS_REPL_USE_READLINE']
|
||||
elif get_option('linenoise')
|
||||
repl_src += ['linenoise.cc']
|
||||
repl_flags = ['-DCS_REPL_USE_LINENOISE']
|
||||
endif
|
||||
|
||||
executable('cubescript',
|
||||
repl_src,
|
||||
dependencies: repl_deps,
|
||||
include_directories: libcubescript_includes + [include_directories('.')],
|
||||
cpp_args: extra_cxxflags + repl_flags,
|
||||
install: true
|
||||
)
|
||||
|
|
290
tools/repl.cc
290
tools/repl.cc
|
@ -1,32 +1,20 @@
|
|||
#ifdef _MSC_VER
|
||||
/* avoid silly complaints about fopen */
|
||||
# define _CRT_SECURE_NO_WARNINGS 1
|
||||
/* work around clang bug with std::function (needed by linenoise) */
|
||||
# if defined(__clang__) && !defined(_HAS_STATIC_RTTI)
|
||||
# define _HAS_STATIC_RTTI 0
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
|
||||
#include <ostd/platform.hh>
|
||||
#include <ostd/io.hh>
|
||||
#include <ostd/string.hh>
|
||||
|
||||
#include <cubescript/cubescript.hh>
|
||||
|
||||
namespace cs = cubescript;
|
||||
using namespace cscript;
|
||||
|
||||
std::string_view version = "CubeScript 0.0.1";
|
||||
ostd::string_range version = "CubeScript 0.0.1";
|
||||
|
||||
/* util */
|
||||
|
||||
#if defined(_WIN32)
|
||||
#ifdef OSTD_PLATFORM_WIN32
|
||||
#include <io.h>
|
||||
static bool stdin_is_tty() {
|
||||
return _isatty(_fileno(stdin));
|
||||
|
@ -40,31 +28,40 @@ static bool stdin_is_tty() {
|
|||
|
||||
/* line editing support */
|
||||
|
||||
inline std::string_view get_complete_cmd(std::string_view buf) {
|
||||
std::string_view not_allowed = "\"/;()[] \t\r\n\0";
|
||||
auto found = buf.find_first_of(not_allowed);
|
||||
while (found != buf.npos) {
|
||||
buf = buf.substr(found + 1, buf.size() - found - 1);
|
||||
found = buf.find_first_of(not_allowed);
|
||||
inline ostd::string_range get_complete_cmd(ostd::string_range buf) {
|
||||
ostd::string_range not_allowed = "\"/;()[] \t\r\n\0";
|
||||
ostd::string_range found = ostd::find_one_of(buf, not_allowed);
|
||||
while (!found.empty()) {
|
||||
++found;
|
||||
buf = found;
|
||||
found = ostd::find_one_of(found, not_allowed);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
inline std::string_view get_arg_type(char arg) {
|
||||
inline ostd::string_range get_arg_type(char arg) {
|
||||
switch (arg) {
|
||||
case 'i':
|
||||
return "int";
|
||||
case 'b':
|
||||
return "int_min";
|
||||
case 'f':
|
||||
return "float";
|
||||
case 'a':
|
||||
case 'F':
|
||||
return "float_prev";
|
||||
case 't':
|
||||
return "any";
|
||||
case 'c':
|
||||
case 'T':
|
||||
return "any_m";
|
||||
case 'E':
|
||||
return "cond";
|
||||
case 'N':
|
||||
return "numargs";
|
||||
case 'S':
|
||||
return "str_m";
|
||||
case 's':
|
||||
return "str";
|
||||
case 'b':
|
||||
case 'e':
|
||||
return "block";
|
||||
case 'r':
|
||||
return "ident";
|
||||
|
@ -74,19 +71,21 @@ inline std::string_view get_arg_type(char arg) {
|
|||
return "illegal";
|
||||
}
|
||||
|
||||
inline void fill_cmd_args(std::string &writer, std::string_view args) {
|
||||
bool variadic = false;
|
||||
inline void fill_cmd_args(std::string &writer, ostd::string_range args) {
|
||||
char variadic = '\0';
|
||||
int nrep = 0;
|
||||
if ((args.size() >= 3) && (args.substr(args.size() - 3) == "...")) {
|
||||
variadic = true;
|
||||
args.remove_suffix(3);
|
||||
if (!args.empty() && ((args.back() == 'V') || (args.back() == 'C'))) {
|
||||
variadic = args.back();
|
||||
args.pop_back();
|
||||
if (!args.empty() && isdigit(args.back())) {
|
||||
nrep = args.back() - '0';
|
||||
args.remove_suffix(1);
|
||||
args.pop_back();
|
||||
}
|
||||
}
|
||||
if (args.empty()) {
|
||||
if (variadic) {
|
||||
if (variadic == 'C') {
|
||||
writer += "concat(...)";
|
||||
} else if (variadic == 'V') {
|
||||
writer += "...";
|
||||
}
|
||||
return;
|
||||
|
@ -97,14 +96,17 @@ inline void fill_cmd_args(std::string &writer, std::string_view args) {
|
|||
if (i != 0) {
|
||||
writer += ", ";
|
||||
}
|
||||
writer += get_arg_type(args.front());
|
||||
args.remove_prefix(1);
|
||||
writer += get_arg_type(*args);
|
||||
++args;
|
||||
}
|
||||
}
|
||||
if (variadic) {
|
||||
if (norep > 0) {
|
||||
writer += ", ";
|
||||
}
|
||||
if (variadic == 'C') {
|
||||
writer += "concat(";
|
||||
}
|
||||
if (!args.empty()) {
|
||||
if (args.size() > 1) {
|
||||
writer += '{';
|
||||
|
@ -120,168 +122,124 @@ inline void fill_cmd_args(std::string &writer, std::string_view args) {
|
|||
}
|
||||
}
|
||||
writer += "...";
|
||||
if (variadic == 'C') {
|
||||
writer += ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline cs::command *get_hint_cmd(cs::state &cs, std::string_view buf) {
|
||||
std::string_view nextchars = "([;";
|
||||
auto lp = buf.find_first_of(nextchars);
|
||||
if (lp != buf.npos) {
|
||||
cs::command *cmd = get_hint_cmd(cs, buf.substr(1, buf.size() - 1));
|
||||
inline cs_command *get_hint_cmd(cs_state &cs, ostd::string_range buf) {
|
||||
ostd::string_range nextchars = "([;";
|
||||
auto lp = ostd::find_one_of(buf, nextchars);
|
||||
if (!lp.empty()) {
|
||||
cs_command *cmd = get_hint_cmd(cs, buf.slice(1, buf.size()));
|
||||
if (cmd) {
|
||||
return cmd;
|
||||
}
|
||||
}
|
||||
std::size_t nsp = 0;
|
||||
for (auto c: buf) {
|
||||
if (!isspace(c)) {
|
||||
break;
|
||||
}
|
||||
++nsp;
|
||||
while (!buf.empty() && isspace(buf.front())) {
|
||||
++buf;
|
||||
}
|
||||
buf.remove_prefix(nsp);
|
||||
std::string_view spaces = " \t\r\n";
|
||||
auto p = buf.find_first_of(spaces);
|
||||
if (p != buf.npos) {
|
||||
buf = buf.substr(0, p);
|
||||
ostd::string_range spaces = " \t\r\n";
|
||||
ostd::string_range s = ostd::find_one_of(buf, spaces);
|
||||
if (!s.empty()) {
|
||||
buf = buf.slice(0, &s[0] - &buf[0]);
|
||||
}
|
||||
if (!buf.empty()) {
|
||||
auto cmd = cs.get_ident(buf);
|
||||
if (cmd && (cmd->get().type() == cs::ident_type::COMMAND)) {
|
||||
return static_cast<cs::command *>(&cmd->get());
|
||||
}
|
||||
return cmd ? cmd->get_command() : nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#include "edit_linenoise.hh"
|
||||
#include "edit_readline.hh"
|
||||
#include "edit_fallback.hh"
|
||||
|
||||
/* usage */
|
||||
|
||||
void print_usage(std::string_view progname, bool err) {
|
||||
std::fprintf(
|
||||
err ? stderr : stdout,
|
||||
"Usage: %s [options] [file]\n"
|
||||
void print_usage(ostd::string_range progname, bool err) {
|
||||
auto &s = err ? ostd::cerr : ostd::cout;
|
||||
s.writeln(
|
||||
"Usage: ", progname, " [options] [file]\n"
|
||||
"Options:\n"
|
||||
" -e str call string \"str\"\n"
|
||||
" -e str run string \"str\"\n"
|
||||
" -i enter interactive mode after the above\n"
|
||||
" -v show version information\n"
|
||||
" -h show this message\n"
|
||||
" -- stop handling options\n"
|
||||
" - execute stdin and stop handling options"
|
||||
"\n",
|
||||
progname.data()
|
||||
);
|
||||
s.flush();
|
||||
}
|
||||
|
||||
void print_version() {
|
||||
printf("%s\n", version.data());
|
||||
ostd::writeln(version);
|
||||
}
|
||||
|
||||
static cs::state *scs = nullptr;
|
||||
static cs_state *scs = nullptr;
|
||||
static void do_sigint(int n) {
|
||||
/* in case another SIGINT happens, terminate normally */
|
||||
signal(n, SIG_DFL);
|
||||
scs->call_hook([](cs::state &css) {
|
||||
css.call_hook(nullptr);
|
||||
throw cs::error{css, "<execution interrupted>"};
|
||||
scs->set_call_hook([](cs_state &cs) {
|
||||
cs.set_call_hook(nullptr);
|
||||
throw cscript::cs_error(cs, "<execution interrupted>");
|
||||
});
|
||||
}
|
||||
|
||||
static bool do_exec_file(
|
||||
cs::state &cs, std::string_view fname, cs::any_value &ret
|
||||
) {
|
||||
FILE *f = std::fopen(fname.data(), "rb");
|
||||
if (!f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::fseek(f, 0, SEEK_END);
|
||||
auto len = std::ftell(f);
|
||||
std::fseek(f, 0, SEEK_SET);
|
||||
|
||||
auto buf = std::make_unique<char[]>(len + 1);
|
||||
if (!buf) {
|
||||
std::fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (std::fread(buf.get(), 1, len, f) != std::size_t(len)) {
|
||||
std::fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
buf[len] = '\0';
|
||||
|
||||
ret = cs.compile(
|
||||
std::string_view{buf.get(), std::size_t(len)}, fname
|
||||
).call(cs);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool do_call(cs::state &cs, std::string_view line, bool file = false) {
|
||||
cs::any_value ret{};
|
||||
static bool do_call(cs_state &cs, ostd::string_range line, bool file = false) {
|
||||
cs_value ret;
|
||||
scs = &cs;
|
||||
signal(SIGINT, do_sigint);
|
||||
try {
|
||||
if (file) {
|
||||
if (!do_exec_file(cs, line, ret)) {
|
||||
std::fprintf(stderr, "cannot read file: %s\n", line.data());
|
||||
if (!cs.run_file(line, ret)) {
|
||||
ostd::cerr.writeln("cannot read file: ", line);
|
||||
}
|
||||
} else {
|
||||
ret = cs.compile(line).call(cs);
|
||||
cs.run(line, ret);
|
||||
}
|
||||
} catch (cs::error const &e) {
|
||||
} catch (cscript::cs_error const &e) {
|
||||
signal(SIGINT, SIG_DFL);
|
||||
scs = nullptr;
|
||||
std::string_view terr = e.what();
|
||||
auto col = terr.find(':');
|
||||
ostd::string_range terr = e.what();
|
||||
auto col = ostd::find(terr, ':');
|
||||
bool is_lnum = false;
|
||||
if (col != terr.npos) {
|
||||
auto pre = terr.substr(0, col);
|
||||
auto it = std::find_if(
|
||||
pre.begin(), pre.end(),
|
||||
if (!col.empty()) {
|
||||
is_lnum = ostd::find_if(
|
||||
terr.slice(0, &col[0] - &terr[0]),
|
||||
[](auto c) { return !isdigit(c); }
|
||||
);
|
||||
is_lnum = (it == pre.end());
|
||||
terr = terr.substr(col + 2, terr.size() - col - 2);
|
||||
).empty();
|
||||
terr = col.slice(2, col.size());
|
||||
}
|
||||
if (!file && ((terr == "missing \"]\"") || (terr == "missing \")\""))) {
|
||||
return true;
|
||||
}
|
||||
std::printf(
|
||||
"%s%s\n", !is_lnum ? "stdin: " : "stdin:", e.what().data()
|
||||
);
|
||||
std::size_t pindex = 1;
|
||||
for (auto &nd: e.stack()) {
|
||||
std::printf(" ");
|
||||
if ((nd.index == 1) && (pindex > 2)) {
|
||||
std::printf("..");
|
||||
}
|
||||
pindex = nd.index;
|
||||
std::printf("%zu) %s\n", nd.index, nd.id.name().data());
|
||||
ostd::writeln(!is_lnum ? "stdin: " : "stdin:", e.what());
|
||||
if (e.get_stack().get()) {
|
||||
cscript::util::print_stack(ostd::cout.iter(), e.get_stack());
|
||||
ostd::write('\n');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
signal(SIGINT, SIG_DFL);
|
||||
scs = nullptr;
|
||||
if (ret.type() != cs::value_type::NONE) {
|
||||
std::printf("%s\n", std::string_view{ret.get_string(cs)}.data());
|
||||
if (ret.get_type() != cs_value_type::Null) {
|
||||
ostd::writeln(ret.get_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void do_tty(cs::state &cs) {
|
||||
auto &prompt = cs.new_var("PROMPT", "> ");
|
||||
auto &prompt2 = cs.new_var("PROMPT2", ">> ");
|
||||
static void do_tty(cs_state &cs) {
|
||||
auto prompt = cs.new_svar("PROMPT", "> ");
|
||||
auto prompt2 = cs.new_svar("PROMPT2", ">> ");
|
||||
|
||||
bool do_exit = false;
|
||||
cs.new_command("quit", "", [&do_exit](auto &, auto, auto &) {
|
||||
do_exit = true;
|
||||
});
|
||||
|
||||
std::printf("%s (REPL mode)\n", version.data());
|
||||
ostd::writeln(version, " (REPL mode)");
|
||||
for (;;) {
|
||||
auto line = read_line(cs, prompt);
|
||||
if (!line) {
|
||||
|
@ -313,67 +271,21 @@ static void do_tty(cs::state &cs) {
|
|||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
cs::state gcs;
|
||||
cs::std_init_all(gcs);
|
||||
cs_state gcs;
|
||||
gcs.init_libs();
|
||||
|
||||
/* this is how you can override a setter for variables; fvar and svar
|
||||
* work equivalently - in this case we want to allow multiple values
|
||||
* to be set, but you may also not be using standard i/o and so on
|
||||
*/
|
||||
gcs.new_command("//ivar", "$iii#", [](auto &css, auto args, auto &) {
|
||||
auto &iv = static_cast<cs::builtin_var &>(args[0].get_ident(css));
|
||||
auto nargs = args[4].get_integer();
|
||||
if (nargs <= 1) {
|
||||
auto val = iv.value().get_integer();
|
||||
if ((val >= 0) && (val < 0xFFFFFF)) {
|
||||
std::printf(
|
||||
"%s = %d (0x%.6X: %d, %d, %d)\n",
|
||||
iv.name().data(), val, val,
|
||||
(val >> 16) & 0xFF, (val >> 8) & 0xFF, val & 0xFF
|
||||
);
|
||||
} else {
|
||||
std::printf("%s = %d\n", iv.name().data(), val);
|
||||
}
|
||||
return;
|
||||
}
|
||||
cs::any_value nv;
|
||||
if (nargs == 2) {
|
||||
nv = args[1];
|
||||
} else if (nargs == 3) {
|
||||
nv = (args[1].get_integer() << 8) | (args[2].get_integer() << 16);
|
||||
} else {
|
||||
nv = (
|
||||
args[1].get_integer() | (args[2].get_integer() << 8) |
|
||||
(args[3].get_integer() << 16)
|
||||
);
|
||||
}
|
||||
iv.set_value(css, nv);
|
||||
});
|
||||
|
||||
gcs.new_command("//var_changed", "$aa", [](auto &css, auto args, auto &) {
|
||||
std::printf(
|
||||
"changed var trigger: %s (was: '%s', now: '%s')\n",
|
||||
args[0].get_ident(css).name().data(),
|
||||
args[1].get_string(css).data(),
|
||||
args[2].get_string(css).data()
|
||||
);
|
||||
});
|
||||
|
||||
gcs.new_command("exec", "s", [](auto &css, auto args, auto &) {
|
||||
auto file = args[0].get_string(css);
|
||||
cs::any_value val{};
|
||||
bool ret = do_exec_file(css, file, val);
|
||||
gcs.new_command("exec", "s", [](auto &cs, auto args, auto &) {
|
||||
auto file = args[0].get_strr();
|
||||
bool ret = cs.run_file(file);
|
||||
if (!ret) {
|
||||
char buf[4096];
|
||||
std::snprintf(
|
||||
buf, sizeof(buf), "could not execute file \"%s\"", file.data()
|
||||
throw cscript::cs_error(
|
||||
cs, "could not run file \"%s\"", file
|
||||
);
|
||||
throw cs::error(css, buf);
|
||||
}
|
||||
});
|
||||
|
||||
gcs.new_command("echo", "...", [](auto &css, auto args, auto &) {
|
||||
std::printf("%s\n", cs::concat_values(css, args, " ").data());
|
||||
gcs.new_command("echo", "C", [](auto &, auto args, auto &) {
|
||||
ostd::writeln(args[0].get_strr());
|
||||
});
|
||||
|
||||
int firstarg = 0;
|
||||
|
@ -459,8 +371,8 @@ endargs:
|
|||
return 0;
|
||||
} else {
|
||||
std::string str;
|
||||
for (int c = '\0'; (c = std::fgetc(stdin)) != EOF;) {
|
||||
str += char(c);
|
||||
for (char c = '\0'; (c = ostd::cin.get_char()) != EOF;) {
|
||||
str += c;
|
||||
}
|
||||
do_call(gcs, str);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue