forked from KPGPMC/furlang
Compare commits
199 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
3b98f77c08
|
|||
|
959d0a7773
|
|||
|
70375af068
|
|||
|
46d0de759a
|
|||
|
01964a495c
|
|||
|
21fb587e36
|
|||
|
fe65a09af9
|
|||
|
712a0d7047
|
|||
|
35d7fc4970
|
|||
|
9bf4da4fd9
|
|||
|
6c7d16631d
|
|||
|
24a9530abc
|
|||
|
0354ec1e79
|
|||
|
ddb60ca93a
|
|||
|
3aaed55485
|
|||
|
cfefca49ee
|
|||
|
21e963168e
|
|||
|
b067bbb4ea
|
|||
|
fe37bf8058
|
|||
|
977b22d8f5
|
|||
|
30a06186db
|
|||
|
5e425d7fe4
|
|||
|
12a131c2b8
|
|||
|
5403c0e1a4
|
|||
|
a1dac8ae3f
|
|||
|
a311795915
|
|||
|
090d8ad81e
|
|||
|
5efcf1b9b0
|
|||
|
136663aeab
|
|||
|
56ea4ba2ab
|
|||
|
dab7f73216
|
|||
|
1d4e8bb1d0
|
|||
|
ed84a65757
|
|||
|
26efd110f1
|
|||
|
dd96f95d77
|
|||
|
70cbb003f2
|
|||
|
665982049d
|
|||
|
195d154474
|
|||
|
26d935e22a
|
|||
|
a4966a057c
|
|||
|
b3ce4fa0ed
|
|||
|
29dd9abb3f
|
|||
|
ffe1208775
|
|||
|
a382b92cf0
|
|||
|
c59f0252ed
|
|||
|
5c841a1428
|
|||
|
6d176e58f3
|
|||
|
6737ed86b0
|
|||
|
4ede6f3476
|
|||
|
c841427eff
|
|||
|
a685ecf226
|
|||
|
84b70077c1
|
|||
|
21e92dcb4b
|
|||
|
713aec199f
|
|||
|
15218d7530
|
|||
|
d419b62225
|
|||
|
c68763f785
|
|||
|
dcefe20d47
|
|||
|
989fcb7199
|
|||
|
4cffd27a33
|
|||
|
f9b910ae13
|
|||
|
7a7a1ec64c
|
|||
|
99dba99db2
|
|||
|
cc88d6b6a0
|
|||
|
0e916c314a
|
|||
|
b9d50c62af
|
|||
|
4b9f58b34f
|
|||
|
a420ebf69f
|
|||
|
e1ffa8a11d
|
|||
|
2126b03069
|
|||
|
47e8b1bf51
|
|||
|
7ebc27cab9
|
|||
|
1db13b4e10
|
|||
|
84c6795ff8
|
|||
|
212b1e2384
|
|||
|
ecdf6944a7
|
|||
|
79b6b9742d
|
|||
|
2d40b9337c
|
|||
|
db485af7f6
|
|||
|
854343a5a3
|
|||
|
3faafe371f
|
|||
|
4604f5186e
|
|||
|
224468446d
|
|||
|
549af109b9
|
|||
|
6dce7016ce
|
|||
|
a1e43576cc
|
|||
|
9f950e45e7
|
|||
|
dfaa3081cb
|
|||
|
46e92deed5
|
|||
|
42e71080a2
|
|||
|
03448d865e
|
|||
|
cb845ca625
|
|||
|
798287ef47
|
|||
|
466d7f003b
|
|||
|
ff3fce53f5
|
|||
|
9b280fab22
|
|||
|
e1b75ccc8e
|
|||
|
cd715ae779
|
|||
|
8d3859ce70
|
|||
|
3c0588e8db
|
|||
|
abbc1714c1
|
|||
|
b7da2f3d32
|
|||
|
d49b4f5bc7
|
|||
|
7de645d323
|
|||
|
2519560451
|
|||
|
7c74850aff
|
|||
|
81a439b2ad
|
|||
|
b913fc0c8b
|
|||
|
ee9eadeea8
|
|||
|
642d57622a
|
|||
|
9690ac3617
|
|||
|
f89f643870
|
|||
|
100c542fe1
|
|||
|
cf284592fd
|
|||
|
f5ebc767ab
|
|||
|
484e16963f
|
|||
|
575054b75a
|
|||
|
a7ab214ce3
|
|||
|
e087c11008
|
|||
|
2164ab0d97
|
|||
|
11b05b8c18
|
|||
|
6ec4710eaa
|
|||
|
09252c3ec3
|
|||
|
3d461048cc
|
|||
|
482ab859ed
|
|||
|
42f020977e
|
|||
|
6ab9254746
|
|||
|
8466902281
|
|||
|
7b609f87c9
|
|||
|
0bcbe1d7d1
|
|||
|
8702298cd2
|
|||
|
55932f6113
|
|||
|
a3db4ef1e1
|
|||
| 079f6326a4 | |||
|
d8d81851d3
|
|||
|
abeb5b390b
|
|||
|
a61f5c194b
|
|||
|
d497e2de45
|
|||
| 1965ab26af | |||
|
70b7b6a2f4
|
|||
|
b5dcd6690a
|
|||
|
0baf896c3a
|
|||
| 318b3e8a65 | |||
|
7d2e748c27
|
|||
|
e485a7900b
|
|||
|
68e1f8fda0
|
|||
|
8909a0cfff
|
|||
|
dad2c180d6
|
|||
|
4478a839b7
|
|||
|
9549a7b61a
|
|||
|
51483db283
|
|||
|
3759c0361f
|
|||
|
1ead2f6592
|
|||
|
b6d3387388
|
|||
|
3a2fa32ce1
|
|||
|
f2294079a0
|
|||
|
39f873a984
|
|||
|
d46aa45c1f
|
|||
|
190aa8f985
|
|||
|
f80abd68ad
|
|||
|
99666495e7
|
|||
|
6f023765b5
|
|||
|
65e5fdf324
|
|||
|
5a1b7c5aa6
|
|||
|
f14f3854e2
|
|||
|
6fbdb2299b
|
|||
|
ec2c81fc56
|
|||
|
e9b38e95a2
|
|||
|
00bcc0e6a5
|
|||
|
e8ccaa6390
|
|||
|
c2b32c9604
|
|||
|
c9f714642c
|
|||
|
0dfa8c1c02
|
|||
|
78acfb6ee2
|
|||
|
43dac1ec6d
|
|||
|
b35a274e81
|
|||
|
63ea593c1e
|
|||
|
8dfc880c07
|
|||
|
eec25e3aea
|
|||
|
e809eb82c7
|
|||
|
9fccaefbfc
|
|||
|
a262014348
|
|||
|
986015bc09
|
|||
|
ac1e226b4b
|
|||
|
d0f3637539
|
|||
|
f1f62fad8c
|
|||
|
065a2af699
|
|||
|
767970c69b
|
|||
|
97c01bc96c
|
|||
|
abd975be23
|
|||
|
be386cf230
|
|||
|
a14252bd1d
|
|||
|
6b92e19b23
|
|||
|
865691df26
|
|||
|
b4fda8d7d0
|
|||
|
3f6fcc56ff
|
|||
|
c1b65838b0
|
|||
| eaa74fd573 | |||
| 9cd54c1c60 |
+9
-2
@@ -11,11 +11,13 @@ Checks: >
|
|||||||
-bugprone-assignment-in-if-condition,
|
-bugprone-assignment-in-if-condition,
|
||||||
-bugprone-easily-swappable-parameters,
|
-bugprone-easily-swappable-parameters,
|
||||||
-bugprone-multi-level-implicit-pointer-conversion,
|
-bugprone-multi-level-implicit-pointer-conversion,
|
||||||
|
-bugprone-unintended-char-ostream-output,
|
||||||
-readability-function-cognitive-complexity,
|
-readability-function-cognitive-complexity,
|
||||||
-readability-magic-numbers,
|
-readability-magic-numbers,
|
||||||
-readability-redundant-access-specifiers,
|
-readability-redundant-access-specifiers,
|
||||||
-readability-use-anyofallof,
|
-readability-use-anyofallof,
|
||||||
-readability-named-parameter,
|
-readability-named-parameter,
|
||||||
|
-readability-convert-member-functions-to-static,
|
||||||
-performance-enum-size,
|
-performance-enum-size,
|
||||||
-cppcoreguidelines-avoid-magic-numbers,
|
-cppcoreguidelines-avoid-magic-numbers,
|
||||||
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
|
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
|
||||||
@@ -28,7 +30,12 @@ Checks: >
|
|||||||
-cppcoreguidelines-macro-usage,
|
-cppcoreguidelines-macro-usage,
|
||||||
-cppcoreguidelines-owning-memory,
|
-cppcoreguidelines-owning-memory,
|
||||||
-cppcoreguidelines-non-private-member-variables-in-classes,
|
-cppcoreguidelines-non-private-member-variables-in-classes,
|
||||||
-cppcoreguidelines-pro-bounds-avoid-unchecked-container-access
|
-cppcoreguidelines-pro-bounds-avoid-unchecked-container-access,
|
||||||
|
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
|
||||||
|
-cppcoreguidelines-use-enum-class,
|
||||||
|
-bugprone-forward-declaration-namespace,
|
||||||
|
-bugprone-tagged-union-member-count,
|
||||||
|
-bugprone-unchecked-optional-access
|
||||||
|
|
||||||
WarningsAsErrors: "*"
|
WarningsAsErrors: "*"
|
||||||
|
|
||||||
@@ -95,4 +102,4 @@ CheckOptions:
|
|||||||
- key: readability-identifier-naming.EnumConstantCase
|
- key: readability-identifier-naming.EnumConstantCase
|
||||||
value: CamelCase
|
value: CamelCase
|
||||||
- key: readability-identifier-naming.ScopedEnumConstantCase
|
- key: readability-identifier-naming.ScopedEnumConstantCase
|
||||||
value: CamelCase
|
value: CamelCase
|
||||||
|
|||||||
@@ -4,16 +4,16 @@ set -e
|
|||||||
|
|
||||||
BUILD_DIR="build"
|
BUILD_DIR="build"
|
||||||
|
|
||||||
echo "Running clang-format..."
|
FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(cpp|hpp)$' | grep -v 'deps/' || true)
|
||||||
|
|
||||||
FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(cpp|hpp)$' || true)
|
|
||||||
|
|
||||||
if [ -z "$FILES" ]; then
|
if [ -z "$FILES" ]; then
|
||||||
echo "No C/C++ files to check"
|
echo "No C/C++ files to check"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
git diff -U0 --cached | \
|
echo "Running clang-format..."
|
||||||
|
|
||||||
|
git diff -U0 --cached -- $FILES | \
|
||||||
python3 <(curl -s https://raw.githubusercontent.com/llvm/llvm-project/refs/heads/main/clang/tools/clang-format/clang-format-diff.py) -p1 -i
|
python3 <(curl -s https://raw.githubusercontent.com/llvm/llvm-project/refs/heads/main/clang/tools/clang-format/clang-format-diff.py) -p1 -i
|
||||||
|
|
||||||
echo "Running clang-tidy..."
|
echo "Running clang-tidy..."
|
||||||
@@ -21,7 +21,8 @@ echo "Running clang-tidy..."
|
|||||||
for file in $FILES; do
|
for file in $FILES; do
|
||||||
clang-tidy \
|
clang-tidy \
|
||||||
"$file" \
|
"$file" \
|
||||||
|
--header-filter="^(?!.*deps/).*" \
|
||||||
-p "$BUILD_DIR"
|
-p "$BUILD_DIR"
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "pre-commit checks passed"
|
echo "pre-commit checks passed"
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "deps/isocline"]
|
||||||
|
path = deps/isocline
|
||||||
|
url = https://github.com/daanx/isocline
|
||||||
+11
-4
@@ -11,9 +11,16 @@ find_package(Doxygen)
|
|||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
add_subdirectory(furlang)
|
add_subdirectory(src/furlang)
|
||||||
add_subdirectory(furvm)
|
add_subdirectory(src/furvm)
|
||||||
add_subdirectory(furc)
|
add_subdirectory(src/furc)
|
||||||
|
add_subdirectory(src/furas)
|
||||||
|
add_subdirectory(src/disfuras)
|
||||||
|
add_subdirectory(src/furdb)
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
set(IC_USE_CXX ON)
|
||||||
|
add_subdirectory(${CMAKE_SOURCE_DIR}/deps/isocline)
|
||||||
|
|
||||||
if(DOXYGEN_FOUND)
|
if(DOXYGEN_FOUND)
|
||||||
set(DOXYGEN_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/docs)
|
set(DOXYGEN_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/docs)
|
||||||
@@ -24,4 +31,4 @@ if(DOXYGEN_FOUND)
|
|||||||
doxygen_add_docs(doxygen_doc ${CMAKE_SOURCE_DIR}/furlang/ ${CMAKE_SOURCE_DIR}/furvm/ ${CMAKE_SOURCE_DIR}/furc/
|
doxygen_add_docs(doxygen_doc ${CMAKE_SOURCE_DIR}/furlang/ ${CMAKE_SOURCE_DIR}/furvm/ ${CMAKE_SOURCE_DIR}/furc/
|
||||||
ALL WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
ALL WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||||
COMMENT "Generating API documentation with Doxygen")
|
COMMENT "Generating API documentation with Doxygen")
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
TBD
|
The commits must be as good as CHatingPython's.
|
||||||
|
|||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
Checks: '-*'
|
||||||
+1
Submodule deps/isocline added at 8d6dc1ef95
@@ -0,0 +1,35 @@
|
|||||||
|
type arr = array $s8 10
|
||||||
|
|
||||||
|
func println $arr = native println
|
||||||
|
|
||||||
|
public func main = #main
|
||||||
|
main:
|
||||||
|
array $arr
|
||||||
|
dup
|
||||||
|
store %0
|
||||||
|
|
||||||
|
lenof
|
||||||
|
store %1
|
||||||
|
|
||||||
|
push $s32 0
|
||||||
|
loop:
|
||||||
|
dup
|
||||||
|
load %1
|
||||||
|
ge
|
||||||
|
jnz #end
|
||||||
|
body:
|
||||||
|
dup
|
||||||
|
load %0
|
||||||
|
swap
|
||||||
|
push $s8 69
|
||||||
|
set
|
||||||
|
|
||||||
|
push $s32 1
|
||||||
|
add
|
||||||
|
|
||||||
|
jmp #loop
|
||||||
|
end:
|
||||||
|
drop
|
||||||
|
load %0
|
||||||
|
call $arr println
|
||||||
|
ret
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
type arr = array $s8 10
|
||||||
|
type arrSlice = slice $s8
|
||||||
|
|
||||||
|
func println $arrSlice = native println
|
||||||
|
|
||||||
|
public func main = #main
|
||||||
|
main:
|
||||||
|
array $arr
|
||||||
|
store %0
|
||||||
|
|
||||||
|
push $s32 2
|
||||||
|
store %1
|
||||||
|
|
||||||
|
push $s32 0
|
||||||
|
loop:
|
||||||
|
dup
|
||||||
|
load %1
|
||||||
|
ge
|
||||||
|
jnz #end
|
||||||
|
body:
|
||||||
|
dup
|
||||||
|
load %0
|
||||||
|
swap
|
||||||
|
push $s8 69
|
||||||
|
set
|
||||||
|
|
||||||
|
push $s32 1
|
||||||
|
add
|
||||||
|
|
||||||
|
jmp #loop
|
||||||
|
end:
|
||||||
|
drop
|
||||||
|
load %0
|
||||||
|
push $u32 0
|
||||||
|
load %1
|
||||||
|
slice
|
||||||
|
call $arrSlice println
|
||||||
|
ret
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
func println $u32 = native println
|
||||||
|
|
||||||
|
allocate uwu
|
||||||
|
|
||||||
|
public func main = #main
|
||||||
|
main:
|
||||||
|
push $u32 0
|
||||||
|
storeg %uwu
|
||||||
|
loop_header:
|
||||||
|
loadg %uwu
|
||||||
|
push $u32 10
|
||||||
|
ge
|
||||||
|
jnz #loop_end
|
||||||
|
loop_body:
|
||||||
|
loadg %uwu
|
||||||
|
call $u32 println
|
||||||
|
|
||||||
|
loadg %uwu
|
||||||
|
push $u32 1
|
||||||
|
add
|
||||||
|
storeg %uwu
|
||||||
|
|
||||||
|
jmp #loop_header
|
||||||
|
loop_end:
|
||||||
|
ret
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
func println $s32 = native println
|
||||||
|
|
||||||
|
public func main = #main
|
||||||
|
main:
|
||||||
|
push $s32 69
|
||||||
|
call $s32 println
|
||||||
|
ret
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
file(GLOB_RECURSE FURC_SRCS "src/**.cpp")
|
|
||||||
file(GLOB_RECURSE FURC_HDRS "include/**.hpp")
|
|
||||||
add_library(libfurc ${FURC_SRCS} ${FURC_HDRS})
|
|
||||||
target_include_directories(libfurc PUBLIC include/)
|
|
||||||
target_compile_definitions(libfurc PRIVATE LIBFURC)
|
|
||||||
set_target_properties(libfurc PROPERTIES PREFIX "")
|
|
||||||
target_link_libraries(libfurc PUBLIC furlang)
|
|
||||||
add_executable(furc src/main.cpp)
|
|
||||||
target_link_libraries(furc PRIVATE libfurc)
|
|
||||||
|
|
||||||
include(GoogleTest)
|
|
||||||
|
|
||||||
file(GLOB_RECURSE FURC_TESTS "test/**.cpp")
|
|
||||||
add_executable(furc_tests ${FURC_TESTS})
|
|
||||||
target_link_libraries(furc_tests PRIVATE libfurc GTest::gtest_main)
|
|
||||||
gtest_discover_tests(furc_tests)
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
#ifndef FURC_AST_DECLARATION_HPP
|
|
||||||
#define FURC_AST_DECLARATION_HPP
|
|
||||||
|
|
||||||
#include "furc/ast/node.hpp"
|
|
||||||
#include "furc/ast/statement.hpp"
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace furc {
|
|
||||||
namespace ast {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Declaration node type.
|
|
||||||
*/
|
|
||||||
enum class declaration_node_t {
|
|
||||||
Func, /**< Function declaration. */
|
|
||||||
FuncDef, /**< Function definition. */
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Declaration AST node interface.
|
|
||||||
*/
|
|
||||||
class declaration_node : public statement_node, public abstract_node {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Construct a new declaration AST node.
|
|
||||||
*
|
|
||||||
* @param location Node location.
|
|
||||||
*/
|
|
||||||
declaration_node(struct location location)
|
|
||||||
: abstract_node(location) {}
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's category.
|
|
||||||
*
|
|
||||||
* @return node_t::Declaration.
|
|
||||||
*/
|
|
||||||
node_t category() const override { return node_t::Declaration; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's statement type.
|
|
||||||
*
|
|
||||||
* @return statement_node_t::Declaration.
|
|
||||||
*/
|
|
||||||
statement_node_t statement_type() const final { return statement_node_t::Declaration; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's declaration type.
|
|
||||||
*
|
|
||||||
* @return The declaration type.
|
|
||||||
*/
|
|
||||||
virtual declaration_node_t declaration_type() const = 0;
|
|
||||||
protected:
|
|
||||||
bool equal(const node& rhs) const override;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Function declaration AST node.
|
|
||||||
*/
|
|
||||||
class function_declaration_node : public declaration_node {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Construct a new function declaration node object from name token.
|
|
||||||
*
|
|
||||||
* @param location Node location.
|
|
||||||
* @param name Name of the function.
|
|
||||||
*/
|
|
||||||
template <typename T>
|
|
||||||
function_declaration_node(struct location location, T&& name)
|
|
||||||
: declaration_node(location), p_name(std::forward<T>(name)) {}
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's declaration type.
|
|
||||||
*
|
|
||||||
* @return declaration_node_t::FunctionDeclaration.
|
|
||||||
*/
|
|
||||||
declaration_node_t declaration_type() const override { return declaration_node_t::Func; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns function's name.
|
|
||||||
*
|
|
||||||
* @return Name of the function.
|
|
||||||
*/
|
|
||||||
std::string name() const { return p_name; }
|
|
||||||
public:
|
|
||||||
void accept(visitor& visitor) const override;
|
|
||||||
|
|
||||||
std::ostream& print(std::ostream& os) const override;
|
|
||||||
protected:
|
|
||||||
bool equal(const node& rhs) const override;
|
|
||||||
protected:
|
|
||||||
/**
|
|
||||||
* @brief Name of the function.
|
|
||||||
*/
|
|
||||||
std::string p_name;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Function definition AST node.
|
|
||||||
*/
|
|
||||||
class function_definition_node final : public function_declaration_node {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Construct a new function definition node object from name and body.
|
|
||||||
*
|
|
||||||
* @param location Node location.
|
|
||||||
* @param name Name of the function.
|
|
||||||
* @param body Body of the function.
|
|
||||||
*/
|
|
||||||
template <typename T>
|
|
||||||
function_definition_node(struct location location, T&& name, body&& body)
|
|
||||||
: function_declaration_node(location, std::forward<T>(name)), m_body(std::move(body)) {}
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's declaration type.
|
|
||||||
*
|
|
||||||
* @return declaration_node_t::FunctionDefinition.
|
|
||||||
*/
|
|
||||||
declaration_node_t declaration_type() const override { return declaration_node_t::FuncDef; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns function's body.
|
|
||||||
*
|
|
||||||
* @return Body of the function.
|
|
||||||
*/
|
|
||||||
const body& body() const { return m_body; }
|
|
||||||
public:
|
|
||||||
void accept(visitor& visitor) const override;
|
|
||||||
|
|
||||||
std::ostream& print(std::ostream& os) const override;
|
|
||||||
protected:
|
|
||||||
bool equal(const node& rhs) const override;
|
|
||||||
private:
|
|
||||||
struct body m_body;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ast
|
|
||||||
} // namespace furc
|
|
||||||
|
|
||||||
#endif // FURC_AST_DECLARATION_HPP
|
|
||||||
@@ -1,356 +0,0 @@
|
|||||||
#ifndef FURC_AST_EXPRESSION_HPP
|
|
||||||
#define FURC_AST_EXPRESSION_HPP
|
|
||||||
|
|
||||||
#include "furc/ast/node.hpp"
|
|
||||||
#include "furc/ast/statement.hpp"
|
|
||||||
|
|
||||||
namespace furc {
|
|
||||||
namespace ast {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Expression node type.
|
|
||||||
*/
|
|
||||||
enum class expression_node_t {
|
|
||||||
Literal, /**< Literal */
|
|
||||||
VarRead, /**< Variable read expression */
|
|
||||||
Unaryop, /**< Unary operation expression */
|
|
||||||
Binop, /**< Binary operation expression */
|
|
||||||
VarAssign, /**< Variable assignment expression */
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Expression AST node.
|
|
||||||
*/
|
|
||||||
class expression_node : public statement_node, public abstract_node {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Construct a new expression AST node.
|
|
||||||
*
|
|
||||||
* @param location Node location.
|
|
||||||
*/
|
|
||||||
expression_node(struct location location)
|
|
||||||
: abstract_node(location) {}
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's category.
|
|
||||||
*
|
|
||||||
* @return node_t::Expression.
|
|
||||||
*/
|
|
||||||
node_t category() const override { return node_t::Expression; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's statement type.
|
|
||||||
*
|
|
||||||
* @return statement_node_t::Expression.
|
|
||||||
*/
|
|
||||||
statement_node_t statement_type() const override { return statement_node_t::Expression; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's expression type.
|
|
||||||
*
|
|
||||||
* @return The expression type.
|
|
||||||
*/
|
|
||||||
virtual expression_node_t expression_type() const = 0;
|
|
||||||
protected:
|
|
||||||
bool equal(const node& rhs) const override;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Var read expression AST node.
|
|
||||||
*/
|
|
||||||
class var_read_expression_node final : public expression_node {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Construct a new var read expression node object from a name handle.
|
|
||||||
*
|
|
||||||
* @param location Node location.
|
|
||||||
* @param name Handle to the name.
|
|
||||||
*/
|
|
||||||
template <typename T>
|
|
||||||
var_read_expression_node(struct location location, T&& name)
|
|
||||||
: expression_node(location), m_name(std::forward<T>(name)) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns the variable's name.
|
|
||||||
*
|
|
||||||
* @return Name of the variable.
|
|
||||||
*/
|
|
||||||
const std::string& get_name() const { return m_name; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns the variable's name.
|
|
||||||
*
|
|
||||||
* @return Name of the variable.
|
|
||||||
*/
|
|
||||||
std::string&& move_name() { return std::move(m_name); }
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's expression type.
|
|
||||||
*
|
|
||||||
* @return expression_node_t::VarRead.
|
|
||||||
*/
|
|
||||||
expression_node_t expression_type() const override { return expression_node_t::VarRead; }
|
|
||||||
public:
|
|
||||||
void accept(visitor& visitor) const override;
|
|
||||||
|
|
||||||
std::ostream& print(std::ostream& os) const override;
|
|
||||||
protected:
|
|
||||||
bool equal(const node& rhs) const override;
|
|
||||||
private:
|
|
||||||
std::string m_name;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Unary operation node type.
|
|
||||||
*/
|
|
||||||
enum class unaryop_expression_node_t {
|
|
||||||
Positive, /**< Positive (unary plus) */
|
|
||||||
Negative, /**< Negative (unary minus) */
|
|
||||||
PrefixIncrement, /**< Prefix increment */
|
|
||||||
PostfixIncrement, /**< Postfix increment */
|
|
||||||
PrefixDecrement, /**< Prefix decrement */
|
|
||||||
PostfixDecrement, /**< Postfix decrement */
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Unary operation expression AST node.
|
|
||||||
*/
|
|
||||||
class unary_op_expression_node final : public expression_node {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Construct a new unaryop expression node object from type and expression node handle.
|
|
||||||
*
|
|
||||||
* @param location Node location.
|
|
||||||
* @param type Operation type.
|
|
||||||
* @param node Handle to the inner expression node.
|
|
||||||
*/
|
|
||||||
unary_op_expression_node(struct location location, unaryop_expression_node_t type, expression_node_p&& node)
|
|
||||||
: expression_node(location), m_type(type), m_node(std::move(node)) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Sets this node's inner expression.
|
|
||||||
*
|
|
||||||
* @param node New node handle.
|
|
||||||
*/
|
|
||||||
void set_node(expression_node_p&& node) { m_node = std::move(node); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns the type of this node's operation.
|
|
||||||
*
|
|
||||||
* @return The operation type.
|
|
||||||
*/
|
|
||||||
unaryop_expression_node_t type() const { return m_type; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's inner expression.
|
|
||||||
*
|
|
||||||
* @return The inner expression.
|
|
||||||
*/
|
|
||||||
const expression_node_p& get_node() const { return m_node; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's inner expression.
|
|
||||||
*
|
|
||||||
* @return The inner expression.
|
|
||||||
*/
|
|
||||||
expression_node_p& get_node() { return m_node; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Moves this node's inner expression.
|
|
||||||
*
|
|
||||||
* @return The moved inner expression.
|
|
||||||
*/
|
|
||||||
expression_node_p&& move_node() { return std::move(m_node); }
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's expression type.
|
|
||||||
*
|
|
||||||
* @return expression_node_t::Unaryop.
|
|
||||||
*/
|
|
||||||
expression_node_t expression_type() const override { return expression_node_t::Unaryop; }
|
|
||||||
public:
|
|
||||||
void accept(visitor& visitor) const override;
|
|
||||||
|
|
||||||
std::ostream& print(std::ostream& os) const override;
|
|
||||||
protected:
|
|
||||||
bool equal(const node& rhs) const override;
|
|
||||||
private:
|
|
||||||
unaryop_expression_node_t m_type;
|
|
||||||
expression_node_p m_node; /**< The inner expression. */
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Binary operation expression node type.
|
|
||||||
*/
|
|
||||||
enum class binop_expression_node_t {
|
|
||||||
None = 0, /**< None */
|
|
||||||
Add, /**< Addition */
|
|
||||||
Sub, /**< Subtraction */
|
|
||||||
Mul, /**< Multiplication */
|
|
||||||
Div, /**< Division */
|
|
||||||
Mod, /**< Modulo */
|
|
||||||
|
|
||||||
Equal, /**< Equality */
|
|
||||||
NotEqual, /**< Inequality */
|
|
||||||
LessThan, /**< Less */
|
|
||||||
GreaterThan, /**< Greater */
|
|
||||||
LessEqual, /**< Less or equal */
|
|
||||||
GreaterEqual, /**< Greater or equal */
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Binary operation expression AST node.
|
|
||||||
*/
|
|
||||||
class binary_op_expression_node final : public expression_node {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Construct a new binary operation expression AST node.
|
|
||||||
*
|
|
||||||
* @param location Node location.
|
|
||||||
* @param type Binary operation type.
|
|
||||||
* @param lhs Left-hand-side expression.
|
|
||||||
* @param rhs Right-hand-side expression.
|
|
||||||
*/
|
|
||||||
binary_op_expression_node(struct location location,
|
|
||||||
binop_expression_node_t type,
|
|
||||||
expression_node_p&& lhs,
|
|
||||||
expression_node_p&& rhs)
|
|
||||||
: expression_node(location), m_type(type), m_lhs(std::move(lhs)), m_rhs(std::move(rhs)) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's binary operation type.
|
|
||||||
*
|
|
||||||
* @return The binary operation type.
|
|
||||||
*/
|
|
||||||
binop_expression_node_t type() const { return m_type; };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's left-hand-side expression.
|
|
||||||
*
|
|
||||||
* @return The left-hand-side expression.
|
|
||||||
*/
|
|
||||||
const expression_node_p& lhs() const { return m_lhs; };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's left-hand-side expression.
|
|
||||||
*
|
|
||||||
* @return The left-hand-side expression.
|
|
||||||
*/
|
|
||||||
expression_node_p& lhs() { return m_lhs; };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Moves this node's left-hand-side expression.
|
|
||||||
*
|
|
||||||
* @return The moved left-hand-side expression.
|
|
||||||
*/
|
|
||||||
expression_node_p&& move_lhs() { return std::move(m_lhs); };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's right-hand-side expression.
|
|
||||||
*
|
|
||||||
* @return The right-hand-side expression.
|
|
||||||
*/
|
|
||||||
const expression_node_p& rhs() const { return m_rhs; };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's right-hand-side expression.
|
|
||||||
*
|
|
||||||
* @return The right-hand-side expression.
|
|
||||||
*/
|
|
||||||
expression_node_p& rhs() { return m_rhs; };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Moves this node's right-hand-side expression.
|
|
||||||
*
|
|
||||||
* @return The moved right-hand-side expression.
|
|
||||||
*/
|
|
||||||
expression_node_p&& move_rhs() { return std::move(m_rhs); };
|
|
||||||
public:
|
|
||||||
expression_node_t expression_type() const override { return expression_node_t::Binop; }
|
|
||||||
public:
|
|
||||||
void accept(visitor& visitor) const override;
|
|
||||||
|
|
||||||
std::ostream& print(std::ostream& os) const override;
|
|
||||||
protected:
|
|
||||||
bool equal(const node& rhs) const override;
|
|
||||||
private:
|
|
||||||
binop_expression_node_t m_type;
|
|
||||||
expression_node_p m_lhs;
|
|
||||||
expression_node_p m_rhs;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Variable assignment expression AST node.
|
|
||||||
*/
|
|
||||||
class var_assign_expression_node final : public expression_node {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Construct a new variable assignment expression AST node.
|
|
||||||
*
|
|
||||||
* @param location Node location.
|
|
||||||
* @param lhs Left-hand-side expression handle.
|
|
||||||
* @param rhs Right-hand-side expression handle.
|
|
||||||
*/
|
|
||||||
var_assign_expression_node(struct location location, expression_node_p&& lhs, expression_node_p&& rhs)
|
|
||||||
: expression_node(location),
|
|
||||||
m_compound(binop_expression_node_t::None),
|
|
||||||
m_lhs(std::move(lhs)),
|
|
||||||
m_rhs(std::move(rhs)) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Construct a new compound variable assignment expression AST node.
|
|
||||||
*
|
|
||||||
* @param location Node location.
|
|
||||||
* @param compound Compound operation type.
|
|
||||||
* @param lhs Left-hand-side expression handle.
|
|
||||||
* @param rhs Right-hand-side expression handle.
|
|
||||||
*/
|
|
||||||
var_assign_expression_node(struct location location,
|
|
||||||
binop_expression_node_t compound,
|
|
||||||
expression_node_p&& lhs,
|
|
||||||
expression_node_p&& rhs)
|
|
||||||
: expression_node(location), m_compound(compound), m_lhs(std::move(lhs)), m_rhs(std::move(rhs)) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's compound operation type.
|
|
||||||
*
|
|
||||||
* @return The compound operation type.
|
|
||||||
*/
|
|
||||||
binop_expression_node_t compound() const { return m_compound; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's left-hand-side expression.
|
|
||||||
*
|
|
||||||
* @return The left-hand-side expression.
|
|
||||||
*/
|
|
||||||
const expression_node_p& lhs() const { return m_lhs; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's right-hand-side expression.
|
|
||||||
*
|
|
||||||
* @return The right-hand-side expression.
|
|
||||||
*/
|
|
||||||
const expression_node_p& rhs() const { return m_rhs; }
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's expression type.
|
|
||||||
*
|
|
||||||
* @return expression_node_t::VarAssign.
|
|
||||||
*/
|
|
||||||
expression_node_t expression_type() const override { return expression_node_t::VarAssign; }
|
|
||||||
public:
|
|
||||||
void accept(visitor& visitor) const override;
|
|
||||||
|
|
||||||
std::ostream& print(std::ostream& os) const override;
|
|
||||||
protected:
|
|
||||||
bool equal(const node& rhs) const override;
|
|
||||||
private:
|
|
||||||
binop_expression_node_t m_compound;
|
|
||||||
expression_node_p m_lhs;
|
|
||||||
expression_node_p m_rhs;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ast
|
|
||||||
} // namespace furc
|
|
||||||
|
|
||||||
#endif // FURC_AST_EXPRESSION_HPP
|
|
||||||
@@ -1,241 +0,0 @@
|
|||||||
#ifndef FURC_AST_FWD_HPP
|
|
||||||
#define FURC_AST_FWD_HPP
|
|
||||||
|
|
||||||
#include "furc/diag.hpp"
|
|
||||||
#include "furlang/result.hpp"
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace furc {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Abstract Syntax Tree definitions.
|
|
||||||
*/
|
|
||||||
namespace ast {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief AST error.
|
|
||||||
*/
|
|
||||||
struct error {
|
|
||||||
location location; /**< Location of the error. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Compares two AST errors for equality.
|
|
||||||
*
|
|
||||||
* @param other Error to compare against.
|
|
||||||
* @return true if the errors are equal.
|
|
||||||
*/
|
|
||||||
bool operator==(const error& other) const { return location == other.location; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Compares two AST errors for inequality.
|
|
||||||
*
|
|
||||||
* @param other Error to compare against.
|
|
||||||
* @return true if the errors are not equal.
|
|
||||||
*/
|
|
||||||
bool operator!=(const error& other) const { return !this->operator==(other); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Prints an AST error to output stream.
|
|
||||||
*
|
|
||||||
* @param os Output stream.
|
|
||||||
* @param error AST error to print.
|
|
||||||
* @return The output stream.
|
|
||||||
*/
|
|
||||||
friend std::ostream& operator<<(std::ostream& os, const error& error);
|
|
||||||
};
|
|
||||||
|
|
||||||
class node;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Alias for a shared pointer to node.
|
|
||||||
*
|
|
||||||
* @tparam T AST node type.
|
|
||||||
*/
|
|
||||||
template <typename T = node>
|
|
||||||
using node_p = std::shared_ptr<T>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Alias for node result.
|
|
||||||
*
|
|
||||||
* @tparam T AST node type.
|
|
||||||
*/
|
|
||||||
template <typename T = node>
|
|
||||||
using node_r = furlang::result<node_p<T>, error>;
|
|
||||||
|
|
||||||
class expression_node;
|
|
||||||
|
|
||||||
using expression_node_p = node_p<expression_node>; /**< Alias for a shared pointer to expression_node. */
|
|
||||||
|
|
||||||
using expression_node_r = node_r<expression_node>; /**< Alias for expression_node result */
|
|
||||||
|
|
||||||
class declaration_node;
|
|
||||||
|
|
||||||
using declaration_node_p = node_p<declaration_node>; /**< Alias for a shared pointer to declaration_node. */
|
|
||||||
|
|
||||||
using declaration_node_r = node_r<declaration_node>; /**< Alias for declaration_node result */
|
|
||||||
|
|
||||||
class statement_node;
|
|
||||||
|
|
||||||
using statement_node_p = node_p<statement_node>; /**< Alias for a shared pointer to statement_node. */
|
|
||||||
|
|
||||||
using statement_node_r = node_r<statement_node>; /**< Alias for statement_node result */
|
|
||||||
|
|
||||||
class program_node;
|
|
||||||
|
|
||||||
using program_node_p = node_p<program_node>; /**< Alias for a shared pointer to program_node. */
|
|
||||||
|
|
||||||
using program_node_r = node_r<program_node>; /**< Alias for program_node result */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Literal node type.
|
|
||||||
*/
|
|
||||||
enum class literal_node_t {
|
|
||||||
String, /**< String literal. */
|
|
||||||
Integer, /**< Integer literal. */
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename, literal_node_t>
|
|
||||||
class literal_node;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief String literal AST node.
|
|
||||||
*/
|
|
||||||
using string_literal_node = literal_node<std::string, literal_node_t::String>;
|
|
||||||
|
|
||||||
using string_literal_node_p = node_p<string_literal_node>; /**< Alias for a shared pointer to string_literal_node */
|
|
||||||
|
|
||||||
using string_literal_node_r = node_r<string_literal_node>; /**< Alias for string_literal_node result */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Integer literal AST node.
|
|
||||||
*/
|
|
||||||
using integer_literal_node = literal_node<std::uint64_t, literal_node_t::Integer>;
|
|
||||||
|
|
||||||
using integer_literal_node_p = node_p<integer_literal_node>; /**< Alias for a shared pointer to integer_literal_node */
|
|
||||||
|
|
||||||
using integer_literal_node_r = node_r<integer_literal_node>; /**< Alias for integer_literal_node result */
|
|
||||||
|
|
||||||
class var_read_expression_node;
|
|
||||||
|
|
||||||
using var_read_expression_node_p =
|
|
||||||
node_p<var_read_expression_node>; /**< Alias for a shared pointer to var_read_expression_node. */
|
|
||||||
|
|
||||||
using var_read_expression_node_r = node_r<var_read_expression_node>; /**< Alias for var_read_expression_node result */
|
|
||||||
|
|
||||||
class unary_op_expression_node;
|
|
||||||
|
|
||||||
using unary_op_expression_node_p =
|
|
||||||
node_p<unary_op_expression_node>; /**< Alias for a shared pointer to unaryop_expression_node. */
|
|
||||||
|
|
||||||
using unary_op_expression_node_r = node_r<unary_op_expression_node>; /**< Alias for unaryop_expression_node result */
|
|
||||||
|
|
||||||
class binary_op_expression_node;
|
|
||||||
|
|
||||||
using binary_op_expression_node_p =
|
|
||||||
node_p<binary_op_expression_node>; /**< Alias for a shared pointer to binop_expression_node. */
|
|
||||||
|
|
||||||
using binary_op_expression_node_r = node_r<binary_op_expression_node>; /**< Alias for binop_expression_node result */
|
|
||||||
|
|
||||||
class var_assign_expression_node;
|
|
||||||
|
|
||||||
using var_assign_expression_node_p =
|
|
||||||
node_p<var_assign_expression_node>; /**< Alias for a shared pointer to var_assign_expression_node. */
|
|
||||||
|
|
||||||
using var_assign_expression_node_r =
|
|
||||||
node_r<var_assign_expression_node>; /**< Alias for var_assign_expression_node result */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief List of statements.
|
|
||||||
*/
|
|
||||||
struct body {
|
|
||||||
/**
|
|
||||||
* @brief Location of the opening curly.
|
|
||||||
*/
|
|
||||||
location begin;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Location of the closing curly.
|
|
||||||
*/
|
|
||||||
location end;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief List of statements.
|
|
||||||
*/
|
|
||||||
std::vector<statement_node_r> statements;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Compares two bodies for equality.
|
|
||||||
*
|
|
||||||
* @param rhs Body to compare against.
|
|
||||||
* @return true if the bodies are equal.
|
|
||||||
*/
|
|
||||||
bool operator==(const body& rhs) const {
|
|
||||||
return begin == rhs.begin && end == rhs.end && statements == rhs.statements;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Compares two bodies for inequality.
|
|
||||||
*
|
|
||||||
* @param rhs Body to compare against.
|
|
||||||
* @return true if the bodies are not equal.
|
|
||||||
*/
|
|
||||||
bool operator!=(const body& rhs) const { return !this->operator==(rhs); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Prints a body to an output stream.
|
|
||||||
*
|
|
||||||
* @param os Output stream.
|
|
||||||
* @param body Body to print.
|
|
||||||
* @return The output stream.
|
|
||||||
*/
|
|
||||||
friend std::ostream& operator<<(std::ostream& os, const body& body);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Alias for body result.
|
|
||||||
* @see body
|
|
||||||
*/
|
|
||||||
using body_r = furlang::result<body, error>;
|
|
||||||
|
|
||||||
class function_declaration_node;
|
|
||||||
|
|
||||||
using function_declaration_node_p =
|
|
||||||
node_p<function_declaration_node>; /**< Alias for a shared pointer to function_declaration_node. */
|
|
||||||
|
|
||||||
using function_declaration_node_r =
|
|
||||||
node_r<function_declaration_node>; /**< Alias for function_declaration_node result */
|
|
||||||
|
|
||||||
class function_definition_node;
|
|
||||||
|
|
||||||
using function_definition_node_p =
|
|
||||||
node_p<function_definition_node>; /**< Alias for a shared pointer to function_definition_node. */
|
|
||||||
|
|
||||||
using function_definition_node_r = node_r<function_definition_node>; /**< Alias for function_definition_node result */
|
|
||||||
|
|
||||||
class return_statement_node;
|
|
||||||
|
|
||||||
using return_statement_node_p =
|
|
||||||
node_p<return_statement_node>; /**< Alias for a shared pointer to return_statement_node. */
|
|
||||||
|
|
||||||
using return_statement_node_r = node_r<return_statement_node>; /**< Alias for return_statement_node result */
|
|
||||||
|
|
||||||
class if_statement_node;
|
|
||||||
|
|
||||||
using if_statement_node_p = node_p<if_statement_node>; /**< Alias for a shared pointer to if_statement_node. */
|
|
||||||
|
|
||||||
using if_statement_node_r = node_r<if_statement_node>; /**< Alias for if_statement_node result */
|
|
||||||
|
|
||||||
class compound_statement_node;
|
|
||||||
|
|
||||||
using compound_statement_node_p =
|
|
||||||
node_p<compound_statement_node>; /**< Alias for a shared pointer to compound_statement_node. */
|
|
||||||
|
|
||||||
using compound_statement_node_r = node_r<compound_statement_node>; /**< Alias for compound_statement_node result */
|
|
||||||
|
|
||||||
} // namespace ast
|
|
||||||
} // namespace furc
|
|
||||||
|
|
||||||
#endif // FURC_AST_FWD_HPP
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
// NOLINTBEGIN(portability-template-virtual-member-function)
|
|
||||||
|
|
||||||
#ifndef FURC_AST_LITERAL_HPP
|
|
||||||
#define FURC_AST_LITERAL_HPP
|
|
||||||
|
|
||||||
#include "furc/ast/expression.hpp"
|
|
||||||
#include "furc/ast/node.hpp"
|
|
||||||
|
|
||||||
namespace furc {
|
|
||||||
namespace ast {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Literal AST node.
|
|
||||||
*/
|
|
||||||
template <typename ValueType, literal_node_t LiteralType>
|
|
||||||
class literal_node : public expression_node {
|
|
||||||
public:
|
|
||||||
using value_type = std::remove_reference_t<ValueType>; /**< Value type. */
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Construct a new literal AST node.
|
|
||||||
*
|
|
||||||
* @param location Node location.
|
|
||||||
*/
|
|
||||||
template <typename = std::enable_if_t<std::is_default_constructible_v<ValueType>>>
|
|
||||||
literal_node(struct location location)
|
|
||||||
: expression_node(location) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Construct a new literal AST node.
|
|
||||||
*
|
|
||||||
* @param location Node location.
|
|
||||||
* @param value Node value to copy.
|
|
||||||
*/
|
|
||||||
literal_node(struct location location, const value_type& value)
|
|
||||||
: expression_node(location), p_value(value) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Construct a new literal AST node.
|
|
||||||
*
|
|
||||||
* @param location Node location.
|
|
||||||
* @param value Node value to move.
|
|
||||||
*/
|
|
||||||
literal_node(struct location location, value_type&& value)
|
|
||||||
: expression_node(location), p_value(std::move(value)) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Construct a new literal AST node.
|
|
||||||
*
|
|
||||||
* @param location Node location.
|
|
||||||
* @param args Arguments to call value constructor with.
|
|
||||||
*/
|
|
||||||
template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<ValueType, Args...>>>
|
|
||||||
literal_node(struct location location, Args&&... args)
|
|
||||||
: expression_node(location), p_value(std::forward<Args>(args)...) {}
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's category.
|
|
||||||
*
|
|
||||||
* @return node_t::Literal.
|
|
||||||
*/
|
|
||||||
node_t category() const override { return node_t::Literal; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's expression type.
|
|
||||||
*
|
|
||||||
* @return expression_node_t::Literal.
|
|
||||||
*/
|
|
||||||
expression_node_t expression_type() const override { return expression_node_t::Literal; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's literal type.
|
|
||||||
*
|
|
||||||
* @return The literal type.
|
|
||||||
*/
|
|
||||||
literal_node_t literal_type() const { return LiteralType; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's value.
|
|
||||||
*
|
|
||||||
* @return A string view result.
|
|
||||||
*/
|
|
||||||
const value_type& value() const { return p_value; }
|
|
||||||
public:
|
|
||||||
void accept(visitor& visitor) const override { visitor.visit(*this); }
|
|
||||||
|
|
||||||
std::ostream& print(std::ostream& os) const override { return os << p_value; }
|
|
||||||
protected:
|
|
||||||
bool equal(const node& rhsNode) const override {
|
|
||||||
const auto& rhs = dynamic_cast<const literal_node&>(rhsNode);
|
|
||||||
return literal_type() == rhs.literal_type() && p_value == rhs.p_value;
|
|
||||||
}
|
|
||||||
protected:
|
|
||||||
value_type p_value; /**< Node value. */
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ast
|
|
||||||
} // namespace furc
|
|
||||||
|
|
||||||
#endif // FURC_AST_LITERAL_HPP
|
|
||||||
|
|
||||||
// NOLINTEND(portability-template-virtual-member-function)
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
#ifndef FURC_AST_NODE_HPP
|
|
||||||
#define FURC_AST_NODE_HPP
|
|
||||||
|
|
||||||
#include "furc/ast/fwd.hpp"
|
|
||||||
#include "furc/ast/visitor.hpp"
|
|
||||||
|
|
||||||
namespace furc {
|
|
||||||
namespace ast {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Node category.
|
|
||||||
*/
|
|
||||||
enum class node_t {
|
|
||||||
Literal, /**< Literal. */
|
|
||||||
Expression, /**< Expression. */
|
|
||||||
Statement, /**< Statement. */
|
|
||||||
Declaration, /**< Declaration. */
|
|
||||||
Program, /**< Program. */
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Prints a node type (category) to an output stream.
|
|
||||||
*
|
|
||||||
* @param os Output stream.
|
|
||||||
* @param type Type to print.
|
|
||||||
* @return The output stream.
|
|
||||||
*/
|
|
||||||
static inline std::ostream& operator<<(std::ostream& os, node_t type) {
|
|
||||||
switch (type) {
|
|
||||||
case node_t::Literal: return os << "literal";
|
|
||||||
case node_t::Expression: return os << "expression";
|
|
||||||
case node_t::Statement: return os << "statement";
|
|
||||||
case node_t::Declaration: return os << "declaration";
|
|
||||||
case node_t::Program: return os << "program";
|
|
||||||
}
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief AST node interface.
|
|
||||||
*/
|
|
||||||
class node {
|
|
||||||
public:
|
|
||||||
node() = default;
|
|
||||||
virtual ~node() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*
|
|
||||||
* Constructs a node by transferring the state of another node.
|
|
||||||
*
|
|
||||||
* @param other Node to move from.
|
|
||||||
*/
|
|
||||||
node(node&& other) = default;
|
|
||||||
node(const node&) = delete;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*
|
|
||||||
* Constructs a node by transferring the state of another node.
|
|
||||||
*
|
|
||||||
* @param other Node to move from.
|
|
||||||
*/
|
|
||||||
node& operator=(node&& other) = default;
|
|
||||||
node& operator=(const node&) = delete;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns the category of this AST node.
|
|
||||||
* @see node_t
|
|
||||||
*
|
|
||||||
* @return The node category.
|
|
||||||
*/
|
|
||||||
virtual node_t category() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns the location of this AST node.
|
|
||||||
* @see locaiton
|
|
||||||
*
|
|
||||||
* @return The location.
|
|
||||||
*/
|
|
||||||
virtual location location() const = 0;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Compares two nodes for equality.
|
|
||||||
*
|
|
||||||
* Nodes are equal if they have the same category and
|
|
||||||
* their derived-class-specific contents are equal.
|
|
||||||
*
|
|
||||||
* @param rhs Node to compare against.
|
|
||||||
* @return true if the nodes are equal.
|
|
||||||
*/
|
|
||||||
bool operator==(const node& rhs) const { return category() == rhs.category() && equal(rhs); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Compares two nodes for inequality.
|
|
||||||
*
|
|
||||||
* @param rhs Node to compare against.
|
|
||||||
* @return true if the nodes are not equal.
|
|
||||||
*/
|
|
||||||
bool operator!=(const node& rhs) const { return !this->operator==(rhs); }
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Accepts a visitor.
|
|
||||||
*
|
|
||||||
* Dispatches to the visitor overload corresponding to the concrete node type.
|
|
||||||
*
|
|
||||||
* @param visitor Visitor instance.
|
|
||||||
*/
|
|
||||||
virtual void accept(visitor& visitor) const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Prints a node to an output stream.
|
|
||||||
*
|
|
||||||
* @param os Output stream.
|
|
||||||
* @return The output stream.
|
|
||||||
*/
|
|
||||||
virtual std::ostream& print(std::ostream& os) const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Prints a node to an output stream.
|
|
||||||
*
|
|
||||||
* Equivalent to calling node.print(os).
|
|
||||||
*
|
|
||||||
* @param os Output stream.
|
|
||||||
* @param node Node to print.
|
|
||||||
* @return The output stream.
|
|
||||||
*/
|
|
||||||
friend std::ostream& operator<<(std::ostream& os, const node& node) {
|
|
||||||
return node.print(os << node.location() << ": ");
|
|
||||||
}
|
|
||||||
protected:
|
|
||||||
/**
|
|
||||||
* @brief Compares two nodes for equality.
|
|
||||||
*
|
|
||||||
* @param rhs Node to compare against.
|
|
||||||
* @return true if nodes are equal.
|
|
||||||
*/
|
|
||||||
virtual bool equal(const node& rhs) const = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief An abstract AST node.
|
|
||||||
* @see node
|
|
||||||
*
|
|
||||||
* Implements location().
|
|
||||||
*/
|
|
||||||
class abstract_node : public virtual node {
|
|
||||||
public:
|
|
||||||
abstract_node(struct location location)
|
|
||||||
: p_location(location) {}
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns the location of this AST node.
|
|
||||||
* @see locaiton
|
|
||||||
*
|
|
||||||
* @return The location.
|
|
||||||
*/
|
|
||||||
struct location location() const override { return p_location; }
|
|
||||||
protected:
|
|
||||||
struct location p_location; /**< Node location. */
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ast
|
|
||||||
} // namespace furc
|
|
||||||
|
|
||||||
#endif // FURC_AST_NODE_HPP
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
#ifndef FURC_AST_PROGRAM_HPP
|
|
||||||
#define FURC_AST_PROGRAM_HPP
|
|
||||||
|
|
||||||
#include "furc/ast/node.hpp"
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace furc {
|
|
||||||
namespace ast {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Program AST node.
|
|
||||||
*/
|
|
||||||
class program_node final : public abstract_node {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Construct a new program AST node.
|
|
||||||
*
|
|
||||||
* @param location Node location.
|
|
||||||
*/
|
|
||||||
program_node(struct location location)
|
|
||||||
: abstract_node(location) {}
|
|
||||||
|
|
||||||
node_t category() const override { return node_t::Program; }
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Adds a declaration to this program.
|
|
||||||
*
|
|
||||||
* @param declaration Declaration to add.
|
|
||||||
*/
|
|
||||||
void push(declaration_node_p&& declaration) { m_declarations.push_back(std::move(declaration)); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns a list of declarations of this program.
|
|
||||||
*
|
|
||||||
* @return The list of this program's declarations.
|
|
||||||
*/
|
|
||||||
const std::vector<declaration_node_p>& declarations() const { return m_declarations; }
|
|
||||||
public:
|
|
||||||
void accept(visitor& visitor) const override;
|
|
||||||
|
|
||||||
std::ostream& print(std::ostream& os) const override;
|
|
||||||
protected:
|
|
||||||
bool equal(const node& rhs) const override;
|
|
||||||
private:
|
|
||||||
std::vector<declaration_node_p> m_declarations;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ast
|
|
||||||
} // namespace furc
|
|
||||||
|
|
||||||
#endif // FURC_AST_PROGRAM_HPP
|
|
||||||
@@ -1,197 +0,0 @@
|
|||||||
#ifndef FURC_AST_STATEMENT_HPP
|
|
||||||
#define FURC_AST_STATEMENT_HPP
|
|
||||||
|
|
||||||
#include "furc/ast/node.hpp"
|
|
||||||
|
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
namespace furc {
|
|
||||||
namespace ast {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Statement node type.
|
|
||||||
*/
|
|
||||||
enum class statement_node_t {
|
|
||||||
Expression, /**< Expression */
|
|
||||||
Declaration, /**< Declaration */
|
|
||||||
Return, /**< Return statement */
|
|
||||||
If, /**< If statement */
|
|
||||||
Compound, /**< Compound statement */
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Statement AST node.
|
|
||||||
*/
|
|
||||||
class statement_node : public virtual node {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's category.
|
|
||||||
*
|
|
||||||
* @return node_t::Statement.
|
|
||||||
*/
|
|
||||||
node_t category() const override { return node_t::Statement; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's statement type.
|
|
||||||
*
|
|
||||||
* @return The statement type.
|
|
||||||
*/
|
|
||||||
virtual statement_node_t statement_type() const = 0;
|
|
||||||
protected:
|
|
||||||
bool equal(const node& rhs) const override;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Return statement AST node.
|
|
||||||
*/
|
|
||||||
class return_statement_node final : public statement_node, public abstract_node {
|
|
||||||
public:
|
|
||||||
using value_type = std::optional<expression_node_p>; /**< Value type. */
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Construct a new return statement AST node.
|
|
||||||
*/
|
|
||||||
return_statement_node(struct location location)
|
|
||||||
: abstract_node(location) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Construct a new return statement AST node.
|
|
||||||
*
|
|
||||||
* @param location Node location.
|
|
||||||
* @param value Return value handle.
|
|
||||||
*/
|
|
||||||
return_statement_node(struct location location, expression_node_p&& value)
|
|
||||||
: abstract_node(location), m_value(std::move(value)) {}
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's return value handle.
|
|
||||||
*
|
|
||||||
* @return The return value handle.
|
|
||||||
*/
|
|
||||||
value_type value() const { return m_value; }
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's statement type.
|
|
||||||
*
|
|
||||||
* @return statement_node_t::Return.
|
|
||||||
*/
|
|
||||||
statement_node_t statement_type() const override { return statement_node_t::Return; }
|
|
||||||
public:
|
|
||||||
void accept(visitor& visitor) const override;
|
|
||||||
|
|
||||||
std::ostream& print(std::ostream& os) const override;
|
|
||||||
protected:
|
|
||||||
bool equal(const node& rhs) const override;
|
|
||||||
private:
|
|
||||||
value_type m_value; /**< Return value handle. */
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief If statement AST node.
|
|
||||||
*/
|
|
||||||
class if_statement_node final : public statement_node, public abstract_node {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Construct a new if statement AST node.
|
|
||||||
*
|
|
||||||
* @param location Node location.
|
|
||||||
* @param cond Condition expression handle.
|
|
||||||
* @param then Then statement handle.
|
|
||||||
*/
|
|
||||||
if_statement_node(struct location location, expression_node_p&& cond, statement_node_p&& then)
|
|
||||||
: abstract_node(location), m_cond(std::move(cond)), m_then(std::move(then)) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Construct a new if statement AST node.
|
|
||||||
*
|
|
||||||
* @param location Node location.
|
|
||||||
* @param cond Condition expression handle.
|
|
||||||
* @param then Then statement handle.
|
|
||||||
* @param elze Else statement handle.
|
|
||||||
*/
|
|
||||||
if_statement_node(struct location location,
|
|
||||||
expression_node_p&& cond,
|
|
||||||
statement_node_p&& then,
|
|
||||||
statement_node_p&& elze)
|
|
||||||
: abstract_node(location), m_cond(std::move(cond)), m_then(std::move(then)), m_else(std::move(elze)) {}
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's condition expression handle.
|
|
||||||
*
|
|
||||||
* @return The condition expression handle.
|
|
||||||
*/
|
|
||||||
expression_node_p cond() const { return m_cond; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's then statement handle.
|
|
||||||
*
|
|
||||||
* @return The then statement handle.
|
|
||||||
*/
|
|
||||||
const statement_node_p& then() const { return m_then; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's else statement handle.
|
|
||||||
*
|
|
||||||
* @return The else statement handle.
|
|
||||||
*/
|
|
||||||
const std::optional<statement_node_p>& elze() const { return m_else; }
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's statement type.
|
|
||||||
*
|
|
||||||
* @return statement_node_t::If.
|
|
||||||
*/
|
|
||||||
statement_node_t statement_type() const override { return statement_node_t::If; }
|
|
||||||
public:
|
|
||||||
void accept(visitor& visitor) const override;
|
|
||||||
|
|
||||||
std::ostream& print(std::ostream& os) const override;
|
|
||||||
protected:
|
|
||||||
bool equal(const node& rhs) const override;
|
|
||||||
private:
|
|
||||||
expression_node_p m_cond; /**< The condition expression handle */
|
|
||||||
statement_node_p m_then; /**< The then statement handle */
|
|
||||||
std::optional<statement_node_p> m_else; /**< The else statement handle */
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Compound statement AST node.
|
|
||||||
*/
|
|
||||||
class compound_statement_node final : public statement_node, public abstract_node {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Construct a new compound statement AST node.
|
|
||||||
*
|
|
||||||
* @param location Node location.
|
|
||||||
* @param body Body handle.
|
|
||||||
*/
|
|
||||||
compound_statement_node(struct location location, body&& body)
|
|
||||||
: abstract_node(location), m_body(std::move(body)) {}
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's body handle.
|
|
||||||
*
|
|
||||||
* @return The body handle.
|
|
||||||
*/
|
|
||||||
const body& body() const { return m_body; }
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns this node's statement type.
|
|
||||||
*
|
|
||||||
* @return statement_node_t::Compound.
|
|
||||||
*/
|
|
||||||
statement_node_t statement_type() const override { return statement_node_t::Compound; }
|
|
||||||
public:
|
|
||||||
void accept(visitor& visitor) const override;
|
|
||||||
|
|
||||||
std::ostream& print(std::ostream& os) const override;
|
|
||||||
protected:
|
|
||||||
bool equal(const node& rhs) const override;
|
|
||||||
private:
|
|
||||||
struct body m_body; /**< The body handle. */
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ast
|
|
||||||
} // namespace furc
|
|
||||||
|
|
||||||
#endif // FURC_AST_STATEMENT_HPP
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
#ifndef FURC_AST_VISITOR_HPP
|
|
||||||
#define FURC_AST_VISITOR_HPP
|
|
||||||
|
|
||||||
#include "furc/ast/fwd.hpp"
|
|
||||||
|
|
||||||
namespace furc {
|
|
||||||
namespace ast {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Visitor pattern class for AST nodes.
|
|
||||||
*/
|
|
||||||
class visitor {
|
|
||||||
public:
|
|
||||||
visitor() = default;
|
|
||||||
virtual ~visitor() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
visitor(visitor&&) = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
visitor& operator=(visitor&&) = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Copy constructor.
|
|
||||||
*/
|
|
||||||
visitor(const visitor&) = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Copy constructor.
|
|
||||||
*/
|
|
||||||
visitor& operator=(const visitor&) = default;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Visit a string_literal_node.
|
|
||||||
* @see string_literal_node
|
|
||||||
*
|
|
||||||
* @param node Node.
|
|
||||||
*/
|
|
||||||
virtual void visit(const string_literal_node& node) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Visit a integer_literal_node.
|
|
||||||
* @see integer_literal_node
|
|
||||||
*
|
|
||||||
* @param node Node.
|
|
||||||
*/
|
|
||||||
virtual void visit(const integer_literal_node& node) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Visit a var_read_expression_node.
|
|
||||||
* @see var_read_expression_node
|
|
||||||
*
|
|
||||||
* @param node Node.
|
|
||||||
*/
|
|
||||||
virtual void visit(const var_read_expression_node& node) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Visit a unaryop_expression_node.
|
|
||||||
* @see unaryop_expression_node
|
|
||||||
*
|
|
||||||
* @param node Node.
|
|
||||||
*/
|
|
||||||
virtual void visit(const unary_op_expression_node& node) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Visit a binop_expression_node.
|
|
||||||
* @see binop_expression_node
|
|
||||||
*
|
|
||||||
* @param node Node.
|
|
||||||
*/
|
|
||||||
virtual void visit(const binary_op_expression_node& node) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Visit a var_assign_expression_node.
|
|
||||||
* @see var_assign_expression_node
|
|
||||||
*
|
|
||||||
* @param node Node.
|
|
||||||
*/
|
|
||||||
virtual void visit(const var_assign_expression_node& node) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Visit a function_declaration_node.
|
|
||||||
* @see function_declaration_node
|
|
||||||
*
|
|
||||||
* @param node Node.
|
|
||||||
*/
|
|
||||||
virtual void visit(const function_declaration_node& node) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Visit a function_definition_node.
|
|
||||||
* @see function_definition_node
|
|
||||||
*
|
|
||||||
* @param node Node.
|
|
||||||
*/
|
|
||||||
virtual void visit(const function_definition_node& node) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Visit a return_statement_node.
|
|
||||||
* @see return_statement_node
|
|
||||||
*
|
|
||||||
* @param node Node.
|
|
||||||
*/
|
|
||||||
virtual void visit(const return_statement_node& node) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Visit a if_statement_node.
|
|
||||||
* @see if_statement_node
|
|
||||||
*
|
|
||||||
* @param node Node.
|
|
||||||
*/
|
|
||||||
virtual void visit(const if_statement_node& node) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Visit a compound_statement_node.
|
|
||||||
* @see compound_statement_node
|
|
||||||
*
|
|
||||||
* @param node Node.
|
|
||||||
*/
|
|
||||||
virtual void visit(const compound_statement_node& node) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Visit an AST error.
|
|
||||||
*
|
|
||||||
* @param error AST error.
|
|
||||||
*/
|
|
||||||
virtual void visit_error(const ast::error& error) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ast
|
|
||||||
} // namespace furc
|
|
||||||
|
|
||||||
#endif // FURC_AST_VISITOR_HPP
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
#ifndef FURC_DIAG_HPP
|
|
||||||
#define FURC_DIAG_HPP
|
|
||||||
|
|
||||||
#include <ostream>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
namespace furc {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief A location in file.
|
|
||||||
*/
|
|
||||||
struct location {
|
|
||||||
std::string_view filename; /**< File's name */
|
|
||||||
std::size_t line = 0; /**< Line */
|
|
||||||
std::size_t column = 0; /**< Column */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Compare two locations for equality.
|
|
||||||
*
|
|
||||||
* @param rhs Location to compare against.
|
|
||||||
* @return true if the locations are equal.
|
|
||||||
*/
|
|
||||||
bool operator==(const location& rhs) const {
|
|
||||||
return filename == rhs.filename && line == rhs.line && column == rhs.column;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Compare two locations for inequality.
|
|
||||||
*
|
|
||||||
* @param rhs Location to compare against.
|
|
||||||
* @return true if the locations are not equal.
|
|
||||||
*/
|
|
||||||
bool operator!=(const location& rhs) const { return !this->operator==(rhs); }
|
|
||||||
};
|
|
||||||
|
|
||||||
static inline std::ostream& operator<<(std::ostream& os, const location& location) {
|
|
||||||
return os << location.filename << ':' << location.line + 1 << ':' << location.column + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace furc
|
|
||||||
|
|
||||||
#endif // FURC_DIAG_HPP
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
#ifndef FURC_FRONT_IR_GENERATOR_HPP
|
|
||||||
#define FURC_FRONT_IR_GENERATOR_HPP
|
|
||||||
|
|
||||||
#include "furc/ast/fwd.hpp"
|
|
||||||
#include "furc/ast/visitor.hpp"
|
|
||||||
#include "furlang/ir/module.hpp"
|
|
||||||
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
namespace furc {
|
|
||||||
namespace front {
|
|
||||||
|
|
||||||
using ir_register = std::uint32_t;
|
|
||||||
|
|
||||||
class ir_generator final : public ast::visitor {
|
|
||||||
public:
|
|
||||||
ir_generator() = default;
|
|
||||||
~ir_generator() override = default;
|
|
||||||
|
|
||||||
ir_generator(ir_generator&&) = default;
|
|
||||||
ir_generator& operator=(ir_generator&&) = default;
|
|
||||||
ir_generator(const ir_generator&) = delete;
|
|
||||||
ir_generator& operator=(const ir_generator&) = delete;
|
|
||||||
public:
|
|
||||||
furlang::ir::module&& move_module() { return std::move(m_module); }
|
|
||||||
public:
|
|
||||||
void visit(const ast::function_definition_node& funcDef) override;
|
|
||||||
void visit(const ast::return_statement_node& returnStmt) override;
|
|
||||||
void visit(const ast::if_statement_node& node) override;
|
|
||||||
void visit(const ast::compound_statement_node& node) override;
|
|
||||||
void visit(const ast::string_literal_node& node) override;
|
|
||||||
void visit(const ast::integer_literal_node& node) override;
|
|
||||||
void visit(const ast::var_read_expression_node& node) override;
|
|
||||||
void visit(const ast::unary_op_expression_node& node) override;
|
|
||||||
void visit(const ast::binary_op_expression_node& node) override;
|
|
||||||
void visit(const ast::var_assign_expression_node& node) override;
|
|
||||||
private:
|
|
||||||
template <typename T, typename... Args>
|
|
||||||
void push(Args&&... args) {
|
|
||||||
if (!m_currentBlock->emplace<T>(std::forward<Args>(args)...)) {
|
|
||||||
throw std::runtime_error("block exited too soon");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
furlang::ir::block_index push_block();
|
|
||||||
private:
|
|
||||||
furlang::ir::module m_module;
|
|
||||||
std::unique_ptr<furlang::ir::function> m_currentFunction;
|
|
||||||
std::shared_ptr<furlang::ir::block> m_currentBlock;
|
|
||||||
ir_register m_registerCounter = 0;
|
|
||||||
|
|
||||||
std::unordered_map<std::string_view, ir_register> m_variables;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace front
|
|
||||||
} // namespace furc
|
|
||||||
|
|
||||||
#endif // FURC_FRONT_IR_GENERATOR_HPP
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
#ifndef FURC_FRONT_LEXER_HPP
|
|
||||||
#define FURC_FRONT_LEXER_HPP
|
|
||||||
|
|
||||||
#include "furc/front/token.hpp"
|
|
||||||
|
|
||||||
namespace furc {
|
|
||||||
namespace front {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Lexer.
|
|
||||||
*
|
|
||||||
* Furlang's lazy tokenizer.
|
|
||||||
*/
|
|
||||||
class lexer {
|
|
||||||
public:
|
|
||||||
lexer() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Construct a new lexer.
|
|
||||||
*
|
|
||||||
* @param filename Filename for debugging.
|
|
||||||
* @param content Content.
|
|
||||||
*/
|
|
||||||
lexer(std::string_view filename, std::string_view content);
|
|
||||||
~lexer() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
lexer(lexer&&) = default;
|
|
||||||
|
|
||||||
lexer(const lexer&) = delete;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
lexer& operator=(lexer&&) = default;
|
|
||||||
|
|
||||||
lexer& operator=(const lexer&) = delete;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns a handle to next token.
|
|
||||||
*
|
|
||||||
* @return The token handle.
|
|
||||||
*/
|
|
||||||
token_r next_token();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Checks whether the cursor is at the EOF.
|
|
||||||
*
|
|
||||||
* @return true if the cursor is at the EOF.
|
|
||||||
*/
|
|
||||||
bool empty() const { return m_cursor >= m_content.size(); }
|
|
||||||
private:
|
|
||||||
void next();
|
|
||||||
char get(std::size_t offset = 0) const;
|
|
||||||
void skip_spaces();
|
|
||||||
location current_location();
|
|
||||||
private:
|
|
||||||
std::string_view m_filename;
|
|
||||||
std::string_view m_content;
|
|
||||||
std::size_t m_cursor = 0;
|
|
||||||
std::size_t m_row = 0;
|
|
||||||
std::size_t m_lineStart = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace front
|
|
||||||
} // namespace furc
|
|
||||||
|
|
||||||
#endif // FURC_FRONT_LEXER_HPP
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
#ifndef FURC_FRONT_PARSER_HPP
|
|
||||||
#define FURC_FRONT_PARSER_HPP
|
|
||||||
|
|
||||||
#include "furc/ast/fwd.hpp"
|
|
||||||
#include "furc/front/lexer.hpp"
|
|
||||||
#include "furlang/arena.hpp"
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace furc {
|
|
||||||
namespace front {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Parser.
|
|
||||||
*
|
|
||||||
* Furlang's parser.
|
|
||||||
*/
|
|
||||||
class parser final {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Construct a new parser from content.
|
|
||||||
*
|
|
||||||
* @param filename Filename for debugging.
|
|
||||||
* @param content Content.
|
|
||||||
*/
|
|
||||||
parser(std::string_view filename, std::string_view content);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Construct a new parser from file.
|
|
||||||
*
|
|
||||||
* Constructs a lexer with content read from file passed through \p filename.
|
|
||||||
*
|
|
||||||
* @param filename Name of the file.
|
|
||||||
*/
|
|
||||||
parser(std::string_view filename);
|
|
||||||
~parser() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
parser(parser&&) = default;
|
|
||||||
|
|
||||||
parser(const parser&) = delete;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
parser& operator=(parser&&) = default;
|
|
||||||
|
|
||||||
parser& operator=(const parser&) = delete;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns a parsed program.
|
|
||||||
*
|
|
||||||
* @return Handle to an AST node of the program.
|
|
||||||
*/
|
|
||||||
ast::program_node_r parse() &;
|
|
||||||
private:
|
|
||||||
ast::declaration_node_r parse_declaration();
|
|
||||||
ast::statement_node_r parse_statement();
|
|
||||||
ast::expression_node_r parse_expression(std::uint32_t precedence = 16);
|
|
||||||
|
|
||||||
ast::expression_node_r parse_expression_primary();
|
|
||||||
ast::expression_node_r parse_expression_unary(std::uint32_t precedence);
|
|
||||||
ast::expression_node_r parse_expression_rhs(ast::expression_node_p&& init, std::uint32_t precedence);
|
|
||||||
|
|
||||||
ast::body_r parse_body();
|
|
||||||
private:
|
|
||||||
token_r next_token();
|
|
||||||
const token_r& peek_token();
|
|
||||||
token_r eat_token(token_t type);
|
|
||||||
private:
|
|
||||||
std::string m_filename;
|
|
||||||
std::string m_content;
|
|
||||||
lexer m_lexer;
|
|
||||||
furlang::arena m_arena;
|
|
||||||
std::vector<token_r> m_peekBuffer;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace front
|
|
||||||
} // namespace furc
|
|
||||||
|
|
||||||
#endif // FURC_FRONT_PARSER_HPP
|
|
||||||
@@ -1,380 +0,0 @@
|
|||||||
#ifndef FURC_FRONT_TOKEN_HPP
|
|
||||||
#define FURC_FRONT_TOKEN_HPP
|
|
||||||
|
|
||||||
#include "furc/diag.hpp"
|
|
||||||
#include "furlang/result.hpp"
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <ostream>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
namespace furc {
|
|
||||||
namespace front {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Token type.
|
|
||||||
*/
|
|
||||||
enum class token_t {
|
|
||||||
None, /**< None */
|
|
||||||
Identifier, /**< Identifier */
|
|
||||||
String, /**< String */
|
|
||||||
Keyword, /**< Keyword */
|
|
||||||
Integer, /**< Integer */
|
|
||||||
|
|
||||||
LParen, /**< `(` */
|
|
||||||
RParen, /**< `)` */
|
|
||||||
LBrace, /**< `{` */
|
|
||||||
RBrace, /**< `}` */
|
|
||||||
LBracket, /**< `[` */
|
|
||||||
RBracket, /**< `]` */
|
|
||||||
Semicolon, /**< `;` */
|
|
||||||
Colon, /**< `:` */
|
|
||||||
Comma, /**< `,` */
|
|
||||||
Dot, /**< `.` */
|
|
||||||
|
|
||||||
Plus, /**< `+` */
|
|
||||||
Minus, /**< `-` */
|
|
||||||
Star, /**< `*` */
|
|
||||||
Slash, /**< `/` */
|
|
||||||
Percent, /**< `%` */
|
|
||||||
DPlus, /**< `++` */
|
|
||||||
DMinus, /**< `--` */
|
|
||||||
|
|
||||||
Eq, /**< `=` */
|
|
||||||
PlusEq, /**< `+=` */
|
|
||||||
MinusEq, /**< `-=` */
|
|
||||||
StarEq, /**< `*=` */
|
|
||||||
SlashEq, /**< `/=` */
|
|
||||||
PercentEq, /**< `%=` */
|
|
||||||
|
|
||||||
DEq, /**< `==` */
|
|
||||||
NotEq, /**< `!=` */
|
|
||||||
LessThan, /**< `<` */
|
|
||||||
GreaterThan, /**< `>` */
|
|
||||||
LessEq, /**< `<=` */
|
|
||||||
GreaterEq, /**< `>=` */
|
|
||||||
};
|
|
||||||
|
|
||||||
static inline std::ostream& operator<<(std::ostream& os, token_t type) {
|
|
||||||
switch (type) {
|
|
||||||
case token_t::None: return os << "none";
|
|
||||||
case token_t::Identifier: return os << "identifier";
|
|
||||||
case token_t::String: return os << "string";
|
|
||||||
case token_t::Keyword: return os << "keyword";
|
|
||||||
case token_t::Integer: return os << "integer";
|
|
||||||
case token_t::LParen: return os << "'('";
|
|
||||||
case token_t::RParen: return os << "')'";
|
|
||||||
case token_t::LBrace: return os << "'{'";
|
|
||||||
case token_t::RBrace: return os << "'}'";
|
|
||||||
case token_t::LBracket: return os << "'['";
|
|
||||||
case token_t::RBracket: return os << "']'";
|
|
||||||
case token_t::Semicolon: return os << "';'";
|
|
||||||
case token_t::Colon: return os << "':'";
|
|
||||||
case token_t::Comma: return os << "','";
|
|
||||||
case token_t::Dot: return os << "'.'";
|
|
||||||
case token_t::Plus: return os << "'+'";
|
|
||||||
case token_t::Minus: return os << "'-'";
|
|
||||||
case token_t::Star: return os << "'*'";
|
|
||||||
case token_t::Slash: return os << "'/'";
|
|
||||||
case token_t::Percent: return os << "'%'";
|
|
||||||
case token_t::DPlus: return os << "++";
|
|
||||||
case token_t::DMinus: return os << "--";
|
|
||||||
case token_t::Eq: return os << "=";
|
|
||||||
case token_t::PlusEq: return os << "+=";
|
|
||||||
case token_t::MinusEq: return os << "-=";
|
|
||||||
case token_t::StarEq: return os << "*=";
|
|
||||||
case token_t::SlashEq: return os << "/=";
|
|
||||||
case token_t::PercentEq: return os << "%=";
|
|
||||||
case token_t::DEq: return os << "==";
|
|
||||||
case token_t::NotEq: return os << "!=";
|
|
||||||
case token_t::LessThan: return os << "<";
|
|
||||||
case token_t::GreaterThan: return os << ">";
|
|
||||||
case token_t::LessEq: return os << "<=";
|
|
||||||
case token_t::GreaterEq: return os << ">=";
|
|
||||||
}
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline std::string operator+(const std::string& str, token_t type) {
|
|
||||||
switch (type) {
|
|
||||||
case token_t::None: return str + "none";
|
|
||||||
case token_t::Identifier: return str + "identifier";
|
|
||||||
case token_t::String: return str + "string";
|
|
||||||
case token_t::Keyword: return str + "keyword";
|
|
||||||
case token_t::Integer: return str + "integer";
|
|
||||||
case token_t::LParen: return str + "'('";
|
|
||||||
case token_t::RParen: return str + "')'";
|
|
||||||
case token_t::LBrace: return str + "'{'";
|
|
||||||
case token_t::RBrace: return str + "'}'";
|
|
||||||
case token_t::LBracket: return str + "'['";
|
|
||||||
case token_t::RBracket: return str + "']'";
|
|
||||||
case token_t::Semicolon: return str + "';'";
|
|
||||||
case token_t::Colon: return str + "':'";
|
|
||||||
case token_t::Comma: return str + "','";
|
|
||||||
case token_t::Dot: return str + "'.'";
|
|
||||||
case token_t::Plus: return str + "'+'";
|
|
||||||
case token_t::Minus: return str + "'-'";
|
|
||||||
case token_t::Star: return str + "'*'";
|
|
||||||
case token_t::Slash: return str + "'/'";
|
|
||||||
case token_t::Percent: return str + "'%'";
|
|
||||||
case token_t::DPlus: return str + "++";
|
|
||||||
case token_t::DMinus: return str + "--";
|
|
||||||
case token_t::Eq: return str + "=";
|
|
||||||
case token_t::PlusEq: return str + "+=";
|
|
||||||
case token_t::MinusEq: return str + "-=";
|
|
||||||
case token_t::StarEq: return str + "*=";
|
|
||||||
case token_t::SlashEq: return str + "/=";
|
|
||||||
case token_t::PercentEq: return str + "%=";
|
|
||||||
case token_t::DEq: return str + "==";
|
|
||||||
case token_t::NotEq: return str + "!=";
|
|
||||||
case token_t::LessThan: return str + "<";
|
|
||||||
case token_t::GreaterThan: return str + ">";
|
|
||||||
case token_t::LessEq: return str + "<=";
|
|
||||||
case token_t::GreaterEq: return str + ">=";
|
|
||||||
}
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Keyword token.
|
|
||||||
*/
|
|
||||||
enum class keyword_token {
|
|
||||||
None, /**< None */
|
|
||||||
Func, /**< `func` */
|
|
||||||
Return, /**< `return` */
|
|
||||||
If, /**< `if` */
|
|
||||||
Else, /**< `else` */
|
|
||||||
};
|
|
||||||
|
|
||||||
static inline std::ostream& operator<<(std::ostream& os, keyword_token keyword) {
|
|
||||||
switch (keyword) {
|
|
||||||
case keyword_token::None: return os << "none";
|
|
||||||
case keyword_token::Func: return os << "func";
|
|
||||||
case keyword_token::Return: return os << "return";
|
|
||||||
case keyword_token::If: return os << "if";
|
|
||||||
case keyword_token::Else: return os << "else";
|
|
||||||
}
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline std::string operator+(const std::string& str, keyword_token keyword) {
|
|
||||||
switch (keyword) {
|
|
||||||
case keyword_token::None: return str + "none";
|
|
||||||
case keyword_token::Func: return str + "func";
|
|
||||||
case keyword_token::Return: return str + "return";
|
|
||||||
case keyword_token::If: return str + "if";
|
|
||||||
case keyword_token::Else: return str + "else";
|
|
||||||
}
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
using integer_token = std::uint64_t; /**< Integer token. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Token.
|
|
||||||
*/
|
|
||||||
struct token {
|
|
||||||
location location; /**< Token location. */
|
|
||||||
token_t type = token_t::None; /**< Token type. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Token's value.
|
|
||||||
*/
|
|
||||||
union value {
|
|
||||||
/**
|
|
||||||
* @brief Null value. For token_t::None, token_t::Plus, token_t::Minus, etc.
|
|
||||||
* @see token_t::None
|
|
||||||
*/
|
|
||||||
std::nullptr_t null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief String value. For token_t::Identifier and token_t::String.
|
|
||||||
* @see token_t::Identifier
|
|
||||||
* @see token_t::String
|
|
||||||
*/
|
|
||||||
std::string_view string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Keyword value. For token_t::Keyword.
|
|
||||||
* @see token_t::Keyword.
|
|
||||||
*/
|
|
||||||
keyword_token keyword;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Integer value. For token_t::Integer.
|
|
||||||
* @see token_t::Integer
|
|
||||||
*/
|
|
||||||
integer_token integer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Construct a new null value.
|
|
||||||
*
|
|
||||||
* @param value Null value.
|
|
||||||
*/
|
|
||||||
value(std::nullptr_t value = nullptr)
|
|
||||||
: null(value) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Construct a new string value.
|
|
||||||
*
|
|
||||||
* @param value The string.
|
|
||||||
*/
|
|
||||||
value(std::string_view value)
|
|
||||||
: string(value) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Construct a new keyword value.
|
|
||||||
*
|
|
||||||
* @param value The keyword.
|
|
||||||
*/
|
|
||||||
value(keyword_token value)
|
|
||||||
: keyword(value) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Construct a new integer value.
|
|
||||||
*
|
|
||||||
* @param value The integer.
|
|
||||||
*/
|
|
||||||
value(integer_token value)
|
|
||||||
: integer(value) {}
|
|
||||||
} value; /**< Token value. */
|
|
||||||
|
|
||||||
token() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Construct a new null token.
|
|
||||||
*
|
|
||||||
* @param location Token's location.
|
|
||||||
* @param type Token's type.
|
|
||||||
*/
|
|
||||||
token(struct location location, token_t type)
|
|
||||||
: location(location), type(type) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Construct a new string token.
|
|
||||||
*
|
|
||||||
* @param location Token's location.
|
|
||||||
* @param type Token's type.
|
|
||||||
* @param value String value.
|
|
||||||
*/
|
|
||||||
token(struct location location, token_t type, std::string_view value)
|
|
||||||
: location(location), type(type), value(value) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Construct a new keyword token.
|
|
||||||
*
|
|
||||||
* @param location Token's location.
|
|
||||||
* @param keyword Keyword value.
|
|
||||||
*/
|
|
||||||
token(struct location location, keyword_token keyword)
|
|
||||||
: location(location), type(token_t::Keyword), value(keyword) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Construct a new integer token.
|
|
||||||
*
|
|
||||||
* @param location Token's location.
|
|
||||||
* @param integer Integer value.
|
|
||||||
*/
|
|
||||||
token(struct location location, integer_token integer)
|
|
||||||
: location(location), type(token_t::Integer), value(integer) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns pointer to this token's value.
|
|
||||||
*
|
|
||||||
* @return Pointer to the token's value.
|
|
||||||
*/
|
|
||||||
union value* operator->() { return &value; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns constant pointer to this token's value.
|
|
||||||
*
|
|
||||||
* @return Pointer to the token's value.
|
|
||||||
*/
|
|
||||||
const union value* operator->() const { return &value; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Compares two tokens for equality.
|
|
||||||
*
|
|
||||||
* @param rhs Token to compare against.
|
|
||||||
* @return true if the tokens are equal.
|
|
||||||
*/
|
|
||||||
bool operator==(const token& rhs) const {
|
|
||||||
if (type != rhs.type) return false;
|
|
||||||
switch (type) {
|
|
||||||
case token_t::Identifier:
|
|
||||||
case token_t::String: return value.string == rhs.value.string;
|
|
||||||
case token_t::Keyword: return value.keyword == rhs.value.keyword;
|
|
||||||
case token_t::Integer: return value.integer == rhs.value.integer;
|
|
||||||
default: return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static inline std::ostream& operator<<(std::ostream& os, const token& token) {
|
|
||||||
switch (token.type) {
|
|
||||||
case token_t::Identifier:
|
|
||||||
case token_t::String: return os << token.value.string;
|
|
||||||
case token_t::Keyword: return os << token.value.keyword;
|
|
||||||
case token_t::Integer: return os << token.value.integer;
|
|
||||||
default: return os << token.type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Token error type
|
|
||||||
*/
|
|
||||||
enum class token_error_t {
|
|
||||||
EndOfFile, /**< End of file */
|
|
||||||
UnexpectedEof, /**< Unexpected end of file */
|
|
||||||
UnexpectedCharacter, /**< Unexpected character */
|
|
||||||
UnexpectedToken, /**< Unexpected character */
|
|
||||||
IntegerOverflow, /**< Integer overflow */
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Token error
|
|
||||||
*
|
|
||||||
* For token_r alias.
|
|
||||||
*/
|
|
||||||
struct token_error {
|
|
||||||
location location; /**< Error location. */
|
|
||||||
token_error_t type; /**< Error type. */
|
|
||||||
std::string message; /**< Error message. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Compares two token errors for equality.
|
|
||||||
*
|
|
||||||
* @param rhs Error to compare against.
|
|
||||||
* @return true if the errors are equal.
|
|
||||||
*/
|
|
||||||
bool operator==(const token_error& rhs) const {
|
|
||||||
return location == rhs.location && type == rhs.type && message == rhs.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Compares two token errors for inequality.
|
|
||||||
*
|
|
||||||
* @param rhs Error to compare against.
|
|
||||||
* @return true if the errors are not equal.
|
|
||||||
*/
|
|
||||||
bool operator!=(const token_error& rhs) const { return !this->operator==(rhs); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Prints a token error to an output stream.
|
|
||||||
*
|
|
||||||
* @param os Output stream.
|
|
||||||
* @param error Token error to print.
|
|
||||||
* @return The output stream.
|
|
||||||
*/
|
|
||||||
friend std::ostream& operator<<(std::ostream& os, const token_error& error) {
|
|
||||||
return os << error.location << ": error: unknown";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
using token_r = furlang::result<token, token_error>; /**< Alias to a token result. */
|
|
||||||
|
|
||||||
} // namespace front
|
|
||||||
} // namespace furc
|
|
||||||
|
|
||||||
#endif // FURC_FRONT_TOKEN_HPP
|
|
||||||
@@ -1,215 +0,0 @@
|
|||||||
#include "furc/ast/declaration.hpp"
|
|
||||||
#include "furc/ast/expression.hpp"
|
|
||||||
#include "furc/ast/node.hpp"
|
|
||||||
#include "furc/ast/program.hpp"
|
|
||||||
#include "furc/ast/statement.hpp"
|
|
||||||
|
|
||||||
#include <ostream>
|
|
||||||
|
|
||||||
namespace furc::ast {
|
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, const error& error) {
|
|
||||||
return os << error.location << ": ERROR: unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
bool expression_node::equal(const node& rhs) const {
|
|
||||||
return expression_type() == dynamic_cast<const expression_node&>(rhs).expression_type();
|
|
||||||
}
|
|
||||||
|
|
||||||
void var_read_expression_node::accept(visitor& visitor) const {
|
|
||||||
visitor.visit(*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream& var_read_expression_node::print(std::ostream& os) const {
|
|
||||||
return os << m_name;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool var_read_expression_node::equal(const node& rhsNode) const {
|
|
||||||
const auto& rhs = dynamic_cast<const var_read_expression_node&>(rhsNode);
|
|
||||||
return expression_node::equal(rhsNode) && m_name == rhs.m_name;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, unaryop_expression_node_t type) {
|
|
||||||
switch (type) {
|
|
||||||
case unaryop_expression_node_t::Positive: return os << "+";
|
|
||||||
case unaryop_expression_node_t::Negative: return os << "-";
|
|
||||||
case unaryop_expression_node_t::PrefixIncrement:
|
|
||||||
case unaryop_expression_node_t::PostfixIncrement: return os << "++";
|
|
||||||
case unaryop_expression_node_t::PrefixDecrement:
|
|
||||||
case unaryop_expression_node_t::PostfixDecrement: return os << "--";
|
|
||||||
}
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
void unary_op_expression_node::accept(visitor& visitor) const {
|
|
||||||
visitor.visit(*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream& unary_op_expression_node::print(std::ostream& os) const {
|
|
||||||
if (m_node == nullptr) return os;
|
|
||||||
switch (m_type) {
|
|
||||||
case unaryop_expression_node_t::Positive:
|
|
||||||
case unaryop_expression_node_t::Negative:
|
|
||||||
case unaryop_expression_node_t::PrefixIncrement:
|
|
||||||
case unaryop_expression_node_t::PrefixDecrement: return os << '(' << m_type << *m_node << ')';
|
|
||||||
case unaryop_expression_node_t::PostfixIncrement:
|
|
||||||
case unaryop_expression_node_t::PostfixDecrement: return os << '(' << *m_node << m_type << ')';
|
|
||||||
}
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool unary_op_expression_node::equal(const node& rhsNode) const {
|
|
||||||
const auto& rhs = dynamic_cast<const unary_op_expression_node&>(rhsNode);
|
|
||||||
return expression_node::equal(rhsNode) && m_type == rhs.m_type && m_node == rhs.m_node;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, binop_expression_node_t type) {
|
|
||||||
switch (type) {
|
|
||||||
default:
|
|
||||||
case binop_expression_node_t::None: return os;
|
|
||||||
case binop_expression_node_t::Add: return os << '+';
|
|
||||||
case binop_expression_node_t::Sub: return os << '-';
|
|
||||||
case binop_expression_node_t::Mul: return os << '*';
|
|
||||||
case binop_expression_node_t::Div: return os << '/';
|
|
||||||
case binop_expression_node_t::Mod: return os << '%';
|
|
||||||
case binop_expression_node_t::Equal: return os << "==";
|
|
||||||
case binop_expression_node_t::NotEqual: return os << "!=";
|
|
||||||
case binop_expression_node_t::LessThan: return os << '<';
|
|
||||||
case binop_expression_node_t::GreaterThan: return os << '>';
|
|
||||||
case binop_expression_node_t::LessEqual: return os << "<=";
|
|
||||||
case binop_expression_node_t::GreaterEqual: return os << ">=";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void binary_op_expression_node::accept(visitor& visitor) const {
|
|
||||||
visitor.visit(*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream& binary_op_expression_node::print(std::ostream& os) const {
|
|
||||||
if (m_type == binop_expression_node_t::None) return os;
|
|
||||||
return os << '(' << *m_lhs << ' ' << m_type << ' ' << *m_rhs << ')';
|
|
||||||
}
|
|
||||||
|
|
||||||
bool binary_op_expression_node::equal(const node& rhsNode) const {
|
|
||||||
const auto& rhs = dynamic_cast<const binary_op_expression_node&>(rhsNode);
|
|
||||||
return expression_node::equal(rhsNode) && m_type == rhs.m_type && m_lhs == rhs.m_lhs && m_rhs == rhs.m_rhs;
|
|
||||||
}
|
|
||||||
|
|
||||||
void var_assign_expression_node::accept(visitor& visitor) const {
|
|
||||||
visitor.visit(*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream& var_assign_expression_node::print(std::ostream& os) const {
|
|
||||||
return os << '(' << *m_lhs << ' ' << m_compound << "= " << *m_rhs << ')';
|
|
||||||
}
|
|
||||||
|
|
||||||
bool var_assign_expression_node::equal(const node& rhsNode) const {
|
|
||||||
const auto& rhs = dynamic_cast<const var_assign_expression_node&>(rhsNode);
|
|
||||||
return expression_node::equal(rhsNode) && m_compound == rhs.m_compound && m_lhs == rhs.m_lhs && m_rhs == rhs.m_rhs;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool declaration_node::equal(const node& rhs) const {
|
|
||||||
return declaration_type() == dynamic_cast<const declaration_node&>(rhs).declaration_type();
|
|
||||||
}
|
|
||||||
|
|
||||||
void function_declaration_node::accept(visitor& visitor) const {
|
|
||||||
visitor.visit(*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream& function_declaration_node::print(std::ostream& os) const {
|
|
||||||
return os << "function " << p_name << " declaration";
|
|
||||||
}
|
|
||||||
|
|
||||||
bool function_declaration_node::equal(const node& rhs) const {
|
|
||||||
return declaration_node::equal(rhs) && p_name == dynamic_cast<const function_declaration_node&>(rhs).p_name;
|
|
||||||
}
|
|
||||||
|
|
||||||
void function_definition_node::accept(visitor& visitor) const {
|
|
||||||
visitor.visit(*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream& function_definition_node::print(std::ostream& os) const {
|
|
||||||
function_declaration_node::print(os);
|
|
||||||
os << ":\n";
|
|
||||||
for (const auto& entry : m_body.statements)
|
|
||||||
os << entry << '\n';
|
|
||||||
return os << m_body.end << ": " << p_name << " end";
|
|
||||||
}
|
|
||||||
|
|
||||||
bool function_definition_node::equal(const node& rhs) const {
|
|
||||||
return function_declaration_node::equal(rhs) && m_body == dynamic_cast<const function_definition_node&>(rhs).m_body;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool statement_node::equal(const node& rhs) const {
|
|
||||||
return statement_type() == dynamic_cast<const statement_node&>(rhs).statement_type();
|
|
||||||
}
|
|
||||||
|
|
||||||
void return_statement_node::accept(visitor& visitor) const {
|
|
||||||
visitor.visit(*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream& return_statement_node::print(std::ostream& os) const {
|
|
||||||
os << "return statement";
|
|
||||||
if (m_value.has_value()) return os << ' ' << *m_value.value();
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool return_statement_node::equal(const node& rhs) const {
|
|
||||||
return statement_node::equal(rhs) && m_value == dynamic_cast<const return_statement_node&>(rhs).m_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void if_statement_node::accept(visitor& visitor) const {
|
|
||||||
visitor.visit(*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream& if_statement_node::print(std::ostream& os) const {
|
|
||||||
os << "if " << *m_cond << ", then:\n";
|
|
||||||
os << m_then;
|
|
||||||
if (m_else.has_value()) os << *m_else.value();
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool if_statement_node::equal(const node& rhsNode) const {
|
|
||||||
const auto& rhs = dynamic_cast<const if_statement_node&>(rhsNode);
|
|
||||||
return statement_node::equal(rhs) && m_cond == rhs.m_cond && m_then == rhs.m_then && m_else == rhs.m_else;
|
|
||||||
}
|
|
||||||
|
|
||||||
void compound_statement_node::accept(visitor& visitor) const {
|
|
||||||
visitor.visit(*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream& compound_statement_node::print(std::ostream& os) const {
|
|
||||||
return os << m_body;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool compound_statement_node::equal(const node& rhs) const {
|
|
||||||
return statement_node::equal(rhs) && m_body == dynamic_cast<const compound_statement_node&>(rhs).m_body;
|
|
||||||
}
|
|
||||||
|
|
||||||
void program_node::accept(visitor& visitor) const {
|
|
||||||
for (const auto& decl : m_declarations) {
|
|
||||||
decl->accept(visitor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream& program_node::print(std::ostream& os) const {
|
|
||||||
os << "program:";
|
|
||||||
for (const auto& handle : m_declarations) {
|
|
||||||
os << '\n' << handle;
|
|
||||||
}
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool program_node::equal(const node& rhs) const {
|
|
||||||
return m_declarations == dynamic_cast<const program_node&>(rhs).m_declarations;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, const body& body) {
|
|
||||||
os << "body:";
|
|
||||||
for (const auto& stmt : body.statements) {
|
|
||||||
os << '\n' << stmt;
|
|
||||||
}
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace furc::ast
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
#include "furc/front/ir_generator.hpp"
|
|
||||||
|
|
||||||
#include "furc/ast/declaration.hpp" // IWYU pragma: keep
|
|
||||||
#include "furc/ast/expression.hpp" // IWYU pragma: keep
|
|
||||||
#include "furc/ast/literal.hpp" // IWYU pragma: keep
|
|
||||||
#include "furc/ast/statement.hpp" // IWYU pragma: keep
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
namespace furc::front {
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
namespace ir = furlang::ir;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ir_generator::visit(const ast::function_definition_node& funcDef) {
|
|
||||||
m_currentFunction = std::make_unique<furlang::ir::function>(std::string(funcDef.name()));
|
|
||||||
|
|
||||||
push_block();
|
|
||||||
for (const auto& stmt : funcDef.body().statements) {
|
|
||||||
stmt.value()->accept(*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_currentBlock->emplace<ir::return_instruction>();
|
|
||||||
|
|
||||||
m_module.push(std::move(m_currentFunction));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ir_generator::visit(const ast::return_statement_node& returnStmt) {
|
|
||||||
if (returnStmt.value().has_value()) {
|
|
||||||
returnStmt.value().value()->accept(*this);
|
|
||||||
push<ir::return_instruction>(ir::operand::new_reg(m_registerCounter - 1));
|
|
||||||
} else {
|
|
||||||
push<ir::return_instruction>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ir_generator::visit(const ast::if_statement_node& node) {
|
|
||||||
node.cond()->accept(*this);
|
|
||||||
ir_register cond = m_registerCounter - 1;
|
|
||||||
push<ir::branch_cond_instruction>(ir::operand::new_reg(cond),
|
|
||||||
m_currentFunction->blocks().size(),
|
|
||||||
m_currentFunction->blocks().size() + 1);
|
|
||||||
|
|
||||||
push_block(); // then block
|
|
||||||
node.then()->accept(*this);
|
|
||||||
if (node.elze().has_value()) {
|
|
||||||
m_currentBlock->emplace<ir::branch_instruction>(m_currentFunction->blocks().size() + 1);
|
|
||||||
|
|
||||||
push_block(); // else block
|
|
||||||
node.elze().value()->accept(*this);
|
|
||||||
}
|
|
||||||
m_currentBlock->emplace<ir::branch_instruction>(m_currentFunction->blocks().size());
|
|
||||||
|
|
||||||
push_block(); // merge block
|
|
||||||
}
|
|
||||||
|
|
||||||
void ir_generator::visit(const ast::compound_statement_node& node) {
|
|
||||||
for (const auto& stmt : node.body().statements) {
|
|
||||||
stmt.value()->accept(*this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ir_generator::visit(const ast::string_literal_node& node) {
|
|
||||||
push<furlang::ir::assign_instruction>(ir::operand::new_string(node.value()),
|
|
||||||
ir::operand::new_reg(m_registerCounter++));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ir_generator::visit(const ast::integer_literal_node& node) {
|
|
||||||
push<furlang::ir::assign_instruction>(ir::operand::new_integer(node.value()),
|
|
||||||
ir::operand::new_reg(m_registerCounter++));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ir_generator::visit(const ast::var_read_expression_node& node) {
|
|
||||||
if (auto it = m_variables.find(node.get_name()); it != m_variables.end()) {
|
|
||||||
push<furlang::ir::assign_instruction>(ir::operand::new_reg(it->second),
|
|
||||||
ir::operand::new_reg(m_registerCounter++));
|
|
||||||
} else {
|
|
||||||
throw std::runtime_error("unknown variable");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ir_generator::visit(const ast::unary_op_expression_node& node) {
|
|
||||||
throw std::runtime_error("unimplemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline furlang::ir::binary_op_instruction_t binary_op_instruction_t(ast::binop_expression_node_t type) {
|
|
||||||
switch (type) {
|
|
||||||
case ast::binop_expression_node_t::Add: return furlang::ir::binary_op_instruction_t::Add;
|
|
||||||
case ast::binop_expression_node_t::Sub: return furlang::ir::binary_op_instruction_t::Sub;
|
|
||||||
case ast::binop_expression_node_t::Mul: return furlang::ir::binary_op_instruction_t::Mul;
|
|
||||||
case ast::binop_expression_node_t::Div: return furlang::ir::binary_op_instruction_t::Div;
|
|
||||||
case ast::binop_expression_node_t::Mod: return furlang::ir::binary_op_instruction_t::Mod;
|
|
||||||
case ast::binop_expression_node_t::Equal: return furlang::ir::binary_op_instruction_t::Eq;
|
|
||||||
case ast::binop_expression_node_t::NotEqual: return furlang::ir::binary_op_instruction_t::NotEq;
|
|
||||||
case ast::binop_expression_node_t::LessThan: return furlang::ir::binary_op_instruction_t::LessThan;
|
|
||||||
case ast::binop_expression_node_t::GreaterThan: return furlang::ir::binary_op_instruction_t::GreaterThan;
|
|
||||||
case ast::binop_expression_node_t::LessEqual: return furlang::ir::binary_op_instruction_t::LessEq;
|
|
||||||
case ast::binop_expression_node_t::GreaterEqual: return furlang::ir::binary_op_instruction_t::GreaterEq;
|
|
||||||
case ast::binop_expression_node_t::None:
|
|
||||||
default: throw std::runtime_error("unreachable");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ir_generator::visit(const ast::binary_op_expression_node& node) {
|
|
||||||
node.lhs()->accept(*this);
|
|
||||||
ir_register lhs = m_registerCounter - 1;
|
|
||||||
node.rhs()->accept(*this);
|
|
||||||
ir_register rhs = m_registerCounter - 1;
|
|
||||||
ir_register dst = m_registerCounter++;
|
|
||||||
push<furlang::ir::binary_op_instruction>(binary_op_instruction_t(node.type()),
|
|
||||||
ir::operand::new_reg(lhs),
|
|
||||||
ir::operand::new_reg(rhs),
|
|
||||||
ir::operand::new_reg(dst));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ir_generator::visit(const ast::var_assign_expression_node& node) {
|
|
||||||
node.rhs()->accept(*this);
|
|
||||||
ir_register rhs = m_registerCounter - 1;
|
|
||||||
assert(node.lhs()->expression_type() == ast::expression_node_t::VarRead);
|
|
||||||
auto lhs = std::dynamic_pointer_cast<ast::var_read_expression_node>(node.lhs());
|
|
||||||
|
|
||||||
ir_register reg = 0;
|
|
||||||
if (auto it = m_variables.find(lhs->get_name()); it != m_variables.end()) {
|
|
||||||
reg = it->second;
|
|
||||||
} else {
|
|
||||||
m_variables[lhs->get_name()] = reg = m_registerCounter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto compound = node.compound();
|
|
||||||
if (compound != ast::binop_expression_node_t::None) {
|
|
||||||
push<ir::binary_op_instruction>(binary_op_instruction_t(compound),
|
|
||||||
ir::operand::new_reg(reg),
|
|
||||||
ir::operand::new_reg(rhs),
|
|
||||||
ir::operand::new_reg(reg));
|
|
||||||
} else {
|
|
||||||
push<ir::assign_instruction>(ir::operand::new_reg(rhs), ir::operand::new_reg(reg));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
furlang::ir::block_index ir_generator::push_block() {
|
|
||||||
if (!m_currentFunction->blocks().empty() && !m_currentFunction->blocks().back()->has_exit()) {
|
|
||||||
throw std::runtime_error(
|
|
||||||
"block " + std::to_string(m_currentFunction->blocks().size() - 1) + " is lacking an exit");
|
|
||||||
}
|
|
||||||
ir::block_index index = m_currentFunction->blocks().size();
|
|
||||||
m_currentBlock = m_currentFunction->push();
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace furc::front
|
|
||||||
@@ -1,188 +0,0 @@
|
|||||||
#include "furc/front/lexer.hpp"
|
|
||||||
|
|
||||||
#include <cctype>
|
|
||||||
#include <limits>
|
|
||||||
#include <map>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
namespace furc::front {
|
|
||||||
|
|
||||||
using namespace std::string_literals;
|
|
||||||
|
|
||||||
lexer::lexer(std::string_view filename, std::string_view content)
|
|
||||||
: m_filename(filename), m_content(content) {}
|
|
||||||
|
|
||||||
token_r lexer::next_token() {
|
|
||||||
skip_spaces();
|
|
||||||
while (m_cursor + 2 <= m_content.size() && m_content[m_cursor] == '/') {
|
|
||||||
if (m_content[m_cursor + 1] == '/') {
|
|
||||||
m_cursor += 2;
|
|
||||||
while (m_content[m_cursor] != '\n') {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
} else if (m_content[m_cursor + 1] == '*') {
|
|
||||||
m_cursor += 2;
|
|
||||||
while (m_cursor + 2 < m_content.size()) {
|
|
||||||
if (m_content[m_cursor + 1] == '*') {
|
|
||||||
next();
|
|
||||||
} else if (m_content[m_cursor + 0] != '*' || m_content[m_cursor + 1] != '/') {
|
|
||||||
next();
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (m_cursor + 2 >= m_content.size()) {
|
|
||||||
next();
|
|
||||||
return token_r(
|
|
||||||
token_error{ current_location(), token_error_t::UnexpectedEof, "before enclosing `*/`" });
|
|
||||||
}
|
|
||||||
m_cursor += 2;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
skip_spaces();
|
|
||||||
}
|
|
||||||
|
|
||||||
location location = current_location();
|
|
||||||
|
|
||||||
switch (get()) {
|
|
||||||
case '"': {
|
|
||||||
std::size_t begin = ++m_cursor;
|
|
||||||
while (m_cursor < m_content.size() && m_content[m_cursor] != '"')
|
|
||||||
++m_cursor;
|
|
||||||
if (m_cursor >= m_content.size()) {
|
|
||||||
return token_r(token_error{ current_location(), token_error_t::UnexpectedEof, "before enclosing '\"'" });
|
|
||||||
}
|
|
||||||
++m_cursor;
|
|
||||||
|
|
||||||
return { location, token_t::String, m_content.substr(begin, m_cursor - begin - 1) };
|
|
||||||
}
|
|
||||||
case std::char_traits<char>::eof(): return token_r(token_error{ current_location(), token_error_t::EndOfFile });
|
|
||||||
default: {
|
|
||||||
if (std::isdigit(get()) != 0) {
|
|
||||||
integer_token integer = 0;
|
|
||||||
integer_token max = std::numeric_limits<integer_token>::max();
|
|
||||||
integer_token upperBound = max / 10;
|
|
||||||
|
|
||||||
std::size_t start = m_cursor;
|
|
||||||
while (std::isdigit(get()) != 0) {
|
|
||||||
integer_token digit = get() - '0';
|
|
||||||
|
|
||||||
if (integer > upperBound || integer == upperBound && (integer - upperBound + digit) > (max % 10)) {
|
|
||||||
while (std::isdigit(get()) != 0)
|
|
||||||
++m_cursor;
|
|
||||||
return token_r(token_error{ location,
|
|
||||||
token_error_t::IntegerOverflow,
|
|
||||||
std::string(m_content.substr(start, m_cursor - start)) });
|
|
||||||
}
|
|
||||||
integer *= 10;
|
|
||||||
integer += digit;
|
|
||||||
++m_cursor;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { location, integer };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (std::isalnum(get()) != 0 || get() == '_') {
|
|
||||||
std::size_t start = m_cursor++;
|
|
||||||
while (std::isalnum(get()) != 0 || get() == '_')
|
|
||||||
next();
|
|
||||||
|
|
||||||
std::string_view value = m_content.substr(start, m_cursor - start);
|
|
||||||
|
|
||||||
static std::unordered_map<std::string_view, keyword_token> s_keywords = {
|
|
||||||
{ "func", keyword_token::Func },
|
|
||||||
{ "return", keyword_token::Return },
|
|
||||||
{ "if", keyword_token::If },
|
|
||||||
{ "else", keyword_token::Else },
|
|
||||||
};
|
|
||||||
|
|
||||||
if (auto it = s_keywords.find(value); it != s_keywords.end()) return { location, it->second };
|
|
||||||
return { location, token_t::Identifier, value };
|
|
||||||
}
|
|
||||||
|
|
||||||
struct compare {
|
|
||||||
bool operator()(const std::string_view& lhs, const std::string_view& rhs) const {
|
|
||||||
if (lhs.size() != rhs.size()) return lhs.size() > rhs.size();
|
|
||||||
return lhs < rhs;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static std::map<std::string_view, token_t, compare> s_tokens = {
|
|
||||||
{ "(", token_t::LParen },
|
|
||||||
{ ")", token_t::RParen },
|
|
||||||
{ "{", token_t::LBrace },
|
|
||||||
{ "}", token_t::RBrace },
|
|
||||||
{ "[", token_t::LBracket },
|
|
||||||
{ "]", token_t::RBracket },
|
|
||||||
{ ";", token_t::Semicolon },
|
|
||||||
{ ":", token_t::Colon },
|
|
||||||
{ ",", token_t::Comma },
|
|
||||||
{ ".", token_t::Dot },
|
|
||||||
{ "+", token_t::Plus },
|
|
||||||
{ "-", token_t::Minus },
|
|
||||||
{ "*", token_t::Star },
|
|
||||||
{ "/", token_t::Slash },
|
|
||||||
{ "%", token_t::Percent },
|
|
||||||
{ "++", token_t::DPlus },
|
|
||||||
{ "--", token_t::DMinus },
|
|
||||||
{ "=", token_t::Eq },
|
|
||||||
{ "+=", token_t::PlusEq },
|
|
||||||
{ "-=", token_t::MinusEq },
|
|
||||||
{ "*=", token_t::StarEq },
|
|
||||||
{ "/=", token_t::SlashEq },
|
|
||||||
{ "%=", token_t::PercentEq },
|
|
||||||
{ "==", token_t::DEq },
|
|
||||||
{ "!=", token_t::NotEq },
|
|
||||||
{ "<", token_t::LessThan },
|
|
||||||
{ ">", token_t::GreaterThan },
|
|
||||||
{ "<=", token_t::LessEq },
|
|
||||||
{ ">=", token_t::GreaterEq },
|
|
||||||
};
|
|
||||||
|
|
||||||
token_t type = token_t::None;
|
|
||||||
std::size_t length = 1;
|
|
||||||
while (m_cursor + length <= m_content.size()) {
|
|
||||||
auto it = s_tokens.find(m_content.substr(m_cursor, length));
|
|
||||||
if (it == s_tokens.end()) break;
|
|
||||||
type = it->second;
|
|
||||||
++length;
|
|
||||||
}
|
|
||||||
if (type != token_t::None) {
|
|
||||||
m_cursor += length - 1;
|
|
||||||
return { location, type };
|
|
||||||
}
|
|
||||||
|
|
||||||
return token_r(
|
|
||||||
token_error{ location, token_error_t::UnexpectedCharacter, std::string(m_content.substr(m_cursor, 1)) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void lexer::next() {
|
|
||||||
if (m_cursor >= m_content.size()) return;
|
|
||||||
char ch = get();
|
|
||||||
++m_cursor;
|
|
||||||
if (ch == '\n') {
|
|
||||||
++m_row;
|
|
||||||
m_lineStart = m_cursor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char lexer::get(std::size_t offset) const {
|
|
||||||
if (m_cursor + offset < m_content.size()) return m_content[m_cursor + offset];
|
|
||||||
return std::char_traits<char>::eof();
|
|
||||||
}
|
|
||||||
|
|
||||||
void lexer::skip_spaces() {
|
|
||||||
while (std::isspace(get()) != 0)
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
location lexer::current_location() {
|
|
||||||
return { m_filename, m_row, m_cursor - m_lineStart };
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace furc::front
|
|
||||||
@@ -1,441 +0,0 @@
|
|||||||
#include "furc/front/parser.hpp"
|
|
||||||
|
|
||||||
#include "furc/ast/declaration.hpp" // IWYU pragma: keep
|
|
||||||
#include "furc/ast/expression.hpp" // IWYU pragma: keep
|
|
||||||
#include "furc/ast/literal.hpp" // IWYU pragma: keep
|
|
||||||
#include "furc/ast/program.hpp" // IWYU pragma: keep
|
|
||||||
#include "furc/ast/statement.hpp" // IWYU pragma: keep
|
|
||||||
|
|
||||||
#include <fstream>
|
|
||||||
#include <iostream>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
namespace furc::front {
|
|
||||||
|
|
||||||
using namespace std::string_literals;
|
|
||||||
|
|
||||||
parser::parser(std::string_view filename, std::string_view content)
|
|
||||||
: m_filename(filename), m_content(content), m_lexer(m_filename, m_content) {}
|
|
||||||
|
|
||||||
parser::parser(std::string_view filename)
|
|
||||||
: m_filename(filename) {
|
|
||||||
std::ifstream file(m_filename, std::ios_base::binary | std::ios_base::ate);
|
|
||||||
if (!file.is_open()) throw std::runtime_error("failed to open file "s.append(m_filename));
|
|
||||||
std::streampos size = file.tellg();
|
|
||||||
file.seekg(0);
|
|
||||||
|
|
||||||
m_content.resize(size);
|
|
||||||
file.read(m_content.data(), size);
|
|
||||||
m_lexer = { filename, m_content };
|
|
||||||
}
|
|
||||||
|
|
||||||
ast::program_node_r parser::parse() & {
|
|
||||||
auto program = m_arena.allocate_shared<ast::program_node>(location{ m_filename });
|
|
||||||
|
|
||||||
while (peek_token().has_value()) {
|
|
||||||
auto decl = parse_declaration();
|
|
||||||
if (decl.has_error()) {
|
|
||||||
program = nullptr;
|
|
||||||
} else if (program != nullptr) {
|
|
||||||
program->push(std::move(decl.value()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return program != nullptr ? std::move(program) : ast::program_node_r(ast::error{ location{ m_filename } });
|
|
||||||
}
|
|
||||||
|
|
||||||
ast::declaration_node_r parser::parse_declaration() {
|
|
||||||
const auto& first = peek_token();
|
|
||||||
switch (first->type) {
|
|
||||||
case token_t::Keyword: {
|
|
||||||
auto first = next_token();
|
|
||||||
switch ((*first)->keyword) {
|
|
||||||
case keyword_token::Func: {
|
|
||||||
auto name = eat_token(token_t::Identifier);
|
|
||||||
if (name.has_error()) return ast::declaration_node_r(ast::error{ name.error().location });
|
|
||||||
|
|
||||||
auto tok = eat_token(token_t::LParen);
|
|
||||||
if (tok.has_error()) return ast::declaration_node_r(ast::error{ tok.error().location });
|
|
||||||
tok = eat_token(token_t::RParen);
|
|
||||||
if (tok.has_error()) return ast::declaration_node_r(ast::error{ tok.error().location });
|
|
||||||
|
|
||||||
const auto& peek = peek_token();
|
|
||||||
if (peek.has_error()) return ast::declaration_node_r(ast::error{ peek.error().location });
|
|
||||||
switch (peek->type) {
|
|
||||||
case token_t::LBrace: {
|
|
||||||
ast::body_r body = parse_body();
|
|
||||||
if (body.has_error()) return ast::declaration_node_r(ast::error{ body.error().location });
|
|
||||||
return m_arena.allocate_shared<ast::function_definition_node>(first->location,
|
|
||||||
name->value.string,
|
|
||||||
std::move(body.value()));
|
|
||||||
}
|
|
||||||
case token_t::Semicolon: {
|
|
||||||
m_peekBuffer.clear();
|
|
||||||
return m_arena.allocate_shared<ast::function_declaration_node>(first->location, name->value.string);
|
|
||||||
}
|
|
||||||
default: return ast::declaration_node_r(ast::error{ tok->location });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default: return ast::declaration_node_r(ast::error{ first->location });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case token_t::None:
|
|
||||||
case token_t::Identifier:
|
|
||||||
case token_t::Integer:
|
|
||||||
case token_t::LParen:
|
|
||||||
case token_t::RParen:
|
|
||||||
case token_t::LBrace:
|
|
||||||
case token_t::RBrace:
|
|
||||||
case token_t::LBracket:
|
|
||||||
case token_t::RBracket:
|
|
||||||
case token_t::Semicolon:
|
|
||||||
case token_t::Colon:
|
|
||||||
default: {
|
|
||||||
return ast::declaration_node_r(ast::error{ first->location });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ast::statement_node_r parser::parse_statement() {
|
|
||||||
const auto& tok = peek_token();
|
|
||||||
if (tok.has_error()) return ast::statement_node_r(ast::error{ tok.error().location });
|
|
||||||
auto location = tok->location;
|
|
||||||
switch (tok->type) {
|
|
||||||
case token_t::Keyword: {
|
|
||||||
switch (tok->value.keyword) {
|
|
||||||
case keyword_token::Return: {
|
|
||||||
auto tok = next_token();
|
|
||||||
if (peek_token()->type == token_t::Semicolon) {
|
|
||||||
next_token();
|
|
||||||
return m_arena.allocate_shared<ast::return_statement_node>(location);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto value = parse_expression();
|
|
||||||
auto err = eat_token(token_t::Semicolon);
|
|
||||||
if (err.has_error()) return ast::statement_node_r(ast::error{ err.error().location });
|
|
||||||
return m_arena.allocate_shared<ast::return_statement_node>(location, std::move(value.value()));
|
|
||||||
}
|
|
||||||
case keyword_token::If: {
|
|
||||||
auto tok = next_token();
|
|
||||||
auto err = eat_token(token_t::LParen);
|
|
||||||
if (err.has_error()) return ast::statement_node_r(ast::error{ err.error().location });
|
|
||||||
|
|
||||||
auto cond = parse_expression();
|
|
||||||
|
|
||||||
err = eat_token(token_t::RParen);
|
|
||||||
if (err.has_error()) return ast::statement_node_r(ast::error{ err.error().location });
|
|
||||||
|
|
||||||
auto then = parse_statement();
|
|
||||||
if (then.has_error()) return ast::statement_node_r(ast::error{ then.error().location });
|
|
||||||
|
|
||||||
if (peek_token().has_value() && peek_token()->type == token_t::Keyword &&
|
|
||||||
peek_token()->value.keyword == keyword_token::Else) {
|
|
||||||
next_token();
|
|
||||||
|
|
||||||
auto elseBody = parse_statement();
|
|
||||||
if (elseBody.has_error()) return ast::statement_node_r(ast::error{ elseBody.error().location });
|
|
||||||
return m_arena.allocate_shared<ast::if_statement_node>(location,
|
|
||||||
std::move(cond.value()),
|
|
||||||
std::move(then.value()),
|
|
||||||
std::move(elseBody.value()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return m_arena.allocate_shared<ast::if_statement_node>(location,
|
|
||||||
std::move(cond.value()),
|
|
||||||
std::move(then.value()));
|
|
||||||
}
|
|
||||||
case keyword_token::None:
|
|
||||||
case keyword_token::Func:
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case token_t::LBrace: {
|
|
||||||
auto body = parse_body();
|
|
||||||
if (body.has_error()) return ast::statement_node_r(ast::error{ body.error().location });
|
|
||||||
return m_arena.allocate_shared<ast::compound_statement_node>(location, std::move(body.value()));
|
|
||||||
}
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto declaration = parse_declaration();
|
|
||||||
if (declaration.has_value()) return std::move(*declaration);
|
|
||||||
auto expression = parse_expression();
|
|
||||||
if (expression.has_value()) {
|
|
||||||
auto semi = eat_token(token_t::Semicolon);
|
|
||||||
if (semi.has_error()) return ast::statement_node_r(ast::error{ semi.error().location });
|
|
||||||
return std::move(*expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto token = next_token();
|
|
||||||
return ast::statement_node_r(ast::error{ token->location });
|
|
||||||
}
|
|
||||||
|
|
||||||
ast::expression_node_r parser::parse_expression(std::uint32_t precedence) {
|
|
||||||
auto expr = parse_expression_unary(precedence);
|
|
||||||
if (expr.has_error()) {
|
|
||||||
return ast::expression_node_r(ast::error{ expr.error().location });
|
|
||||||
}
|
|
||||||
return parse_expression_rhs(std::move(expr.value()), precedence);
|
|
||||||
}
|
|
||||||
|
|
||||||
ast::expression_node_r parser::parse_expression_primary() {
|
|
||||||
const auto& tok = peek_token();
|
|
||||||
switch (tok->type) {
|
|
||||||
case token_t::Identifier: {
|
|
||||||
auto tok = next_token();
|
|
||||||
if (tok.has_error()) return ast::expression_node_r(ast::error{ tok.error().location });
|
|
||||||
return m_arena.allocate_shared<ast::var_read_expression_node>(tok->location, (*tok)->string);
|
|
||||||
}
|
|
||||||
case token_t::LParen: {
|
|
||||||
auto tok = next_token();
|
|
||||||
auto node = parse_expression();
|
|
||||||
auto err = eat_token(token_t::RParen);
|
|
||||||
if (err.has_error()) return ast::expression_node_r(ast::error{ err.error().location });
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
case token_t::String: {
|
|
||||||
auto tok = next_token();
|
|
||||||
if (tok.has_error()) return ast::expression_node_r(ast::error{ tok.error().location });
|
|
||||||
return m_arena.allocate_shared<ast::string_literal_node>(tok->location, (*tok)->string);
|
|
||||||
}
|
|
||||||
case token_t::Integer: {
|
|
||||||
auto tok = next_token();
|
|
||||||
if (tok.has_error()) return ast::expression_node_r(ast::error{ tok.error().location });
|
|
||||||
return m_arena.allocate_shared<ast::integer_literal_node>(tok->location, (*tok)->integer);
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return ast::expression_node_r(ast::error{ tok->location });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct unaryop_info {
|
|
||||||
ast::unaryop_expression_node_t type;
|
|
||||||
std::uint32_t precedence;
|
|
||||||
};
|
|
||||||
|
|
||||||
ast::expression_node_r parser::parse_expression_unary(std::uint32_t precedence) {
|
|
||||||
static std::unordered_map<token_t, unaryop_info> s_prefixes = {
|
|
||||||
{ token_t::Plus, unaryop_info{ ast::unaryop_expression_node_t::Positive, 3 } },
|
|
||||||
{ token_t::Minus, unaryop_info{ ast::unaryop_expression_node_t::Negative, 3 } },
|
|
||||||
{ token_t::DPlus, unaryop_info{ ast::unaryop_expression_node_t::PrefixIncrement, 2 } },
|
|
||||||
{ token_t::DMinus, unaryop_info{ ast::unaryop_expression_node_t::PrefixDecrement, 2 } },
|
|
||||||
};
|
|
||||||
|
|
||||||
std::shared_ptr<ast::unary_op_expression_node> result;
|
|
||||||
while (true) {
|
|
||||||
auto it = s_prefixes.find(peek_token()->type);
|
|
||||||
if (it == s_prefixes.end()) break;
|
|
||||||
auto current = it->second;
|
|
||||||
if (current.precedence >= precedence) break;
|
|
||||||
auto token = next_token();
|
|
||||||
|
|
||||||
ast::expression_node_p expression;
|
|
||||||
|
|
||||||
auto nextIt = s_prefixes.find(peek_token()->type);
|
|
||||||
if (nextIt != s_prefixes.end()) {
|
|
||||||
auto next = nextIt->second;
|
|
||||||
auto expr = parse_expression_unary(current.precedence + 1);
|
|
||||||
if (expr.has_error()) {
|
|
||||||
return ast::expression_node_r(ast::error{ expr.error().location });
|
|
||||||
}
|
|
||||||
expression = std::move(std::move(expr.value()));
|
|
||||||
}
|
|
||||||
|
|
||||||
result = m_arena.allocate_shared<ast::unary_op_expression_node>(token->location,
|
|
||||||
current.type,
|
|
||||||
std::move(expression));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result == nullptr) return parse_expression_primary();
|
|
||||||
if (result->get_node() == nullptr) {
|
|
||||||
auto expr = parse_expression_primary();
|
|
||||||
if (expr.has_error()) {
|
|
||||||
return ast::expression_node_r(ast::error{ expr.error().location });
|
|
||||||
}
|
|
||||||
result->set_node(std::move(std::move(expr.value())));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class associativity {
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class rhsop_info_t {
|
|
||||||
Unaryop,
|
|
||||||
Binop,
|
|
||||||
Assignment,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct rhsop_info {
|
|
||||||
rhsop_info_t type;
|
|
||||||
std::uint32_t precedence;
|
|
||||||
associativity associativity;
|
|
||||||
union {
|
|
||||||
ast::unaryop_expression_node_t unary;
|
|
||||||
ast::binop_expression_node_t binary;
|
|
||||||
ast::binop_expression_node_t assignment;
|
|
||||||
};
|
|
||||||
|
|
||||||
static rhsop_info create(ast::unaryop_expression_node_t type, std::uint32_t precedence) {
|
|
||||||
rhsop_info info{};
|
|
||||||
info.type = rhsop_info_t::Unaryop;
|
|
||||||
info.precedence = precedence;
|
|
||||||
info.associativity = associativity::Left;
|
|
||||||
info.unary = type;
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
static rhsop_info create(ast::binop_expression_node_t type,
|
|
||||||
std::uint32_t precedence,
|
|
||||||
enum associativity associativity) {
|
|
||||||
rhsop_info info{};
|
|
||||||
info.type = rhsop_info_t::Binop;
|
|
||||||
info.precedence = precedence;
|
|
||||||
info.associativity = associativity;
|
|
||||||
info.binary = type;
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
static rhsop_info create(ast::binop_expression_node_t compound = ast::binop_expression_node_t::None) {
|
|
||||||
rhsop_info info{};
|
|
||||||
info.type = rhsop_info_t::Assignment;
|
|
||||||
info.precedence = 14;
|
|
||||||
info.associativity = associativity::Right;
|
|
||||||
info.assignment = compound;
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ast::expression_node_r parser::parse_expression_rhs(ast::expression_node_p&& init, std::uint32_t precedence) {
|
|
||||||
static std::unordered_map<token_t, rhsop_info> s_rhsops = {
|
|
||||||
{ token_t::Plus, rhsop_info::create(ast::binop_expression_node_t::Add, 5, associativity::Left) },
|
|
||||||
{ token_t::Minus, rhsop_info::create(ast::binop_expression_node_t::Sub, 5, associativity::Left) },
|
|
||||||
{ token_t::Star, rhsop_info::create(ast::binop_expression_node_t::Mul, 4, associativity::Left) },
|
|
||||||
{ token_t::Slash, rhsop_info::create(ast::binop_expression_node_t::Div, 4, associativity::Left) },
|
|
||||||
{ token_t::Percent, rhsop_info::create(ast::binop_expression_node_t::Mod, 5, associativity::Left) },
|
|
||||||
{ token_t::DPlus, rhsop_info::create(ast::unaryop_expression_node_t::PostfixIncrement, 1) },
|
|
||||||
{ token_t::DMinus, rhsop_info::create(ast::unaryop_expression_node_t::PostfixDecrement, 1) },
|
|
||||||
{ token_t::DMinus, rhsop_info::create(ast::unaryop_expression_node_t::PostfixDecrement, 1) },
|
|
||||||
{ token_t::Eq, rhsop_info::create() },
|
|
||||||
{ token_t::PlusEq, rhsop_info::create(ast::binop_expression_node_t::Add) },
|
|
||||||
{ token_t::MinusEq, rhsop_info::create(ast::binop_expression_node_t::Sub) },
|
|
||||||
{ token_t::StarEq, rhsop_info::create(ast::binop_expression_node_t::Mul) },
|
|
||||||
{ token_t::SlashEq, rhsop_info::create(ast::binop_expression_node_t::Div) },
|
|
||||||
{ token_t::PercentEq, rhsop_info::create(ast::binop_expression_node_t::Mod) },
|
|
||||||
{ token_t::DEq, rhsop_info::create(ast::binop_expression_node_t::Equal, 10, associativity::Left) },
|
|
||||||
{ token_t::NotEq, rhsop_info::create(ast::binop_expression_node_t::NotEqual, 10, associativity::Left) },
|
|
||||||
{ token_t::LessThan, rhsop_info::create(ast::binop_expression_node_t::LessThan, 9, associativity::Left) },
|
|
||||||
{ token_t::GreaterThan, rhsop_info::create(ast::binop_expression_node_t::GreaterThan, 9, associativity::Left) },
|
|
||||||
{ token_t::LessEq, rhsop_info::create(ast::binop_expression_node_t::LessEqual, 9, associativity::Left) },
|
|
||||||
{ token_t::GreaterEq, rhsop_info::create(ast::binop_expression_node_t::GreaterEqual, 9, associativity::Left) },
|
|
||||||
};
|
|
||||||
|
|
||||||
ast::expression_node_p lhs = std::move(init);
|
|
||||||
while (peek_token().has_value()) {
|
|
||||||
auto it = s_rhsops.find(peek_token()->type);
|
|
||||||
if (it == s_rhsops.end()) return lhs;
|
|
||||||
|
|
||||||
rhsop_info current = it->second;
|
|
||||||
if (current.precedence >= precedence) return lhs;
|
|
||||||
auto opToken = next_token();
|
|
||||||
|
|
||||||
ast::expression_node_p rhs;
|
|
||||||
if (current.type != rhsop_info_t::Unaryop) {
|
|
||||||
auto expr = parse_expression_unary(current.precedence + 1); // unary prefix is always right-associative
|
|
||||||
if (expr.has_error()) {
|
|
||||||
return ast::expression_node_r(ast::error{ expr.error().location });
|
|
||||||
}
|
|
||||||
rhs = std::move(expr.value());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto nextIt = s_rhsops.find(peek_token()->type);
|
|
||||||
if (nextIt != s_rhsops.end()) {
|
|
||||||
rhsop_info next = nextIt->second;
|
|
||||||
|
|
||||||
auto expr = std::move(parse_expression_rhs(std::move(rhs),
|
|
||||||
current.precedence + static_cast<std::uint32_t>(current.associativity == associativity::Right)));
|
|
||||||
if (expr.has_error()) {
|
|
||||||
return ast::expression_node_r(ast::error{ expr.error().location });
|
|
||||||
}
|
|
||||||
if (current.type != rhsop_info_t::Unaryop) {
|
|
||||||
rhs = std::move(expr.value());
|
|
||||||
} else {
|
|
||||||
lhs = std::move(expr.value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (current.type) {
|
|
||||||
case rhsop_info_t::Unaryop:
|
|
||||||
lhs = m_arena.allocate_shared<ast::unary_op_expression_node>(opToken->location,
|
|
||||||
current.unary,
|
|
||||||
std::move(lhs));
|
|
||||||
break;
|
|
||||||
case rhsop_info_t::Binop:
|
|
||||||
lhs = m_arena.allocate_shared<ast::binary_op_expression_node>(opToken->location,
|
|
||||||
current.binary,
|
|
||||||
std::move(lhs),
|
|
||||||
std::move(rhs));
|
|
||||||
break;
|
|
||||||
case rhsop_info_t::Assignment:
|
|
||||||
lhs = m_arena.allocate_shared<ast::var_assign_expression_node>(opToken->location,
|
|
||||||
current.assignment,
|
|
||||||
std::move(lhs),
|
|
||||||
std::move(rhs));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return lhs;
|
|
||||||
}
|
|
||||||
|
|
||||||
ast::body_r parser::parse_body() {
|
|
||||||
ast::body body;
|
|
||||||
|
|
||||||
auto begin = eat_token(token_t::LBrace);
|
|
||||||
if (begin.has_error()) return ast::body_r(ast::error{ begin.error().location });
|
|
||||||
body.begin = begin->location;
|
|
||||||
|
|
||||||
while (!peek_token().has_error() && peek_token()->type != token_t::None && peek_token()->type != token_t::RBrace) {
|
|
||||||
body.statements.push_back(parse_statement());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto end = eat_token(token_t::RBrace);
|
|
||||||
if (end.has_error()) return ast::body_r(ast::error{ end.error().location });
|
|
||||||
body.end = end->location;
|
|
||||||
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
|
|
||||||
token_r parser::next_token() {
|
|
||||||
if (!m_peekBuffer.empty()) {
|
|
||||||
auto token = std::move(m_peekBuffer.back());
|
|
||||||
m_peekBuffer.pop_back();
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
return m_lexer.next_token();
|
|
||||||
}
|
|
||||||
|
|
||||||
const token_r& parser::peek_token() {
|
|
||||||
if (m_peekBuffer.empty()) {
|
|
||||||
auto token = m_lexer.next_token();
|
|
||||||
return m_peekBuffer.emplace_back(std::move(token));
|
|
||||||
}
|
|
||||||
return m_peekBuffer.front();
|
|
||||||
}
|
|
||||||
|
|
||||||
token_r parser::eat_token(token_t type) {
|
|
||||||
auto token = next_token();
|
|
||||||
if (token.has_error()) return token;
|
|
||||||
if (token->type != type) {
|
|
||||||
if (token->type == token_t::None)
|
|
||||||
return token_r(token_error{ token->location, token_error_t::UnexpectedToken, ", expected " + type });
|
|
||||||
return token_r(
|
|
||||||
token_error{ token->location, token_error_t::UnexpectedToken, ""s + token->type + ", expected " + type });
|
|
||||||
}
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace furc::front
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
#ifndef LIBFURC
|
|
||||||
|
|
||||||
#include "furc/ast/program.hpp"
|
|
||||||
#include "furc/front/ir_generator.hpp"
|
|
||||||
#include "furc/front/parser.hpp"
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
int main(void) {
|
|
||||||
try {
|
|
||||||
std::string programStr = R"(
|
|
||||||
func main() {
|
|
||||||
x = 5;
|
|
||||||
x -= 3;
|
|
||||||
if (x < 3) {
|
|
||||||
y = x * 2;
|
|
||||||
w = y;
|
|
||||||
} else {
|
|
||||||
y = x - 3;
|
|
||||||
}
|
|
||||||
w = x - y;
|
|
||||||
z = x + y;
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
furc::front::parser parser("<TEMP>", programStr);
|
|
||||||
furc::front::ir_generator generator;
|
|
||||||
|
|
||||||
auto programResult = parser.parse();
|
|
||||||
if (programResult.has_error()) {
|
|
||||||
std::cerr << programResult.error() << '\n';
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
const auto& program = *programResult;
|
|
||||||
program->accept(generator);
|
|
||||||
|
|
||||||
auto module = std::move(generator.move_module());
|
|
||||||
std::cout << "Generated IR:\n";
|
|
||||||
for (const auto& function : module.functions()) {
|
|
||||||
std::cout << function->name() << ":\n";
|
|
||||||
furlang::ir::block_index blockIndex = 0;
|
|
||||||
for (const auto& block : function->blocks()) {
|
|
||||||
std::cout << " # block " << blockIndex++ << '\n';
|
|
||||||
for (const auto& instruction : block->instructions()) {
|
|
||||||
std::cout << " " << *instruction << '\n';
|
|
||||||
}
|
|
||||||
std::cout << " " << *block->exit() << '\n';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
} catch (...) {
|
|
||||||
std::cerr << "Caught an exception in main!\n";
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // LIBFURC
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
// NOLINTBEGIN(readability-identifier-naming)
|
|
||||||
|
|
||||||
#include "furc/front/lexer.hpp"
|
|
||||||
|
|
||||||
#include "gtest/gtest.h"
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
using namespace furc::front;
|
|
||||||
using namespace std::string_view_literals;
|
|
||||||
using namespace std::string_literals;
|
|
||||||
|
|
||||||
using lexer_case = std::pair<std::string, std::vector<token_r>>;
|
|
||||||
|
|
||||||
class LexerTestingFixture : public testing::TestWithParam<lexer_case> {};
|
|
||||||
|
|
||||||
TEST_P(LexerTestingFixture, LexerTest) {
|
|
||||||
auto [code, expected] = GetParam();
|
|
||||||
|
|
||||||
lexer lexer("<TEMP>", code);
|
|
||||||
auto it = expected.begin();
|
|
||||||
while (it != expected.end()) {
|
|
||||||
const auto& expected = *it++;
|
|
||||||
|
|
||||||
EXPECT_EQ(lexer.next_token(), expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto eof = std::move(lexer.next_token());
|
|
||||||
ASSERT_TRUE(eof.has_error());
|
|
||||||
ASSERT_EQ(eof.error().type, token_error_t::EndOfFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
furc::location loc(size_t col, size_t line) {
|
|
||||||
return furc::location{ "<TEMP>", line, col };
|
|
||||||
}
|
|
||||||
|
|
||||||
INSTANTIATE_TEST_SUITE_P(EmptyTests,
|
|
||||||
LexerTestingFixture,
|
|
||||||
testing::Values(lexer_case("", {}), lexer_case(" ", {}), lexer_case("\t", {}), lexer_case("\n", {})));
|
|
||||||
|
|
||||||
INSTANTIATE_TEST_SUITE_P(Comments,
|
|
||||||
LexerTestingFixture,
|
|
||||||
testing::Values(lexer_case("(/** skibidi **/func{//)\n}",
|
|
||||||
{ { loc(0, 0), token_t::LParen },
|
|
||||||
{ loc(16, 0), keyword_token::Func },
|
|
||||||
{ loc(20, 0), token_t::LBrace },
|
|
||||||
{ loc(0, 1), token_t::RBrace } })));
|
|
||||||
|
|
||||||
INSTANTIATE_TEST_SUITE_P(Integers,
|
|
||||||
LexerTestingFixture,
|
|
||||||
testing::Values(lexer_case("67 6\n7", { { loc(0, 0), 67 }, { loc(3, 0), 6 }, { loc(0, 1), 7 } }),
|
|
||||||
lexer_case("18446744073709551615\n18446744073709551616",
|
|
||||||
{ { loc(0, 0), 18446744073709551615ULL },
|
|
||||||
token_r(
|
|
||||||
token_error{ loc(0, 1), token_error_t::IntegerOverflow, std::string("18446744073709551616") }) })));
|
|
||||||
|
|
||||||
INSTANTIATE_TEST_SUITE_P(Tokens,
|
|
||||||
LexerTestingFixture,
|
|
||||||
testing::Values(lexer_case("()\n\t\t{\n}[\"shto-to\"]; :,.main return func",
|
|
||||||
{ { loc(0, 0), token_t::LParen },
|
|
||||||
{ loc(1, 0), token_t::RParen },
|
|
||||||
{ loc(2, 1), token_t::LBrace },
|
|
||||||
{ loc(0, 2), token_t::RBrace },
|
|
||||||
{ loc(1, 2), token_t::LBracket },
|
|
||||||
{ loc(2, 2), token_t::String, "shto-to"sv },
|
|
||||||
{ loc(10, 2), token_t::RBracket },
|
|
||||||
{ loc(11, 2), token_t::Semicolon },
|
|
||||||
{ loc(15, 2), token_t::Colon },
|
|
||||||
{ loc(16, 2), token_t::Comma },
|
|
||||||
{ loc(17, 2), token_t::Dot },
|
|
||||||
{ loc(18, 2), token_t::Identifier, "main"sv },
|
|
||||||
{ loc(23, 2), keyword_token::Return },
|
|
||||||
{ loc(30, 2), keyword_token::Func } })));
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
// NOLINTEND(readability-identifier-naming)
|
|
||||||
@@ -1,274 +0,0 @@
|
|||||||
#include "furc/front/parser.hpp"
|
|
||||||
|
|
||||||
#include "furc/ast/declaration.hpp" // IWYU pragma: keep
|
|
||||||
#include "furc/ast/expression.hpp" // IWYU pragma: keep
|
|
||||||
#include "furc/ast/literal.hpp" // IWYU pragma: keep
|
|
||||||
#include "furc/ast/program.hpp" // IWYU pragma: keep
|
|
||||||
#include "furc/ast/statement.hpp" // IWYU pragma: keep
|
|
||||||
|
|
||||||
#include "gtest/gtest.h" // IWYU pragma: keep
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
using namespace furc::front;
|
|
||||||
using namespace furc::ast;
|
|
||||||
using namespace std::string_view_literals;
|
|
||||||
|
|
||||||
// TEST(Parser, EmptyFunctions) {
|
|
||||||
// parser parser("<TEMP>", "func main() {}\nfunc foo();");
|
|
||||||
// auto program = parser.parse();
|
|
||||||
// EXPECT_TRUE(program.present());
|
|
||||||
// EXPECT_EQ(program->declarations().size(), 2);
|
|
||||||
// {
|
|
||||||
// auto first = program->declarations()[0];
|
|
||||||
// EXPECT_TRUE(first.present());
|
|
||||||
// EXPECT_EQ(first->declaration_type(), declaration_node_t::FuncDef);
|
|
||||||
// function_definition_node_h funcDef = first;
|
|
||||||
// EXPECT_EQ(funcDef->name()->string, "main");
|
|
||||||
// EXPECT_EQ(funcDef->body()->statements.size(), 0);
|
|
||||||
// }
|
|
||||||
// {
|
|
||||||
// auto second = program->declarations()[1];
|
|
||||||
// EXPECT_TRUE(second.present());
|
|
||||||
// EXPECT_EQ(second->declaration_type(), declaration_node_t::Func);
|
|
||||||
// function_declaration_node_h funcDecl = second;
|
|
||||||
// EXPECT_EQ(funcDecl->name()->string, "foo");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #define EXPECT_INTLIT(expr, integer) \
|
|
||||||
// do { \
|
|
||||||
// EXPECT_EQ((expr)->expression_type(), expression_node_t::Literal); \
|
|
||||||
// literal_node_h literal = (expr); \
|
|
||||||
// EXPECT_EQ(literal->literal_type(), literal_node_t::Integer); \
|
|
||||||
// integer_literal_node_h intLit = literal; \
|
|
||||||
// EXPECT_EQ(intLit->value(), integer_token((integer))); \
|
|
||||||
// } while (0)
|
|
||||||
|
|
||||||
// TEST(Parser, Literals) {
|
|
||||||
// parser parser("<TEMP>", R"(
|
|
||||||
// func test1() { return 67; }
|
|
||||||
// func test2() { return "uwu"; }
|
|
||||||
// )");
|
|
||||||
// auto program = parser.parse();
|
|
||||||
// EXPECT_TRUE(program.present());
|
|
||||||
// EXPECT_EQ(program->declarations().size(), 2);
|
|
||||||
// {
|
|
||||||
// auto test1 = program->declarations()[0];
|
|
||||||
// EXPECT_TRUE(test1.present());
|
|
||||||
// EXPECT_EQ(test1->declaration_type(), declaration_node_t::FuncDef);
|
|
||||||
// function_definition_node_h funcDef = test1;
|
|
||||||
// EXPECT_EQ(funcDef->name()->string, "test1");
|
|
||||||
// EXPECT_EQ(funcDef->body()->statements.size(), 1);
|
|
||||||
// return_statement_node_h ret = funcDef->body()->statements[0];
|
|
||||||
// EXPECT_INTLIT(ret->value(), 67);
|
|
||||||
// }
|
|
||||||
// {
|
|
||||||
// auto test2 = program->declarations()[1];
|
|
||||||
// EXPECT_TRUE(test2.present());
|
|
||||||
// EXPECT_EQ(test2->declaration_type(), declaration_node_t::FuncDef);
|
|
||||||
// function_definition_node_h funcDecl = test2;
|
|
||||||
// EXPECT_EQ(funcDecl->name()->string, "test2");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #define EXPECT_VARREAD(expr, varname) \
|
|
||||||
// do { \
|
|
||||||
// EXPECT_EQ((expr)->expression_type(), expression_node_t::VarRead); \
|
|
||||||
// var_read_expression_node_h varRead = (expr); \
|
|
||||||
// EXPECT_EQ(varRead->get_name(), (varname)); \
|
|
||||||
// } while (0)
|
|
||||||
|
|
||||||
// // TODO: Use arena (I am too exhausted rn to do it)
|
|
||||||
// TEST(Parser, OperatorPrecedence_AddMul) {
|
|
||||||
// parser parser("<TEMP>", "func main() { return 1 + 2 * 3; }");
|
|
||||||
// auto program = parser.parse();
|
|
||||||
// EXPECT_TRUE(program.present());
|
|
||||||
// EXPECT_EQ(program->declarations().size(), 1);
|
|
||||||
// auto func = program->declarations()[0];
|
|
||||||
// EXPECT_TRUE(func.present());
|
|
||||||
// EXPECT_EQ(func->declaration_type(), declaration_node_t::FuncDef);
|
|
||||||
// function_definition_node_h funcDef = func;
|
|
||||||
// EXPECT_EQ(funcDef->name()->string, "main");
|
|
||||||
// EXPECT_EQ(funcDef->body()->statements.size(), 1);
|
|
||||||
// return_statement_node_h ret = funcDef->body()->statements[0];
|
|
||||||
|
|
||||||
// auto retVal = ret->value();
|
|
||||||
// EXPECT_TRUE(retVal.present());
|
|
||||||
|
|
||||||
// EXPECT_EQ(retVal->expression_type(), expression_node_t::Binop);
|
|
||||||
// binop_expression_node_h add = retVal;
|
|
||||||
// EXPECT_EQ(add->type(), binop_expression_node_t::Add);
|
|
||||||
// EXPECT_INTLIT(add->lhs(), 1);
|
|
||||||
|
|
||||||
// EXPECT_EQ(add->rhs()->expression_type(), expression_node_t::Binop);
|
|
||||||
// binop_expression_node_h mul = add->rhs();
|
|
||||||
// EXPECT_EQ(mul->type(), binop_expression_node_t::Mul);
|
|
||||||
// EXPECT_INTLIT(mul->lhs(), 2);
|
|
||||||
// EXPECT_INTLIT(mul->rhs(), 3);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// TEST(Parser, OperatorPrecedence_Complex) {
|
|
||||||
// parser parser("<TEMP>", "func main() { return 1 + 2 * 3 - 4 / 2; }");
|
|
||||||
// auto program = parser.parse();
|
|
||||||
// EXPECT_TRUE(program.present());
|
|
||||||
// EXPECT_EQ(program->declarations().size(), 1);
|
|
||||||
// auto func = program->declarations()[0];
|
|
||||||
// EXPECT_TRUE(func.present());
|
|
||||||
// EXPECT_EQ(func->declaration_type(), declaration_node_t::FuncDef);
|
|
||||||
// function_definition_node_h funcDef = func;
|
|
||||||
// EXPECT_EQ(funcDef->name()->string, "main");
|
|
||||||
// EXPECT_EQ(funcDef->body()->statements.size(), 1);
|
|
||||||
// return_statement_node_h ret = funcDef->body()->statements[0];
|
|
||||||
|
|
||||||
// auto retVal = ret->value();
|
|
||||||
// EXPECT_TRUE(retVal.present());
|
|
||||||
|
|
||||||
// EXPECT_EQ(retVal->expression_type(), expression_node_t::Binop);
|
|
||||||
// binop_expression_node_h sub = retVal;
|
|
||||||
// EXPECT_EQ(sub->type(), binop_expression_node_t::Sub);
|
|
||||||
|
|
||||||
// EXPECT_EQ(sub->lhs()->expression_type(), expression_node_t::Binop);
|
|
||||||
// binop_expression_node_h add = sub->lhs();
|
|
||||||
// EXPECT_EQ(add->type(), binop_expression_node_t::Add);
|
|
||||||
// EXPECT_INTLIT(add->lhs(), 1);
|
|
||||||
|
|
||||||
// EXPECT_EQ(add->rhs()->expression_type(), expression_node_t::Binop);
|
|
||||||
// binop_expression_node_h mul = add->rhs();
|
|
||||||
// EXPECT_EQ(mul->type(), binop_expression_node_t::Mul);
|
|
||||||
// EXPECT_INTLIT(mul->lhs(), 2);
|
|
||||||
// EXPECT_INTLIT(mul->rhs(), 3);
|
|
||||||
|
|
||||||
// EXPECT_EQ(sub->rhs()->expression_type(), expression_node_t::Binop);
|
|
||||||
// binop_expression_node_h div = sub->rhs();
|
|
||||||
// EXPECT_EQ(div->type(), binop_expression_node_t::Div);
|
|
||||||
// EXPECT_INTLIT(div->lhs(), 4);
|
|
||||||
// EXPECT_INTLIT(div->rhs(), 2);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// TEST(Parser, UnaryOperator_Simple) {
|
|
||||||
// parser parser("<TEMP>", "func main() { return -5; }");
|
|
||||||
// auto program = parser.parse();
|
|
||||||
// EXPECT_TRUE(program.present());
|
|
||||||
// EXPECT_EQ(program->declarations().size(), 1);
|
|
||||||
// auto func = program->declarations()[0];
|
|
||||||
// EXPECT_TRUE(func.present());
|
|
||||||
// EXPECT_EQ(func->declaration_type(), declaration_node_t::FuncDef);
|
|
||||||
// function_definition_node_h funcDef = func;
|
|
||||||
// EXPECT_EQ(funcDef->name()->string, "main");
|
|
||||||
// EXPECT_EQ(funcDef->body()->statements.size(), 1);
|
|
||||||
// return_statement_node_h ret = funcDef->body()->statements[0];
|
|
||||||
|
|
||||||
// auto retVal = ret->value();
|
|
||||||
// EXPECT_TRUE(retVal.present());
|
|
||||||
|
|
||||||
// EXPECT_EQ(retVal->expression_type(), expression_node_t::Unaryop);
|
|
||||||
// unaryop_expression_node_h neg = retVal;
|
|
||||||
// EXPECT_EQ(neg->type(), unaryop_expression_node_t::Negative);
|
|
||||||
// EXPECT_INTLIT(neg->get_node(), 5);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// TEST(Parser, UnaryOperator_PrePost) {
|
|
||||||
// parser parser("<TEMP>", "func main() { return --5++; }");
|
|
||||||
// auto program = parser.parse();
|
|
||||||
// EXPECT_TRUE(program.present());
|
|
||||||
// EXPECT_EQ(program->declarations().size(), 1);
|
|
||||||
// auto func = program->declarations()[0];
|
|
||||||
// EXPECT_TRUE(func.present());
|
|
||||||
// EXPECT_EQ(func->declaration_type(), declaration_node_t::FuncDef);
|
|
||||||
// function_definition_node_h funcDef = func;
|
|
||||||
// EXPECT_EQ(funcDef->name()->string, "main");
|
|
||||||
// EXPECT_EQ(funcDef->body()->statements.size(), 1);
|
|
||||||
// return_statement_node_h ret = funcDef->body()->statements[0];
|
|
||||||
|
|
||||||
// auto retVal = ret->value();
|
|
||||||
// EXPECT_TRUE(retVal.present());
|
|
||||||
|
|
||||||
// EXPECT_EQ(retVal->expression_type(), expression_node_t::Unaryop);
|
|
||||||
// unaryop_expression_node_h inc = retVal;
|
|
||||||
// EXPECT_EQ(inc->type(), unaryop_expression_node_t::PostfixIncrement);
|
|
||||||
|
|
||||||
// EXPECT_EQ(inc->get_node()->expression_type(), expression_node_t::Unaryop);
|
|
||||||
// unaryop_expression_node_h dec = inc->get_node();
|
|
||||||
// EXPECT_INTLIT(dec->get_node(), 5);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// TEST(Parser, Paren) {
|
|
||||||
// parser parser("<TEMP>", "func main() { return --(x++); }");
|
|
||||||
// auto program = parser.parse();
|
|
||||||
// EXPECT_TRUE(program.present());
|
|
||||||
// EXPECT_EQ(program->declarations().size(), 1);
|
|
||||||
// auto func = program->declarations()[0];
|
|
||||||
// EXPECT_TRUE(func.present());
|
|
||||||
// EXPECT_EQ(func->declaration_type(), declaration_node_t::FuncDef);
|
|
||||||
// function_definition_node_h funcDef = func;
|
|
||||||
// EXPECT_EQ(funcDef->name()->string, "main");
|
|
||||||
// EXPECT_EQ(funcDef->body()->statements.size(), 1);
|
|
||||||
// return_statement_node_h ret = funcDef->body()->statements[0];
|
|
||||||
|
|
||||||
// auto retVal = ret->value();
|
|
||||||
// EXPECT_TRUE(retVal.present());
|
|
||||||
|
|
||||||
// EXPECT_EQ(retVal->expression_type(), expression_node_t::Unaryop);
|
|
||||||
// unaryop_expression_node_h dec = retVal;
|
|
||||||
// EXPECT_EQ(dec->type(), unaryop_expression_node_t::PrefixDecrement);
|
|
||||||
|
|
||||||
// EXPECT_EQ(dec->get_node()->expression_type(), expression_node_t::Unaryop);
|
|
||||||
// unaryop_expression_node_h inc = dec->get_node();
|
|
||||||
// EXPECT_EQ(inc->type(), unaryop_expression_node_t::PostfixIncrement);
|
|
||||||
// EXPECT_VARREAD(inc->get_node(), "x"sv);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// TEST(Parser, Assignment) {
|
|
||||||
// parser parser("<TEMP>", "func main() { x = 10; }");
|
|
||||||
// auto program = parser.parse();
|
|
||||||
// EXPECT_TRUE(program.present());
|
|
||||||
// EXPECT_EQ(program->declarations().size(), 1);
|
|
||||||
// auto func = program->declarations()[0];
|
|
||||||
// EXPECT_TRUE(func.present());
|
|
||||||
// EXPECT_EQ(func->declaration_type(), declaration_node_t::FuncDef);
|
|
||||||
// function_definition_node_h funcDef = func;
|
|
||||||
// EXPECT_EQ(funcDef->name()->string, "main");
|
|
||||||
// EXPECT_EQ(funcDef->body()->statements.size(), 1);
|
|
||||||
|
|
||||||
// EXPECT_EQ(funcDef->body()->statements[0]->statement_type(), statement_node_t::Expression);
|
|
||||||
// expression_node_h expr = funcDef->body()->statements[0];
|
|
||||||
|
|
||||||
// EXPECT_EQ(expr->expression_type(), expression_node_t::VarAssign);
|
|
||||||
// var_assign_expression_node_h assign = expr;
|
|
||||||
// EXPECT_EQ(assign->compound(), binop_expression_node_t::None);
|
|
||||||
|
|
||||||
// EXPECT_VARREAD(assign->lhs(), "x"sv);
|
|
||||||
|
|
||||||
// expression_node_h rhs = assign->rhs();
|
|
||||||
// EXPECT_EQ(rhs->expression_type(), expression_node_t::Literal);
|
|
||||||
// EXPECT_INTLIT(rhs, 10);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// TEST(Parser, CompoundAssignment) {
|
|
||||||
// parser parser("<TEMP>", "func main() { x += 10; }");
|
|
||||||
// auto program = parser.parse();
|
|
||||||
// EXPECT_TRUE(program.present());
|
|
||||||
// EXPECT_EQ(program->declarations().size(), 1);
|
|
||||||
// auto func = program->declarations()[0];
|
|
||||||
// EXPECT_TRUE(func.present());
|
|
||||||
// EXPECT_EQ(func->declaration_type(), declaration_node_t::FuncDef);
|
|
||||||
// function_definition_node_h funcDef = func;
|
|
||||||
// EXPECT_EQ(funcDef->name()->string, "main");
|
|
||||||
// EXPECT_EQ(funcDef->body()->statements.size(), 1);
|
|
||||||
|
|
||||||
// EXPECT_EQ(funcDef->body()->statements[0]->statement_type(), statement_node_t::Expression);
|
|
||||||
// expression_node_h expr = funcDef->body()->statements[0];
|
|
||||||
|
|
||||||
// EXPECT_EQ(expr->expression_type(), expression_node_t::VarAssign);
|
|
||||||
// var_assign_expression_node_h assign = expr;
|
|
||||||
// EXPECT_EQ(assign->compound(), binop_expression_node_t::Add);
|
|
||||||
|
|
||||||
// EXPECT_VARREAD(assign->lhs(), "x"sv);
|
|
||||||
|
|
||||||
// expression_node_h rhs = assign->rhs();
|
|
||||||
// EXPECT_EQ(rhs->expression_type(), expression_node_t::Literal);
|
|
||||||
// EXPECT_INTLIT(rhs, 10);
|
|
||||||
// }
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
file(GLOB_RECURSE FURLANG_SRCS "src/**.cpp")
|
|
||||||
file(GLOB_RECURSE FURLANG_HDRS "include/**.hpp")
|
|
||||||
add_library(furlang STATIC ${FURLANG_SRCS} ${FURLANG_HDRS})
|
|
||||||
target_include_directories(furlang PUBLIC include/)
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
#ifndef FURLANG_IR_BLOCK_HPP
|
|
||||||
#define FURLANG_IR_BLOCK_HPP
|
|
||||||
|
|
||||||
#include "furlang/ir/instruction.hpp"
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace furlang {
|
|
||||||
namespace ir {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Basic block.
|
|
||||||
*
|
|
||||||
* A basic block of IR instructions. https://en.wikipedia.org/wiki/Basic_block
|
|
||||||
*/
|
|
||||||
class block {
|
|
||||||
public:
|
|
||||||
using value_type = std::unique_ptr<instruction>; /**< Value type */
|
|
||||||
public:
|
|
||||||
block() = default;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Emplaces a new instruction.
|
|
||||||
*
|
|
||||||
* Emplaces a new instruction, if exit instruction hasn't been emplaced in this block yet.
|
|
||||||
*
|
|
||||||
* @tparam T Type of the instruction to emplace.
|
|
||||||
* @param args Arguments to call the constructor with.
|
|
||||||
* @return true if the instruction has been emplaced successfully.
|
|
||||||
*/
|
|
||||||
template <typename T, typename... Args, typename = std::enable_if_t<std::is_base_of_v<instruction, T>>>
|
|
||||||
bool emplace(Args&&... args) {
|
|
||||||
if (has_exit()) return false;
|
|
||||||
auto instr = std::make_unique<T>(std::forward<Args>(args)...);
|
|
||||||
if (is_exit_instruction(instr->type())) {
|
|
||||||
m_exit = std::move(instr);
|
|
||||||
} else {
|
|
||||||
m_instructions.emplace_back(std::move(instr));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this block's instructions.
|
|
||||||
*
|
|
||||||
* @return The instructions.
|
|
||||||
*/
|
|
||||||
std::vector<value_type>& instructions() { return m_instructions; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this block's instructions.
|
|
||||||
*
|
|
||||||
* @return The instructions.
|
|
||||||
*/
|
|
||||||
const std::vector<value_type>& instructions() const { return m_instructions; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Checks whether an exit instruction has been emplaced in this block yet.
|
|
||||||
*
|
|
||||||
* @return true if the exit instruction has been emplaced.
|
|
||||||
*/
|
|
||||||
bool has_exit() const { return m_exit != nullptr; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this block's exit instruction.
|
|
||||||
*
|
|
||||||
* @return The exit instruction.
|
|
||||||
*/
|
|
||||||
value_type& exit() { return m_exit; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this block's exit instruction.
|
|
||||||
*
|
|
||||||
* @return The exit instruction.
|
|
||||||
*/
|
|
||||||
const value_type& exit() const { return m_exit; }
|
|
||||||
private:
|
|
||||||
std::vector<value_type> m_instructions;
|
|
||||||
value_type m_exit;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ir
|
|
||||||
} // namespace furlang
|
|
||||||
|
|
||||||
#endif // FURLANG_IR_BLOCK_HPP
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
#ifndef FURLANG_IR_FUNCTION_HPP
|
|
||||||
#define FURLANG_IR_FUNCTION_HPP
|
|
||||||
|
|
||||||
#include "furlang/ir/block.hpp"
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace furlang {
|
|
||||||
namespace ir {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief IR function.
|
|
||||||
*
|
|
||||||
* Consists of a name and blocks.
|
|
||||||
* @see block
|
|
||||||
*/
|
|
||||||
class function {
|
|
||||||
public:
|
|
||||||
using value_type = std::shared_ptr<block>; /**< Value type. */
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Construct a new IR function.
|
|
||||||
*
|
|
||||||
* @param name Name to copy.
|
|
||||||
*/
|
|
||||||
function(const std::string& name)
|
|
||||||
: m_name(name) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Construct a new IR function.
|
|
||||||
*
|
|
||||||
* @param name Name to move.
|
|
||||||
*/
|
|
||||||
function(std::string&& name)
|
|
||||||
: m_name(std::move(name)) {}
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns this function's name.
|
|
||||||
*
|
|
||||||
* @return The name.
|
|
||||||
*/
|
|
||||||
const std::string& name() const { return m_name; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Pushes and returns a new IR block.
|
|
||||||
*
|
|
||||||
* @return The new IR block.
|
|
||||||
*/
|
|
||||||
value_type push() { return m_blocks.emplace_back(std::make_shared<block>()); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this function's IR blocks.
|
|
||||||
*
|
|
||||||
* @return The IR blocks.
|
|
||||||
*/
|
|
||||||
const std::vector<value_type>& blocks() const { return m_blocks; }
|
|
||||||
private:
|
|
||||||
std::string m_name;
|
|
||||||
std::vector<value_type> m_blocks;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ir
|
|
||||||
} // namespace furlang
|
|
||||||
|
|
||||||
#endif // FURLANG_IR_FUNCTION_HPP
|
|
||||||
@@ -1,464 +0,0 @@
|
|||||||
#ifndef FURLANG_IR_INSTRUCTION_HPP
|
|
||||||
#define FURLANG_IR_INSTRUCTION_HPP
|
|
||||||
|
|
||||||
#include "furlang/ir/operand.hpp"
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <optional>
|
|
||||||
#include <ostream>
|
|
||||||
|
|
||||||
namespace furlang {
|
|
||||||
namespace ir {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief IR instruction type.
|
|
||||||
*/
|
|
||||||
enum class instruction_t {
|
|
||||||
Alloca, /**< Unused */
|
|
||||||
Assign, /**< Assign */
|
|
||||||
BinaryOp, /**< Binary operation */
|
|
||||||
Call, /**< Call */
|
|
||||||
Branch, /**< Branch */
|
|
||||||
BranchCond, /**< Conditional branch */
|
|
||||||
Return, /**< Return */
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Checks if an instruction type exits.
|
|
||||||
*
|
|
||||||
* @param type Instruction type.
|
|
||||||
* @return true if the instruction type exits.
|
|
||||||
*/
|
|
||||||
static inline bool is_exit_instruction(instruction_t type) {
|
|
||||||
switch (type) {
|
|
||||||
case instruction_t::Branch:
|
|
||||||
case instruction_t::BranchCond:
|
|
||||||
case instruction_t::Return: return true;
|
|
||||||
default: return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief IR instruction
|
|
||||||
*/
|
|
||||||
class instruction {
|
|
||||||
public:
|
|
||||||
instruction() = default;
|
|
||||||
virtual ~instruction() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor
|
|
||||||
*/
|
|
||||||
instruction(instruction&&) = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor
|
|
||||||
*/
|
|
||||||
instruction& operator=(instruction&&) = default;
|
|
||||||
|
|
||||||
instruction(const instruction&) = delete;
|
|
||||||
|
|
||||||
instruction& operator=(const instruction&) = delete;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns this instruction's type.
|
|
||||||
*
|
|
||||||
* @return The type.
|
|
||||||
*/
|
|
||||||
virtual instruction_t type() const = 0;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Prints an instruction to an output stream.
|
|
||||||
*
|
|
||||||
* Equivalent to calling instruction.print(os).
|
|
||||||
*
|
|
||||||
* @param os Output stream.
|
|
||||||
* @param instruction Instruction to print.
|
|
||||||
* @return The output stream.
|
|
||||||
*/
|
|
||||||
friend std::ostream& operator<<(std::ostream& os, const instruction& instruction) { return instruction.print(os); }
|
|
||||||
protected:
|
|
||||||
/**
|
|
||||||
* @brief Prints this instruction to an output stream.
|
|
||||||
*
|
|
||||||
* @param os Output stream.
|
|
||||||
* @return The output stream.
|
|
||||||
*/
|
|
||||||
virtual std::ostream& print(std::ostream& os) const = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Alloca instruction
|
|
||||||
*/
|
|
||||||
class alloca_instruction final : public instruction {
|
|
||||||
public:
|
|
||||||
alloca_instruction() {}
|
|
||||||
|
|
||||||
~alloca_instruction() override = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor
|
|
||||||
*/
|
|
||||||
alloca_instruction(alloca_instruction&&) = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor
|
|
||||||
*/
|
|
||||||
alloca_instruction& operator=(alloca_instruction&&) = default;
|
|
||||||
|
|
||||||
alloca_instruction(const alloca_instruction&) = delete;
|
|
||||||
|
|
||||||
alloca_instruction& operator=(const alloca_instruction&) = delete;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns this instruction's type.
|
|
||||||
*
|
|
||||||
* @return instruction_t::Alloca.
|
|
||||||
*/
|
|
||||||
instruction_t type() const override { return instruction_t::Alloca; }
|
|
||||||
protected:
|
|
||||||
std::ostream& print(std::ostream& os) const override { return os << "alloca"; }
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Assign instruction
|
|
||||||
*/
|
|
||||||
class assign_instruction final : public instruction {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Construct a new assign instruction.
|
|
||||||
*
|
|
||||||
* @param src Source operand.
|
|
||||||
* @param dst Destination operand.
|
|
||||||
*/
|
|
||||||
assign_instruction(operand&& src, operand&& dst)
|
|
||||||
: m_source(std::move(src)), m_destination(std::move(dst)) {}
|
|
||||||
|
|
||||||
~assign_instruction() override = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor
|
|
||||||
*/
|
|
||||||
assign_instruction(assign_instruction&&) = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor
|
|
||||||
*/
|
|
||||||
assign_instruction& operator=(assign_instruction&&) = default;
|
|
||||||
|
|
||||||
assign_instruction(const assign_instruction&) = delete;
|
|
||||||
|
|
||||||
assign_instruction& operator=(const assign_instruction&) = delete;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns this instruction's type.
|
|
||||||
*
|
|
||||||
* @return instruction_t::Assign.
|
|
||||||
*/
|
|
||||||
instruction_t type() const override { return instruction_t::Assign; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this instruction's source.
|
|
||||||
*
|
|
||||||
* @return The source.
|
|
||||||
*/
|
|
||||||
const operand& source() const { return m_source; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this instruction's destination.
|
|
||||||
*
|
|
||||||
* @return The destination.
|
|
||||||
*/
|
|
||||||
const operand& destination() const { return m_destination; }
|
|
||||||
private:
|
|
||||||
operand m_source;
|
|
||||||
operand m_destination;
|
|
||||||
protected:
|
|
||||||
std::ostream& print(std::ostream& os) const override {
|
|
||||||
return os << "assign " << m_source << ", " << m_destination;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Binary operation instruction type
|
|
||||||
*/
|
|
||||||
enum class binary_op_instruction_t {
|
|
||||||
Add, /**< Addition */
|
|
||||||
Sub, /**< Subtraction */
|
|
||||||
Mul, /**< Multiplication */
|
|
||||||
Div, /**< Division */
|
|
||||||
Mod, /**< Modulo */
|
|
||||||
|
|
||||||
Eq, /**< Equal */
|
|
||||||
NotEq, /**< Not equal */
|
|
||||||
LessThan, /**< Less than */
|
|
||||||
GreaterThan, /**< Greater than */
|
|
||||||
LessEq, /**< Less or equal */
|
|
||||||
GreaterEq, /**< Greater or equal */
|
|
||||||
};
|
|
||||||
|
|
||||||
static inline std::ostream& operator<<(std::ostream& os, binary_op_instruction_t type) {
|
|
||||||
switch (type) {
|
|
||||||
case binary_op_instruction_t::Add: return os << '+';
|
|
||||||
case binary_op_instruction_t::Sub: return os << '-';
|
|
||||||
case binary_op_instruction_t::Mul: return os << '*';
|
|
||||||
case binary_op_instruction_t::Div: return os << '/';
|
|
||||||
case binary_op_instruction_t::Mod: return os << '%';
|
|
||||||
case binary_op_instruction_t::Eq: return os << "==";
|
|
||||||
case binary_op_instruction_t::NotEq: return os << "!=";
|
|
||||||
case binary_op_instruction_t::LessThan: return os << '<';
|
|
||||||
case binary_op_instruction_t::GreaterThan: return os << '>';
|
|
||||||
case binary_op_instruction_t::LessEq: return os << "<=";
|
|
||||||
case binary_op_instruction_t::GreaterEq: return os << ">=";
|
|
||||||
}
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Binary operation instruction
|
|
||||||
*/
|
|
||||||
class binary_op_instruction final : public instruction {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Construct a new binary operation instruction.
|
|
||||||
*
|
|
||||||
* @param type Operation type.
|
|
||||||
* @param lhs Left-hand-side operand.
|
|
||||||
* @param rhs Right-hand-side operand.
|
|
||||||
* @param dst Destination operand.
|
|
||||||
*/
|
|
||||||
binary_op_instruction(binary_op_instruction_t type, operand&& lhs, operand&& rhs, operand&& dst)
|
|
||||||
: m_type(type), m_lhs(std::move(lhs)), m_rhs(std::move(rhs)), m_dst(std::move(dst)) {}
|
|
||||||
|
|
||||||
~binary_op_instruction() override = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor
|
|
||||||
*/
|
|
||||||
binary_op_instruction(binary_op_instruction&&) = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor
|
|
||||||
*/
|
|
||||||
binary_op_instruction& operator=(binary_op_instruction&&) = default;
|
|
||||||
|
|
||||||
binary_op_instruction(const binary_op_instruction&) = delete;
|
|
||||||
|
|
||||||
binary_op_instruction& operator=(const binary_op_instruction&) = delete;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns this instruction's type.
|
|
||||||
*
|
|
||||||
* @return instruction_t::BinaryOp.
|
|
||||||
*/
|
|
||||||
instruction_t type() const override { return instruction_t::BinaryOp; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this instruction's operation type.
|
|
||||||
*
|
|
||||||
* @return The operation type.
|
|
||||||
*/
|
|
||||||
binary_op_instruction_t op_type() const { return m_type; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this instruction's left-hand-side operand.
|
|
||||||
*
|
|
||||||
* @return The operand.
|
|
||||||
*/
|
|
||||||
const operand& lhs() const { return m_lhs; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this instruction's right-hand-side operand.
|
|
||||||
*
|
|
||||||
* @return The operand.
|
|
||||||
*/
|
|
||||||
const operand& rhs() const { return m_rhs; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this instruction's destination operand.
|
|
||||||
*
|
|
||||||
* @return The operand.
|
|
||||||
*/
|
|
||||||
const operand& dst() const { return m_dst; }
|
|
||||||
private:
|
|
||||||
binary_op_instruction_t m_type;
|
|
||||||
operand m_lhs /**< Left-hand-side operand */;
|
|
||||||
operand m_rhs /**< Right-hand-side operand */;
|
|
||||||
operand m_dst /**< Destination operand */;
|
|
||||||
protected:
|
|
||||||
std::ostream& print(std::ostream& os) const override {
|
|
||||||
return os << "binop(" << m_type << ") " << m_lhs << ", " << m_rhs << ", " << m_dst;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
using block_index = std::uint64_t; /**< IR block index alias */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Branch instruction
|
|
||||||
*/
|
|
||||||
class branch_instruction final : public instruction {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Construct a new branch instruction.
|
|
||||||
*
|
|
||||||
* @param block Destination block index.
|
|
||||||
*/
|
|
||||||
branch_instruction(block_index block)
|
|
||||||
: m_block(block) {}
|
|
||||||
|
|
||||||
~branch_instruction() override = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
branch_instruction(branch_instruction&&) = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
branch_instruction& operator=(branch_instruction&&) = default;
|
|
||||||
|
|
||||||
branch_instruction(const branch_instruction&) = delete;
|
|
||||||
|
|
||||||
branch_instruction& operator=(const branch_instruction&) = delete;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns this instruction's type.
|
|
||||||
*
|
|
||||||
* @return instruction_t::Branch.
|
|
||||||
*/
|
|
||||||
instruction_t type() const override { return instruction_t::Branch; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this instruction's destination block index.
|
|
||||||
*
|
|
||||||
* @return The destination block index.
|
|
||||||
*/
|
|
||||||
block_index block() const { return m_block; }
|
|
||||||
private:
|
|
||||||
block_index m_block; /**< Destination block index. */
|
|
||||||
protected:
|
|
||||||
std::ostream& print(std::ostream& os) const override { return os << "branch #" << m_block; }
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Conditional branch instruction
|
|
||||||
*/
|
|
||||||
class branch_cond_instruction final : public instruction {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Construct a new conditional branch instruction.
|
|
||||||
*
|
|
||||||
* @param condition Condition operand.
|
|
||||||
* @param ifBlock Destination block index.
|
|
||||||
* @param elseBlock Else block index.
|
|
||||||
*/
|
|
||||||
branch_cond_instruction(operand&& condition, block_index ifBlock, block_index elseBlock)
|
|
||||||
: m_condition(std::move(condition)), m_ifBlock(ifBlock), m_elseBlock(elseBlock) {}
|
|
||||||
|
|
||||||
~branch_cond_instruction() override = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
branch_cond_instruction(branch_cond_instruction&&) = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
branch_cond_instruction& operator=(branch_cond_instruction&&) = default;
|
|
||||||
|
|
||||||
branch_cond_instruction(const branch_cond_instruction&) = delete;
|
|
||||||
|
|
||||||
branch_cond_instruction& operator=(const branch_cond_instruction&) = delete;
|
|
||||||
public:
|
|
||||||
instruction_t type() const override { return instruction_t::BranchCond; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this instruction's condition operand.
|
|
||||||
*
|
|
||||||
* @return The operand.
|
|
||||||
*/
|
|
||||||
const operand& condition() const { return m_condition; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this instruction's destination block index.
|
|
||||||
*
|
|
||||||
* @return The destination block index.
|
|
||||||
*/
|
|
||||||
block_index if_block() const { return m_ifBlock; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this instruction's else block index.
|
|
||||||
*
|
|
||||||
* @return The else block index.
|
|
||||||
*/
|
|
||||||
block_index else_block() const { return m_elseBlock; }
|
|
||||||
private:
|
|
||||||
operand m_condition /**< Condition operand. */;
|
|
||||||
block_index m_ifBlock /**< Destination block index. */;
|
|
||||||
block_index m_elseBlock /**< Else block index. */;
|
|
||||||
protected:
|
|
||||||
std::ostream& print(std::ostream& os) const override {
|
|
||||||
return os << "branch_cond " << m_condition << ", #" << m_ifBlock << ", #" << m_elseBlock;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Return instruction
|
|
||||||
*/
|
|
||||||
class return_instruction final : public instruction {
|
|
||||||
public:
|
|
||||||
return_instruction() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Construct a new return instruction.
|
|
||||||
*
|
|
||||||
* @param value Return value operand.
|
|
||||||
*/
|
|
||||||
return_instruction(operand&& value)
|
|
||||||
: m_value(std::move(value)) {}
|
|
||||||
|
|
||||||
~return_instruction() override = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
return_instruction(return_instruction&&) = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
return_instruction& operator=(return_instruction&&) = default;
|
|
||||||
|
|
||||||
return_instruction(const return_instruction&) = delete;
|
|
||||||
|
|
||||||
return_instruction& operator=(const return_instruction&) = delete;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns this instruction's type.
|
|
||||||
*
|
|
||||||
* @return instruction_t::Return.
|
|
||||||
*/
|
|
||||||
instruction_t type() const override { return instruction_t::Return; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this instruction's return value operand.
|
|
||||||
*
|
|
||||||
* @return The operand.
|
|
||||||
*/
|
|
||||||
const std::optional<operand>& value() const { return m_value; }
|
|
||||||
private:
|
|
||||||
std::optional<operand> m_value; /**< The return value operand. */
|
|
||||||
protected:
|
|
||||||
std::ostream& print(std::ostream& os) const override {
|
|
||||||
os << "return";
|
|
||||||
if (m_value.has_value()) os << ' ' << m_value.value();
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ir
|
|
||||||
} // namespace furlang
|
|
||||||
|
|
||||||
#endif // FURLANG_IR_INSTRUCTION_HPP
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
#ifndef FURLANG_IR_MODULE_HPP
|
|
||||||
#define FURLANG_IR_MODULE_HPP
|
|
||||||
|
|
||||||
#include "furlang/ir/function.hpp"
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace furlang {
|
|
||||||
namespace ir {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief IR module
|
|
||||||
*/
|
|
||||||
class module {
|
|
||||||
public:
|
|
||||||
using value_type = std::unique_ptr<function>; /**< Value type. */
|
|
||||||
public:
|
|
||||||
module() = default;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Pushes and returns a new IR function.
|
|
||||||
*
|
|
||||||
* @param args Arguments to call the constructor with.
|
|
||||||
* @return The new IR function.
|
|
||||||
*/
|
|
||||||
template <typename... Args>
|
|
||||||
value_type& push(Args&&... args) {
|
|
||||||
return m_functions.emplace_back(std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this module's functions.
|
|
||||||
*
|
|
||||||
* @return The functions.
|
|
||||||
*/
|
|
||||||
std::vector<value_type>& functions() { return m_functions; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this module's functions.
|
|
||||||
*
|
|
||||||
* @return The functions.
|
|
||||||
*/
|
|
||||||
const std::vector<value_type>& functions() const { return m_functions; }
|
|
||||||
private:
|
|
||||||
std::vector<value_type> m_functions;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ir
|
|
||||||
} // namespace furlang
|
|
||||||
|
|
||||||
#endif // FURLANG_IR_MODULE_HPP
|
|
||||||
@@ -1,231 +0,0 @@
|
|||||||
#ifndef FURLANG_IR_OPERAND_HPP
|
|
||||||
#define FURLANG_IR_OPERAND_HPP
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <ostream>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace furlang {
|
|
||||||
namespace ir {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Operand type
|
|
||||||
*/
|
|
||||||
enum class operand_t {
|
|
||||||
None, /**< None */
|
|
||||||
Register, /**< Register */
|
|
||||||
Variable, /**< Variable */
|
|
||||||
Integer, /**< Integer */
|
|
||||||
String, /**< String */
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Register operand alias.
|
|
||||||
* @see operand_t::Register
|
|
||||||
*/
|
|
||||||
using register_operand = std::uint32_t;
|
|
||||||
/**
|
|
||||||
* @brief Variable operand alias.
|
|
||||||
* @see operand_t::Variable
|
|
||||||
*/
|
|
||||||
using variable_operand = std::string;
|
|
||||||
/**
|
|
||||||
* @brief Integer operand alias.
|
|
||||||
* @see operand_t::Integer
|
|
||||||
*/
|
|
||||||
using integer_operand = std::uint64_t;
|
|
||||||
/**
|
|
||||||
* @brief String operand alias.
|
|
||||||
* @see operand_t::String
|
|
||||||
*/
|
|
||||||
using string_operand = std::string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief IR operand
|
|
||||||
*/
|
|
||||||
class operand {
|
|
||||||
public:
|
|
||||||
~operand() {
|
|
||||||
if (m_type == operand_t::String) {
|
|
||||||
m_value.string.~basic_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
operand(operand&& other) noexcept
|
|
||||||
: m_type(other.m_type) {
|
|
||||||
switch (m_type) {
|
|
||||||
case operand_t::None: break;
|
|
||||||
case operand_t::Register: {
|
|
||||||
m_value.reg = other.m_value.reg;
|
|
||||||
} break;
|
|
||||||
case operand_t::Variable: {
|
|
||||||
new (&m_value.variable) variable_operand(std::move(other.m_value.variable));
|
|
||||||
} break;
|
|
||||||
case operand_t::Integer: {
|
|
||||||
m_value.integer = other.m_value.integer;
|
|
||||||
} break;
|
|
||||||
case operand_t::String: {
|
|
||||||
new (&m_value.string) string_operand(std::move(other.m_value.string));
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
other.m_value.destroy(other.m_type);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
operand& operator=(operand&& other) noexcept {
|
|
||||||
if (this == &other) return *this;
|
|
||||||
m_type = other.m_type;
|
|
||||||
switch (m_type) {
|
|
||||||
case operand_t::None: break;
|
|
||||||
case operand_t::Register: {
|
|
||||||
m_value.reg = other.m_value.reg;
|
|
||||||
} break;
|
|
||||||
case operand_t::Variable: {
|
|
||||||
new (&m_value.variable) variable_operand(std::move(other.m_value.variable));
|
|
||||||
} break;
|
|
||||||
case operand_t::Integer: {
|
|
||||||
m_value.integer = other.m_value.integer;
|
|
||||||
} break;
|
|
||||||
case operand_t::String: {
|
|
||||||
new (&m_value.string) string_operand(std::move(other.m_value.string));
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
other.m_value.destroy(other.m_type);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
operand(const operand&) = delete;
|
|
||||||
operand& operator=(const operand&) = delete;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Construct a new register operand.
|
|
||||||
*
|
|
||||||
* @param value Value of the new register operand.
|
|
||||||
* @return The register operand.
|
|
||||||
*/
|
|
||||||
static operand new_reg(register_operand value) {
|
|
||||||
operand operand;
|
|
||||||
operand.m_type = operand_t::Register;
|
|
||||||
operand.m_value.reg = value;
|
|
||||||
return operand;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Construct a new variable operand.
|
|
||||||
*
|
|
||||||
* @param value Value of the new variable operand.
|
|
||||||
* @return The variable operand.
|
|
||||||
*/
|
|
||||||
template <typename T>
|
|
||||||
static operand new_variable(T&& value) {
|
|
||||||
operand operand;
|
|
||||||
operand.m_type = operand_t::Variable;
|
|
||||||
new (&operand.m_value.variable) variable_operand(std::forward<T>(value));
|
|
||||||
return operand;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Construct a new integer operand.
|
|
||||||
*
|
|
||||||
* @param value Value of the new integer operand.
|
|
||||||
* @return The integer operand.
|
|
||||||
*/
|
|
||||||
static operand new_integer(integer_operand value) {
|
|
||||||
operand operand;
|
|
||||||
operand.m_type = operand_t::Integer;
|
|
||||||
operand.m_value.integer = value;
|
|
||||||
return operand;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Construct a new string operand.
|
|
||||||
*
|
|
||||||
* @param value Value of the new string operand.
|
|
||||||
* @return The string operand.
|
|
||||||
*/
|
|
||||||
template <typename T>
|
|
||||||
static operand new_string(T&& value) {
|
|
||||||
operand operand;
|
|
||||||
operand.m_type = operand_t::String;
|
|
||||||
new (&operand.m_value.string) string_operand(std::forward<T>(value));
|
|
||||||
return operand;
|
|
||||||
}
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns this operand's type.
|
|
||||||
*
|
|
||||||
* @return The operand type.
|
|
||||||
*/
|
|
||||||
operand_t type() const { return m_type; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this operand's register value.
|
|
||||||
*
|
|
||||||
* @return The register value.
|
|
||||||
*/
|
|
||||||
register_operand reg() const { return m_value.reg; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this operand's integer value.
|
|
||||||
*
|
|
||||||
* @return The integer value.
|
|
||||||
*/
|
|
||||||
integer_operand integer() const { return m_value.integer; }
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Prints an operand to an output stream.
|
|
||||||
*
|
|
||||||
* @param os Output stream.
|
|
||||||
* @param operand Operand to print.
|
|
||||||
* @return The output stream.
|
|
||||||
*/
|
|
||||||
friend std::ostream& operator<<(std::ostream& os, const operand& operand) {
|
|
||||||
switch (operand.m_type) {
|
|
||||||
case operand_t::None: return os << "none";
|
|
||||||
case operand_t::Register: return os << '%' << operand.m_value.reg;
|
|
||||||
case operand_t::Variable: return os << operand.m_value.variable;
|
|
||||||
case operand_t::Integer: return os << operand.m_value.integer;
|
|
||||||
case operand_t::String: return os << '"' << operand.m_value.string << '"';
|
|
||||||
}
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
operand() = default;
|
|
||||||
private:
|
|
||||||
operand_t m_type = operand_t::None;
|
|
||||||
union value {
|
|
||||||
std::nullptr_t null = nullptr;
|
|
||||||
register_operand reg;
|
|
||||||
variable_operand variable;
|
|
||||||
integer_operand integer;
|
|
||||||
string_operand string;
|
|
||||||
|
|
||||||
void destroy(operand_t type) {
|
|
||||||
switch (type) {
|
|
||||||
case operand_t::String: {
|
|
||||||
string.~basic_string();
|
|
||||||
} break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
null = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
value() = default;
|
|
||||||
~value() {}
|
|
||||||
|
|
||||||
value(value&&) noexcept = delete;
|
|
||||||
value& operator=(value&&) noexcept = delete;
|
|
||||||
value(const value&) = delete;
|
|
||||||
value& operator=(const value&) = delete;
|
|
||||||
} m_value;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ir
|
|
||||||
} // namespace furlang
|
|
||||||
|
|
||||||
#endif // FURLANG_IR_OPERAND_HPP
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
#ifndef FURVM_CONSTANT_HPP
|
|
||||||
#define FURVM_CONSTANT_HPP
|
|
||||||
|
|
||||||
#include "furvm/fwd.hpp"
|
|
||||||
|
|
||||||
#include <exception>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
namespace furvm {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Bad constant access exception.
|
|
||||||
*/
|
|
||||||
class bad_constant_access : public std::exception {
|
|
||||||
public:
|
|
||||||
bad_constant_access() = default;
|
|
||||||
~bad_constant_access() override = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
bad_constant_access(bad_constant_access&&) noexcept = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
bad_constant_access& operator=(bad_constant_access&&) noexcept = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Copy constructor.
|
|
||||||
*/
|
|
||||||
bad_constant_access(const bad_constant_access&) = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Copy constructor.
|
|
||||||
*/
|
|
||||||
bad_constant_access& operator=(const bad_constant_access&) = default;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns a C-style string describing the cause of the error.
|
|
||||||
*
|
|
||||||
* @return The cause of the error.
|
|
||||||
*/
|
|
||||||
const char* what() const noexcept override { return "bad constant access"; }
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class constant_t : std::uint8_t {
|
|
||||||
String, /**< String constant. */
|
|
||||||
};
|
|
||||||
|
|
||||||
class constant {
|
|
||||||
public:
|
|
||||||
using string_type = std::string_view; /**< String constant type. */
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Construct a new string constant.
|
|
||||||
*
|
|
||||||
* @param string String.
|
|
||||||
*/
|
|
||||||
constant(string_type string)
|
|
||||||
: m_type(constant_t::String), m_value(string) {}
|
|
||||||
|
|
||||||
~constant() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
constant(constant&&) = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
constant& operator=(constant&&) = default;
|
|
||||||
|
|
||||||
constant(const constant&) = delete;
|
|
||||||
constant& operator=(const constant&) = delete;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns this constant's type.
|
|
||||||
* @see constant_t
|
|
||||||
*
|
|
||||||
* @return The constant type.
|
|
||||||
*/
|
|
||||||
constexpr constant_t type() const { return m_type; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this constant's string value.
|
|
||||||
* @throws bad_constant_access if this constant's type is not constant_t::String.
|
|
||||||
*
|
|
||||||
* @return The string value.
|
|
||||||
*/
|
|
||||||
constexpr string_type string() const {
|
|
||||||
require_type(constant_t::String);
|
|
||||||
return m_value.string;
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
void require_type(constant_t type) const {
|
|
||||||
if (m_type != type) throw bad_constant_access();
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
constant_t m_type{};
|
|
||||||
union value {
|
|
||||||
string_type string;
|
|
||||||
|
|
||||||
value(string_type sv)
|
|
||||||
: string(sv) {}
|
|
||||||
} m_value;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace furvm
|
|
||||||
|
|
||||||
#endif // FURVM_CONSTANT_HPP
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
#ifndef FURVM_CONTEXT_HPP
|
|
||||||
#define FURVM_CONTEXT_HPP
|
|
||||||
|
|
||||||
#include "furlang/arena.hpp"
|
|
||||||
#include "furvm/fwd.hpp"
|
|
||||||
#include "furvm/module.hpp"
|
|
||||||
|
|
||||||
#include <queue>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace furvm {
|
|
||||||
|
|
||||||
class context {
|
|
||||||
public:
|
|
||||||
friend class executor;
|
|
||||||
friend class thing;
|
|
||||||
public:
|
|
||||||
context();
|
|
||||||
~context() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
context(context&&) noexcept = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
context& operator=(context&&) noexcept = default;
|
|
||||||
|
|
||||||
context(const context&) = delete;
|
|
||||||
context& operator=(const context&) = delete;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Adds a module to this context.
|
|
||||||
*
|
|
||||||
* @param args Arguments to forward to module's constructor.
|
|
||||||
* @return An index to the emplaced module.
|
|
||||||
*/
|
|
||||||
template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<mod, module_handle, Args...>>>
|
|
||||||
constexpr const auto& emplace(Args&&... args) {
|
|
||||||
module_handle id = static_cast<module_handle>(m_modules.size());
|
|
||||||
return m_modules.emplace_back(std::make_unique<mod>(id, std::forward<Args>(args)...));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Erases a module from this context.
|
|
||||||
*
|
|
||||||
* @param index Index to the module.
|
|
||||||
* @return Old value.
|
|
||||||
*/
|
|
||||||
mod_p erase(module_handle index) {
|
|
||||||
if (index >= m_modules.size()) return nullptr;
|
|
||||||
return std::move(m_modules[index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns a module of this context.
|
|
||||||
*
|
|
||||||
* @param index Position of the module.
|
|
||||||
* @return The module.
|
|
||||||
*/
|
|
||||||
constexpr mod_p& operator[](module_handle index) { return m_modules[index]; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns a module of this context.
|
|
||||||
*
|
|
||||||
* @param index Position of the module.
|
|
||||||
* @return The module.
|
|
||||||
*/
|
|
||||||
constexpr const mod_p& operator[](module_handle index) const { return m_modules[index]; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns a module of this context.
|
|
||||||
*
|
|
||||||
* @param index Position of the module.
|
|
||||||
* @return The module.
|
|
||||||
*/
|
|
||||||
constexpr mod_p& at(module_handle index) { return m_modules.at(index); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns a module of this context.
|
|
||||||
*
|
|
||||||
* @param index Position of the module.
|
|
||||||
* @return The module.
|
|
||||||
*/
|
|
||||||
constexpr const mod_p& at(module_handle index) const { return m_modules.at(index); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns how many does this context have modules.
|
|
||||||
*
|
|
||||||
* @return The module count.
|
|
||||||
*/
|
|
||||||
constexpr size_t size() const { return m_modules.size(); }
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Removes unreferenced things from the thing list.
|
|
||||||
*/
|
|
||||||
void collect();
|
|
||||||
private:
|
|
||||||
std::vector<mod_p> m_modules;
|
|
||||||
std::vector<thing_p> m_things;
|
|
||||||
std::vector<executor_p> m_executors;
|
|
||||||
|
|
||||||
std::queue<thing_handle> m_deadThings;
|
|
||||||
std::vector<void*> m_deadThingData;
|
|
||||||
furlang::arena m_thingArena;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace furvm
|
|
||||||
|
|
||||||
#endif // FURVM_CONTEXT_HPP
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
#ifndef FURVM_EXCEPTIONS_HPP
|
|
||||||
#define FURVM_EXCEPTIONS_HPP
|
|
||||||
|
|
||||||
#include <exception>
|
|
||||||
|
|
||||||
namespace furvm {
|
|
||||||
|
|
||||||
class stack_underflow : public std::exception {
|
|
||||||
public:
|
|
||||||
stack_underflow() = default;
|
|
||||||
~stack_underflow() override = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
stack_underflow(stack_underflow&&) noexcept = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
stack_underflow& operator=(stack_underflow&&) noexcept = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Copy constructor.
|
|
||||||
*/
|
|
||||||
stack_underflow(const stack_underflow&) = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Copy constructor.
|
|
||||||
*/
|
|
||||||
stack_underflow& operator=(const stack_underflow&) = default;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns a C-style string describing the cause of the error.
|
|
||||||
*
|
|
||||||
* @return The cause of the error.
|
|
||||||
*/
|
|
||||||
const char* what() const noexcept override { return "stack underflow"; }
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace furvm
|
|
||||||
|
|
||||||
#endif // FURVM_EXCEPTIONS_HPP
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
#ifndef FURVM_EXECUTOR_HPP
|
|
||||||
#define FURVM_EXECUTOR_HPP
|
|
||||||
|
|
||||||
#include "furvm/fwd.hpp"
|
|
||||||
|
|
||||||
#include <stack>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace furvm {
|
|
||||||
|
|
||||||
enum class executor_flags : std::uint32_t {
|
|
||||||
Suspended = (1 << 0), /**< Execution suspended. */
|
|
||||||
Done = (1 << 1), /**< Execution is finished. */
|
|
||||||
};
|
|
||||||
|
|
||||||
static inline executor_flags operator|(executor_flags lhs, executor_flags rhs) {
|
|
||||||
return executor_flags(static_cast<std::uint32_t>(lhs) | static_cast<std::uint32_t>(rhs));
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline executor_flags operator&(executor_flags lhs, executor_flags rhs) {
|
|
||||||
return executor_flags(static_cast<std::uint32_t>(lhs) & static_cast<std::uint32_t>(rhs));
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline executor_flags operator~(executor_flags flags) {
|
|
||||||
return executor_flags(~static_cast<std::uint32_t>(flags));
|
|
||||||
}
|
|
||||||
|
|
||||||
class executor {
|
|
||||||
private:
|
|
||||||
/**
|
|
||||||
* @brief A private token for the private constructor.
|
|
||||||
*
|
|
||||||
* Also `egzekutor` in Polish translates to `executor` I think.
|
|
||||||
*/
|
|
||||||
struct egzekutor {
|
|
||||||
explicit egzekutor() = default;
|
|
||||||
};
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Executor frame.
|
|
||||||
*
|
|
||||||
* Call frame.
|
|
||||||
*/
|
|
||||||
struct frame {
|
|
||||||
mod_p mod; /**< Shared pointer to a module with the bytecode. */
|
|
||||||
std::size_t position; /**< Cursor to a current instruction in the bytecode. */
|
|
||||||
std::size_t stackBase; /**< Snapshot of the stack size before this frame. */
|
|
||||||
|
|
||||||
std::vector<thing_p> variables; /**< Frame variables. */
|
|
||||||
};
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Private constructor.
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
* @param context
|
|
||||||
*/
|
|
||||||
executor(egzekutor, executor_handle id, const context_p& context);
|
|
||||||
|
|
||||||
~executor();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
executor(executor&&) noexcept = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
executor& operator=(executor&&) noexcept = default;
|
|
||||||
|
|
||||||
executor(const executor&) = delete;
|
|
||||||
executor& operator=(const executor&) = delete;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns a new executor.
|
|
||||||
*
|
|
||||||
* @param context Furvm context.
|
|
||||||
* @return Shared pointer to the new executor.
|
|
||||||
*/
|
|
||||||
static executor_p create(const context_p& context);
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns an id of this executor.
|
|
||||||
*
|
|
||||||
* @return The id.
|
|
||||||
*/
|
|
||||||
executor_handle id() const { return m_id; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns flags of this executor.
|
|
||||||
*
|
|
||||||
* @return The flags.
|
|
||||||
*/
|
|
||||||
executor_flags flags() const { return m_flags; }
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Pushes a new frame.
|
|
||||||
*
|
|
||||||
* @param function Function.
|
|
||||||
*/
|
|
||||||
void push_frame(const function_p& function);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Pops the top frame.
|
|
||||||
*
|
|
||||||
* @return The popped frame.
|
|
||||||
*/
|
|
||||||
frame pop_frame();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns the top frame.
|
|
||||||
*
|
|
||||||
* @return The frame.
|
|
||||||
*/
|
|
||||||
frame frame() const;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Pushes a thing onto the stack.
|
|
||||||
*
|
|
||||||
* @param thing The thing to push.
|
|
||||||
*/
|
|
||||||
void push_thing(const thing_p& thing);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Pushes a thing onto the stack.
|
|
||||||
*
|
|
||||||
* @param thing The thing to push.
|
|
||||||
*/
|
|
||||||
void push_thing(thing_p&& thing);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Pops a thing from the stack.
|
|
||||||
*
|
|
||||||
* @return The popped thing.
|
|
||||||
*/
|
|
||||||
thing_p pop_thing();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns the top thing on the stack.
|
|
||||||
*
|
|
||||||
* @return The thing.
|
|
||||||
*/
|
|
||||||
thing_p thing() const;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Stores a thing in a variable.
|
|
||||||
*
|
|
||||||
* @param variable Variable to store the thing in.
|
|
||||||
* @param thing Thing to store.
|
|
||||||
*/
|
|
||||||
void store_thing(variable_t variable, const thing_p& thing);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Stores a thing in a variable.
|
|
||||||
*
|
|
||||||
* @param variable Variable to store the thing in.
|
|
||||||
* @param thing Thing to store.
|
|
||||||
*/
|
|
||||||
void store_thing(variable_t variable, thing_p&& thing);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns a thing stored in a variable.
|
|
||||||
*
|
|
||||||
* @param variable Variable where the thing is stored.
|
|
||||||
* @return The thing stored in the variable.
|
|
||||||
*/
|
|
||||||
thing_p load_thing(variable_t variable) const;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Executes next instruction.
|
|
||||||
*/
|
|
||||||
void step();
|
|
||||||
private:
|
|
||||||
executor_handle m_id;
|
|
||||||
executor_flags m_flags{}; // NOLINT(bugprone-invalid-enum-default-initialization)
|
|
||||||
context_p m_context;
|
|
||||||
|
|
||||||
std::stack<struct frame> m_frames;
|
|
||||||
std::stack<thing_p> m_stack;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace furvm
|
|
||||||
|
|
||||||
#endif // FURVM_EXECUTOR_HPP
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
#ifndef FURVM_FUNCTION_HPP
|
|
||||||
#define FURVM_FUNCTION_HPP
|
|
||||||
|
|
||||||
#include "furvm/fwd.hpp"
|
|
||||||
#include "furvm/module.hpp" // IWYU pragma: keep
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <stdexcept>
|
|
||||||
|
|
||||||
namespace furvm {
|
|
||||||
|
|
||||||
enum class function_t : std::uint8_t {
|
|
||||||
Normal = 0, /**< A normal bytecode function. */
|
|
||||||
Native, /**< A native function implemented through furvm API. */
|
|
||||||
Import, /**< A function imported from another module. */
|
|
||||||
};
|
|
||||||
|
|
||||||
using native_function = std::function<void(executor&)>;
|
|
||||||
|
|
||||||
class function {
|
|
||||||
private:
|
|
||||||
/**
|
|
||||||
* @brief A private token for the private constructor.
|
|
||||||
*
|
|
||||||
* Also `funkcja` in Polish translates to `function` from what I heard.
|
|
||||||
*/
|
|
||||||
struct funkcja {
|
|
||||||
explicit funkcja() = default;
|
|
||||||
};
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Private constructor.
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
* @param position
|
|
||||||
* @param mod
|
|
||||||
*/
|
|
||||||
function(funkcja, function_handle id, std::size_t position, const mod_p& mod);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Private constructor.
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
* @param native
|
|
||||||
* @param mod
|
|
||||||
*/
|
|
||||||
function(funkcja, function_handle id, const native_function& native, const mod_p& mod);
|
|
||||||
|
|
||||||
~function() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
function(function&&) noexcept;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
function& operator=(function&&) noexcept;
|
|
||||||
|
|
||||||
function(const function&) = delete;
|
|
||||||
function& operator=(const function&) = delete;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns a new function.
|
|
||||||
*
|
|
||||||
* @param mod Module.
|
|
||||||
* @param args Arguments to pass to the function constructor.
|
|
||||||
* @return The new function.
|
|
||||||
*/
|
|
||||||
template <typename... Args,
|
|
||||||
typename = std::enable_if_t<std::is_constructible_v<function, funkcja, function_handle, Args..., const mod_p&>>>
|
|
||||||
static function_p create(const mod_p& mod, Args&&... args) {
|
|
||||||
function_handle id = mod->m_functions.size();
|
|
||||||
|
|
||||||
auto func = std::make_shared<function>(funkcja{}, id, std::forward<Args>(args)..., mod);
|
|
||||||
mod->m_functions.emplace(mod->m_functions.begin() + id, func);
|
|
||||||
return std::move(func);
|
|
||||||
}
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns an id of this function.
|
|
||||||
*
|
|
||||||
* @return The id.
|
|
||||||
*/
|
|
||||||
constexpr function_handle id() const { return m_id; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns a type of this function.
|
|
||||||
*
|
|
||||||
* @return The type.
|
|
||||||
*/
|
|
||||||
constexpr function_t type() const { return m_type; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns a parent module of this function.
|
|
||||||
*
|
|
||||||
* @return A shared pointer to the module.
|
|
||||||
*/
|
|
||||||
const mod_p& mod() const { return m_module; }
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns a value for normal function.
|
|
||||||
*
|
|
||||||
* @return The value.
|
|
||||||
*/
|
|
||||||
std::size_t position() const {
|
|
||||||
if (m_type != function_t::Normal) throw std::runtime_error("function type mismatch");
|
|
||||||
return m_value.position;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns a value for native function.
|
|
||||||
*
|
|
||||||
* @return The value.
|
|
||||||
*/
|
|
||||||
const native_function& native() const {
|
|
||||||
if (m_type != function_t::Native) throw std::runtime_error("function type mismatch");
|
|
||||||
return m_value.native;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns a module of imported function.
|
|
||||||
*
|
|
||||||
* @return A handle to the module.
|
|
||||||
*/
|
|
||||||
module_handle imported_module() const {
|
|
||||||
if (m_type != function_t::Import) throw std::runtime_error("function type mismatch");
|
|
||||||
return m_value.imp.mod;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns a module of imported function.
|
|
||||||
*
|
|
||||||
* @return A handle to the module.
|
|
||||||
*/
|
|
||||||
function_handle imported_function() const {
|
|
||||||
if (m_type != function_t::Import) throw std::runtime_error("function type mismatch");
|
|
||||||
return m_value.imp.function;
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
function_handle m_id;
|
|
||||||
function_t m_type;
|
|
||||||
mod_p m_module;
|
|
||||||
|
|
||||||
union value {
|
|
||||||
std::size_t position = 0;
|
|
||||||
native_function native;
|
|
||||||
struct {
|
|
||||||
module_handle mod;
|
|
||||||
function_handle function;
|
|
||||||
} imp;
|
|
||||||
|
|
||||||
value() = default;
|
|
||||||
|
|
||||||
value(std::size_t position)
|
|
||||||
: position(position) {}
|
|
||||||
|
|
||||||
value(const native_function& native)
|
|
||||||
: native(native) {}
|
|
||||||
|
|
||||||
value(module_handle mod, function_handle function)
|
|
||||||
: imp({ mod, function }) {}
|
|
||||||
|
|
||||||
~value() {}
|
|
||||||
|
|
||||||
value(value&& other) = delete;
|
|
||||||
value& operator=(value&& other) = delete;
|
|
||||||
value(const value& other) = delete;
|
|
||||||
value& operator=(const value& other) = delete;
|
|
||||||
} m_value;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace furvm
|
|
||||||
|
|
||||||
#endif // FURVM_FUNCTION_HPP
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
#ifndef FURVM_INSTRUCTION_HPP
|
|
||||||
#define FURVM_INSTRUCTION_HPP
|
|
||||||
|
|
||||||
#include "furvm/fwd.hpp"
|
|
||||||
|
|
||||||
namespace furvm {
|
|
||||||
|
|
||||||
enum class instruction_t : byte {
|
|
||||||
/**
|
|
||||||
* @brief No operation.
|
|
||||||
*/
|
|
||||||
NoOperation = 0,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Pushes an integer from a byte onto the stack.
|
|
||||||
*
|
|
||||||
* Pushes an integer constructed from a next byte onto the stack.
|
|
||||||
*/
|
|
||||||
PushB2I,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Pushes a constant onto the stack.
|
|
||||||
*
|
|
||||||
* Pushes a constant from the constant pool denoted by two next bytes in little-endian onto the stack.
|
|
||||||
*/
|
|
||||||
PushConstant,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Pops top element from the stack.
|
|
||||||
*/
|
|
||||||
Drop,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Duplicates top element on the stack.
|
|
||||||
*/
|
|
||||||
Duplicate,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Clones top element on the stack.
|
|
||||||
*/
|
|
||||||
Clone,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Adds two things together on the stack.
|
|
||||||
*/
|
|
||||||
Add,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Subtracts two things together on the stack.
|
|
||||||
*/
|
|
||||||
Sub,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Multiplies two things together on the stack.
|
|
||||||
*/
|
|
||||||
Mul,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Divides two things together on the stack.
|
|
||||||
*/
|
|
||||||
Div,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Modulos two things together on the stack.
|
|
||||||
*/
|
|
||||||
Mod,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Pushes a variable onto the stack.
|
|
||||||
*
|
|
||||||
* Fetches a variable denoted by next two bytes in little-endian and pushes it onto the stack.
|
|
||||||
*/
|
|
||||||
Load,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Stores an element from the stack in a variable.
|
|
||||||
*
|
|
||||||
* Pops a thing from the stack and stores it in a variable denoted by next two bytes in little-endian.
|
|
||||||
*/
|
|
||||||
Store,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Calls a function.
|
|
||||||
*
|
|
||||||
* Calls a function denoted by next two bytes in little-endian from current frame's module.
|
|
||||||
*/
|
|
||||||
Call,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Pops the current call frame.
|
|
||||||
*/
|
|
||||||
Return,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Pops the current call frame and pushes the first element from the previous stack onto the new stack.
|
|
||||||
*/
|
|
||||||
ReturnValue,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct instruction {
|
|
||||||
instruction_t type; /**< Instruction type. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Instruction value.
|
|
||||||
*/
|
|
||||||
union value {
|
|
||||||
constant_index constant; /**< Constant instruction argument. */
|
|
||||||
} value; /**< Instruction value. */
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace furvm
|
|
||||||
|
|
||||||
#endif // FURVM_INSTRUCTION_HPP
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
#ifndef FURVM_MODULE_HPP
|
|
||||||
#define FURVM_MODULE_HPP
|
|
||||||
|
|
||||||
#include "furvm/fwd.hpp"
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace furvm {
|
|
||||||
|
|
||||||
class mod {
|
|
||||||
friend class function;
|
|
||||||
friend class serializer;
|
|
||||||
public:
|
|
||||||
using bytecode_t = std::vector<byte>; /**< An alias to a vector of bytes. */
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Construct a new module.
|
|
||||||
*
|
|
||||||
* @param id Id of this module.
|
|
||||||
* @param args Arguments to forward to bytecode's constructor.
|
|
||||||
*/
|
|
||||||
template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<bytecode_t, Args...>>>
|
|
||||||
mod(module_handle id, Args&&... args)
|
|
||||||
: m_id(id), m_bytecode(std::forward<Args>(args)...) {}
|
|
||||||
|
|
||||||
~mod() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
mod(mod&&) = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
mod& operator=(mod&&) = default;
|
|
||||||
|
|
||||||
mod(const mod&) = delete;
|
|
||||||
mod& operator=(const mod&) = delete;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns an id of this module.
|
|
||||||
*
|
|
||||||
* @return The id.
|
|
||||||
*/
|
|
||||||
constexpr module_handle id() const { return m_id; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns a byte from bytecode of this module.
|
|
||||||
*
|
|
||||||
* @param offset An offset of the byte.
|
|
||||||
* @return The byte.
|
|
||||||
*/
|
|
||||||
constexpr byte byte(std::size_t offset) const { return m_bytecode.at(offset); }
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns a function from this module.
|
|
||||||
*
|
|
||||||
* @param id Id of the function.
|
|
||||||
* @return The function.
|
|
||||||
*/
|
|
||||||
constexpr const function_p& function_at(function_handle id) const { return m_functions.at(id); }
|
|
||||||
private:
|
|
||||||
module_handle m_id;
|
|
||||||
bytecode_t m_bytecode;
|
|
||||||
std::vector<function_p> m_functions;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace furvm
|
|
||||||
|
|
||||||
#endif // FURVM_MODULE_HPP
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
#ifndef FURVM_SERIALIZER_HPP
|
|
||||||
#define FURVM_SERIALIZER_HPP
|
|
||||||
|
|
||||||
#include "furvm/fwd.hpp"
|
|
||||||
|
|
||||||
#include <ostream>
|
|
||||||
|
|
||||||
namespace furvm {
|
|
||||||
|
|
||||||
class serializer {
|
|
||||||
public:
|
|
||||||
static constexpr byte MODULE_MAGIC[4] = { 'F', 'u', 'r', 'M' };
|
|
||||||
static constexpr std::uint32_t VERSION = 0;
|
|
||||||
public:
|
|
||||||
static bool serialize_module(std::ostream& os, const mod_p& mod);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace furvm
|
|
||||||
|
|
||||||
#endif // FURVM_SERIALIZER_HPP
|
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
#ifndef FURVM_THING_HPP
|
|
||||||
#define FURVM_THING_HPP
|
|
||||||
|
|
||||||
#include "furvm/context.hpp" // IWYU pragma: keep
|
|
||||||
#include "furvm/fwd.hpp"
|
|
||||||
|
|
||||||
namespace furvm {
|
|
||||||
|
|
||||||
enum class thing_t : std::uint8_t {
|
|
||||||
Int32,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns data size of a thing.
|
|
||||||
*
|
|
||||||
* @param type Type of the thing.
|
|
||||||
* @return The data size of the thing.
|
|
||||||
*/
|
|
||||||
std::size_t thing_type_size(thing_t type);
|
|
||||||
|
|
||||||
class bad_thing_access : public std::exception {
|
|
||||||
public:
|
|
||||||
bad_thing_access() = default;
|
|
||||||
~bad_thing_access() override = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
bad_thing_access(bad_thing_access&&) noexcept = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
bad_thing_access& operator=(bad_thing_access&&) noexcept = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Copy constructor.
|
|
||||||
*/
|
|
||||||
bad_thing_access(const bad_thing_access&) = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Copy constructor.
|
|
||||||
*/
|
|
||||||
bad_thing_access& operator=(const bad_thing_access&) = default;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns a C-style string describing the cause of the error.
|
|
||||||
*
|
|
||||||
* @return The cause of the error.
|
|
||||||
*/
|
|
||||||
const char* what() const noexcept override { return "bad thing access"; }
|
|
||||||
};
|
|
||||||
|
|
||||||
class thing final {
|
|
||||||
friend class executor;
|
|
||||||
private:
|
|
||||||
/**
|
|
||||||
* @brief A private token for the private constructor.
|
|
||||||
*
|
|
||||||
* Also `rzecz` in Polish translates to `thing` I think.
|
|
||||||
*/
|
|
||||||
struct rzecz {
|
|
||||||
explicit rzecz() = default;
|
|
||||||
};
|
|
||||||
public:
|
|
||||||
using nref_t = std::size_t; /**< Type of reference count. */
|
|
||||||
|
|
||||||
static constexpr thing_handle GENERATION_SIZE = 12; /**< Bit size of generation part in thing_handle. */
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Private constructor.
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
* @param type
|
|
||||||
* @param context
|
|
||||||
*/
|
|
||||||
thing(rzecz, thing_handle id, thing_t type, const context_p& context);
|
|
||||||
|
|
||||||
~thing();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
thing(thing&&) noexcept;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
thing& operator=(thing&&) noexcept;
|
|
||||||
|
|
||||||
thing(const thing&) = delete;
|
|
||||||
thing& operator=(const thing&) = delete;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Adds two things together.
|
|
||||||
*
|
|
||||||
* @param lhs Left-hand-side thing.
|
|
||||||
* @param rhs Right-hand-side thing.
|
|
||||||
* @return Shared pointer to result thing.
|
|
||||||
*/
|
|
||||||
friend thing_p operator+(const thing_p& lhs, const thing_p& rhs);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Subtracts two things together.
|
|
||||||
*
|
|
||||||
* @param lhs Left-hand-side thing.
|
|
||||||
* @param rhs Right-hand-side thing.
|
|
||||||
* @return Shared pointer to result thing.
|
|
||||||
*/
|
|
||||||
friend thing_p operator-(const thing_p& lhs, const thing_p& rhs);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Multiplies two things together.
|
|
||||||
*
|
|
||||||
* @param lhs Left-hand-side thing.
|
|
||||||
* @param rhs Right-hand-side thing.
|
|
||||||
* @return Shared pointer to result thing.
|
|
||||||
*/
|
|
||||||
friend thing_p operator*(const thing_p& lhs, const thing_p& rhs);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Divides two things together.
|
|
||||||
*
|
|
||||||
* @param lhs Left-hand-side thing.
|
|
||||||
* @param rhs Right-hand-side thing.
|
|
||||||
* @return Shared pointer to result thing.
|
|
||||||
*/
|
|
||||||
friend thing_p operator/(const thing_p& lhs, const thing_p& rhs);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Modulos two things together.
|
|
||||||
*
|
|
||||||
* @param lhs Left-hand-side thing.
|
|
||||||
* @param rhs Right-hand-side thing.
|
|
||||||
* @return Shared pointer to result thing.
|
|
||||||
*/
|
|
||||||
friend thing_p operator%(const thing_p& lhs, const thing_p& rhs);
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns a new thing.
|
|
||||||
*
|
|
||||||
* @param context Furvm context.
|
|
||||||
* @param args Arguments to forward to the thing constructor.
|
|
||||||
* @return Shared pointer to the new thing.
|
|
||||||
*/
|
|
||||||
template <typename... Args,
|
|
||||||
typename = std::enable_if_t<std::is_constructible_v<thing, rzecz, thing_handle, Args..., const context_p&>>>
|
|
||||||
static thing_p create(const context_p& context, Args&&... args) {
|
|
||||||
thing_handle id = context->m_things.size();
|
|
||||||
if (!context->m_deadThings.empty()) {
|
|
||||||
id = context->m_deadThings.front();
|
|
||||||
context->m_deadThings.pop();
|
|
||||||
id += 1 << GENERATION_SIZE;
|
|
||||||
}
|
|
||||||
thing_handle idx = id & ((1ULL << ((sizeof(id) * 8) - GENERATION_SIZE)) - 1);
|
|
||||||
|
|
||||||
auto th = std::make_shared<thing>(rzecz{}, id, std::forward<Args>(args)..., context);
|
|
||||||
context->m_things.emplace(context->m_things.begin() + idx, th);
|
|
||||||
return std::move(th);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns a clone of the thing.
|
|
||||||
*
|
|
||||||
* @param thing Thing to clone.
|
|
||||||
* @return Shared pointer to a clone of the thing.
|
|
||||||
*/
|
|
||||||
static thing_p clone(const thing_p& thing);
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns an int32 value from this thing.
|
|
||||||
*
|
|
||||||
* @return The value.
|
|
||||||
*/
|
|
||||||
std::int32_t& int32();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns an int32 value from this thing.
|
|
||||||
*
|
|
||||||
* @return The value.
|
|
||||||
*/
|
|
||||||
const std::int32_t& int32() const;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Increments reference count of this thing by one.
|
|
||||||
*/
|
|
||||||
void add_reference() { ++m_refCount; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Decrements reference count of this thing by one.
|
|
||||||
*/
|
|
||||||
void remove_reference() { --m_refCount; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns reference count of this thing.
|
|
||||||
*
|
|
||||||
* @return The reference count.
|
|
||||||
*/
|
|
||||||
constexpr nref_t reference_count() const { return m_refCount; }
|
|
||||||
private:
|
|
||||||
thing_handle m_id;
|
|
||||||
thing_t m_type;
|
|
||||||
context_p m_context;
|
|
||||||
|
|
||||||
nref_t m_refCount = 0;
|
|
||||||
void* m_data = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace furvm
|
|
||||||
|
|
||||||
#endif // FURVM_THING_HPP
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
#include "furvm/context.hpp"
|
|
||||||
|
|
||||||
#include "furvm/thing.hpp" // IWYU pragma: keep
|
|
||||||
|
|
||||||
namespace furvm {
|
|
||||||
|
|
||||||
context::context() {}
|
|
||||||
|
|
||||||
void context::collect() {
|
|
||||||
for (auto& ref : m_things) {
|
|
||||||
if (ref->reference_count() != 0) continue;
|
|
||||||
ref = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace furvm
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
#include "furvm/executor.hpp"
|
|
||||||
|
|
||||||
#include "furvm/context.hpp" // IWYU pragma: keep
|
|
||||||
#include "furvm/exceptions.hpp"
|
|
||||||
#include "furvm/function.hpp" // IWYU pragma: keep
|
|
||||||
#include "furvm/fwd.hpp"
|
|
||||||
#include "furvm/instruction.hpp"
|
|
||||||
#include "furvm/thing.hpp"
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <stdexcept>
|
|
||||||
|
|
||||||
namespace furvm {
|
|
||||||
|
|
||||||
executor::executor(egzekutor, executor_handle id, const context_p& context)
|
|
||||||
: m_id(id), m_context(context) {}
|
|
||||||
|
|
||||||
executor::~executor() {
|
|
||||||
m_context->m_executors[m_id] = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
executor_p executor::create(const context_p& context) {
|
|
||||||
auto ex = std::make_shared<executor>(egzekutor{}, context->m_executors.size(), context);
|
|
||||||
context->m_executors.push_back(ex);
|
|
||||||
return std::move(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void executor::push_frame(const function_p& function) {
|
|
||||||
if (function->type() != function_t::Normal) return;
|
|
||||||
m_frames.emplace((struct executor::frame){ function->mod(), function->position(), m_stack.size() });
|
|
||||||
}
|
|
||||||
|
|
||||||
struct executor::frame executor::pop_frame() {
|
|
||||||
if (m_frames.empty()) throw stack_underflow();
|
|
||||||
struct executor::frame frame = m_frames.top();
|
|
||||||
m_frames.pop();
|
|
||||||
return frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct executor::frame executor::frame() const {
|
|
||||||
return m_frames.top();
|
|
||||||
}
|
|
||||||
|
|
||||||
void executor::push_thing(const thing_p& thing) {
|
|
||||||
thing->add_reference();
|
|
||||||
m_stack.push(thing);
|
|
||||||
}
|
|
||||||
|
|
||||||
void executor::push_thing(thing_p&& thing) {
|
|
||||||
thing->add_reference();
|
|
||||||
m_stack.push(std::move(thing));
|
|
||||||
}
|
|
||||||
|
|
||||||
thing_p executor::pop_thing() {
|
|
||||||
if (m_frames.top().stackBase >= m_stack.size()) throw stack_underflow();
|
|
||||||
thing_p top = std::move(m_stack.top());
|
|
||||||
m_stack.pop();
|
|
||||||
top->remove_reference();
|
|
||||||
return top;
|
|
||||||
}
|
|
||||||
|
|
||||||
thing_p executor::thing() const {
|
|
||||||
if (m_frames.top().stackBase >= m_stack.size()) throw stack_underflow();
|
|
||||||
return m_stack.top();
|
|
||||||
}
|
|
||||||
|
|
||||||
void executor::store_thing(variable_t variable, const thing_p& thing) {
|
|
||||||
auto& frame = m_frames.top();
|
|
||||||
frame.variables.resize(variable + 1);
|
|
||||||
if (frame.variables[variable] != nullptr) {
|
|
||||||
frame.variables[variable]->remove_reference();
|
|
||||||
}
|
|
||||||
frame.variables[variable] = thing;
|
|
||||||
thing->add_reference();
|
|
||||||
}
|
|
||||||
|
|
||||||
void executor::store_thing(variable_t variable, thing_p&& thing) {
|
|
||||||
auto& frame = m_frames.top();
|
|
||||||
frame.variables.resize(variable + 1);
|
|
||||||
if (frame.variables[variable] != nullptr) {
|
|
||||||
frame.variables[variable]->remove_reference();
|
|
||||||
}
|
|
||||||
thing->add_reference();
|
|
||||||
frame.variables[variable] = std::move(thing);
|
|
||||||
}
|
|
||||||
|
|
||||||
thing_p executor::load_thing(variable_t variable) const {
|
|
||||||
const auto& frame = m_frames.top();
|
|
||||||
return frame.variables[variable];
|
|
||||||
}
|
|
||||||
|
|
||||||
void executor::step() {
|
|
||||||
if ((m_flags & executor_flags::Suspended) == executor_flags::Suspended) return;
|
|
||||||
|
|
||||||
struct frame& frame = m_frames.top();
|
|
||||||
|
|
||||||
instruction_t instr = static_cast<instruction_t>(frame.mod->byte(frame.position++));
|
|
||||||
switch (instr) {
|
|
||||||
case instruction_t::NoOperation: break;
|
|
||||||
case instruction_t::PushB2I: {
|
|
||||||
auto thing = thing::create(m_context, thing_t::Int32);
|
|
||||||
thing->int32() = frame.mod->byte(frame.position++);
|
|
||||||
push_thing(std::move(thing));
|
|
||||||
} break;
|
|
||||||
case instruction_t::Drop: {
|
|
||||||
pop_thing();
|
|
||||||
} break;
|
|
||||||
case instruction_t::Duplicate: {
|
|
||||||
push_thing(thing());
|
|
||||||
} break;
|
|
||||||
case instruction_t::Clone: {
|
|
||||||
push_thing(std::move(thing::clone(thing())));
|
|
||||||
} break;
|
|
||||||
case instruction_t::Add: {
|
|
||||||
auto rhs = pop_thing();
|
|
||||||
auto lhs = pop_thing();
|
|
||||||
push_thing(lhs + rhs);
|
|
||||||
} break;
|
|
||||||
case instruction_t::Sub: {
|
|
||||||
auto rhs = pop_thing();
|
|
||||||
auto lhs = pop_thing();
|
|
||||||
push_thing(lhs - rhs);
|
|
||||||
} break;
|
|
||||||
case instruction_t::Mul: {
|
|
||||||
auto rhs = pop_thing();
|
|
||||||
auto lhs = pop_thing();
|
|
||||||
push_thing(lhs * rhs);
|
|
||||||
} break;
|
|
||||||
case instruction_t::Div: {
|
|
||||||
auto rhs = pop_thing();
|
|
||||||
auto lhs = pop_thing();
|
|
||||||
push_thing(lhs / rhs);
|
|
||||||
} break;
|
|
||||||
case instruction_t::Mod: {
|
|
||||||
auto rhs = pop_thing();
|
|
||||||
auto lhs = pop_thing();
|
|
||||||
push_thing(lhs % rhs);
|
|
||||||
} break;
|
|
||||||
case instruction_t::Load: {
|
|
||||||
variable_t variable = static_cast<std::uint16_t>(frame.mod->byte(frame.position)) |
|
|
||||||
(static_cast<std::uint16_t>(frame.mod->byte(frame.position + 1)) << 8);
|
|
||||||
push_thing(load_thing(variable));
|
|
||||||
} break;
|
|
||||||
case instruction_t::Store: {
|
|
||||||
variable_t variable = static_cast<std::uint16_t>(frame.mod->byte(frame.position)) |
|
|
||||||
(static_cast<std::uint16_t>(frame.mod->byte(frame.position + 1)) << 8);
|
|
||||||
store_thing(variable, std::move(pop_thing()));
|
|
||||||
} break;
|
|
||||||
case instruction_t::Call: {
|
|
||||||
function_handle funcId = static_cast<std::uint16_t>(frame.mod->byte(frame.position)) |
|
|
||||||
(static_cast<std::uint16_t>(frame.mod->byte(frame.position + 1)) << 8);
|
|
||||||
frame.position += 2;
|
|
||||||
|
|
||||||
const function_p& function = frame.mod->function_at(funcId);
|
|
||||||
switch (function->type()) {
|
|
||||||
case function_t::Normal: push_frame(function); break;
|
|
||||||
case function_t::Native: function->native()(*this); break;
|
|
||||||
case function_t::Import: {
|
|
||||||
const mod_p& impMod = m_context->m_modules.at(function->imported_module());
|
|
||||||
push_frame(impMod->function_at(function->imported_function()));
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
case instruction_t::Return: {
|
|
||||||
pop_frame();
|
|
||||||
if (m_frames.empty()) m_flags = m_flags | executor_flags::Done;
|
|
||||||
} break;
|
|
||||||
case instruction_t::ReturnValue: {
|
|
||||||
auto value = pop_thing();
|
|
||||||
pop_frame();
|
|
||||||
push_thing(std::move(value));
|
|
||||||
} break;
|
|
||||||
case instruction_t::PushConstant: throw std::runtime_error("unimplemented");
|
|
||||||
default: throw std::runtime_error("unknown instruction");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace furvm
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
#include "furvm/function.hpp"
|
|
||||||
|
|
||||||
namespace furvm {
|
|
||||||
|
|
||||||
function::function(funkcja, function_handle id, std::size_t position, const mod_p& mod)
|
|
||||||
: m_id(id), m_type(function_t::Normal), m_module(mod), m_value(position) {}
|
|
||||||
|
|
||||||
function::function(funkcja, function_handle id, const native_function& native, const mod_p& mod)
|
|
||||||
: m_id(id), m_type(function_t::Native), m_module(mod), m_value(native) {}
|
|
||||||
|
|
||||||
function::function(function&& other) noexcept
|
|
||||||
: m_id(other.m_id), m_type(other.m_type), m_module(std::move(other.m_module)) {
|
|
||||||
switch (m_type) {
|
|
||||||
case function_t::Normal: {
|
|
||||||
m_value.position = other.m_value.position;
|
|
||||||
} break;
|
|
||||||
case function_t::Native: {
|
|
||||||
new (&m_value.native) native_function(std::move(other.m_value.native));
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
other.m_value.position = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function& function::operator=(function&& other) noexcept {
|
|
||||||
if (this == &other) return *this;
|
|
||||||
|
|
||||||
m_id = other.m_id;
|
|
||||||
m_type = other.m_type;
|
|
||||||
m_module = std::move(other.m_module);
|
|
||||||
switch (m_type) {
|
|
||||||
case function_t::Normal: {
|
|
||||||
m_value.position = other.m_value.position;
|
|
||||||
} break;
|
|
||||||
case function_t::Native: {
|
|
||||||
new (&m_value.native) native_function(std::move(other.m_value.native));
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
other.m_value.position = 0;
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace furvm
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
#ifndef LIBFURVM
|
|
||||||
|
|
||||||
#include "furvm/context.hpp"
|
|
||||||
#include "furvm/executor.hpp"
|
|
||||||
#include "furvm/function.hpp"
|
|
||||||
#include "furvm/instruction.hpp"
|
|
||||||
#include "furvm/serializer.hpp"
|
|
||||||
#include "furvm/thing.hpp"
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <fstream>
|
|
||||||
#include <iostream>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
static constexpr std::array<furvm::byte, 8> s_bytecode = {
|
|
||||||
furvm::byte(furvm::instruction_t::PushB2I),
|
|
||||||
67,
|
|
||||||
furvm::byte(furvm::instruction_t::Clone),
|
|
||||||
furvm::byte(furvm::instruction_t::Add),
|
|
||||||
furvm::byte(furvm::instruction_t::Return),
|
|
||||||
};
|
|
||||||
|
|
||||||
int main(void) {
|
|
||||||
auto context = std::make_shared<furvm::context>();
|
|
||||||
|
|
||||||
auto mainModule = context->emplace(s_bytecode.begin(), s_bytecode.end());
|
|
||||||
|
|
||||||
auto mainFunction = furvm::function::create(mainModule, 0);
|
|
||||||
|
|
||||||
std::ofstream file("./test.furm", std::ios::binary);
|
|
||||||
furvm::serializer::serialize_module(file, mainModule);
|
|
||||||
|
|
||||||
auto executor = furvm::executor::create(context);
|
|
||||||
executor->push_frame(mainFunction);
|
|
||||||
|
|
||||||
static constexpr std::size_t FPC = 3; // Frames per collection
|
|
||||||
|
|
||||||
std::size_t count = 0;
|
|
||||||
while ((executor->flags() & furvm::executor_flags::Done) != furvm::executor_flags::Done) {
|
|
||||||
executor->step();
|
|
||||||
if ((++count % FPC) == 0) context->collect();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // LUBFURVM
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
#include "furvm/module.hpp"
|
|
||||||
|
|
||||||
namespace furvm {}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
#include "furvm/serializer.hpp"
|
|
||||||
|
|
||||||
#include "furvm/function.hpp" // IWYU pragma: keep
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <cstring>
|
|
||||||
#include <istream>
|
|
||||||
#include <optional>
|
|
||||||
#include <ostream>
|
|
||||||
#include <stdexcept>
|
|
||||||
|
|
||||||
namespace furvm {
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
static inline void write_raw(std::ostream& os, const T& value) {
|
|
||||||
os.write(reinterpret_cast<const char*>(&value), sizeof(T));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
static inline void read_raw(std::istream& is, T& value) {
|
|
||||||
is.read(reinterpret_cast<char*>(&value), sizeof(T));
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void write_u64(std::ostream& os, std::uint64_t value) {
|
|
||||||
os.put(static_cast<char>((value >> 0ULL) & 0xFF));
|
|
||||||
os.put(static_cast<char>((value >> 8ULL) & 0xFF));
|
|
||||||
os.put(static_cast<char>((value >> 16ULL) & 0xFF));
|
|
||||||
os.put(static_cast<char>((value >> 24ULL) & 0xFF));
|
|
||||||
os.put(static_cast<char>((value >> 32ULL) & 0xFF));
|
|
||||||
os.put(static_cast<char>((value >> 40ULL) & 0xFF));
|
|
||||||
os.put(static_cast<char>((value >> 48ULL) & 0xFF));
|
|
||||||
os.put(static_cast<char>((value >> 56ULL) & 0xFF));
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void read_u64(std::istream& is, std::uint64_t& value) {
|
|
||||||
value = (static_cast<std::uint64_t>(is.get()) << 0) | (static_cast<std::uint64_t>(is.get()) << 8) |
|
|
||||||
(static_cast<std::uint64_t>(is.get()) << 16) | (static_cast<std::uint64_t>(is.get()) << 24) |
|
|
||||||
(static_cast<std::uint64_t>(is.get()) << 32) | (static_cast<std::uint64_t>(is.get()) << 40) |
|
|
||||||
(static_cast<std::uint64_t>(is.get()) << 48) | (static_cast<std::uint64_t>(is.get()) << 56);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void write_u32(std::ostream& os, std::uint32_t value) {
|
|
||||||
os.put(static_cast<char>((value >> 0ULL) & 0xFF));
|
|
||||||
os.put(static_cast<char>((value >> 8ULL) & 0xFF));
|
|
||||||
os.put(static_cast<char>((value >> 16ULL) & 0xFF));
|
|
||||||
os.put(static_cast<char>((value >> 24ULL) & 0xFF));
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void read_u32(std::istream& is, std::uint32_t& value) {
|
|
||||||
value = static_cast<std::uint32_t>(is.get() << 0) | static_cast<std::uint32_t>(is.get() << 8) |
|
|
||||||
static_cast<std::uint32_t>(is.get() << 16) | static_cast<std::uint32_t>(is.get() << 24);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void write_u16(std::ostream& os, std::uint16_t value) {
|
|
||||||
os.put(static_cast<char>((value >> 0ULL) & 0xFF));
|
|
||||||
os.put(static_cast<char>((value >> 8ULL) & 0xFF));
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void read_u16(std::istream& is, std::uint16_t& value) {
|
|
||||||
value = static_cast<std::uint16_t>(is.get() << 0) | static_cast<std::uint16_t>(is.get() << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void write_u8(std::ostream& os, std::uint8_t value) {
|
|
||||||
os.put(static_cast<char>((value >> 0ULL) & 0xFF));
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void read_u8(std::istream& is, std::uint8_t& value) {
|
|
||||||
value = static_cast<std::uint8_t>(is.get() << 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool serializer::serialize_module(std::ostream& os, const mod_p& mod) {
|
|
||||||
os.write(reinterpret_cast<const char*>(MODULE_MAGIC), sizeof(MODULE_MAGIC));
|
|
||||||
write_u32(os, VERSION);
|
|
||||||
|
|
||||||
write_u32(os, mod->m_functions.size());
|
|
||||||
for (const auto& func : mod->m_functions) {
|
|
||||||
write_u16(os, func->id());
|
|
||||||
write_u8(os, static_cast<std::uint8_t>(func->type()));
|
|
||||||
switch (func->type()) {
|
|
||||||
case function_t::Normal: {
|
|
||||||
write_u64(os, func->position());
|
|
||||||
} break;
|
|
||||||
case function_t::Native: {
|
|
||||||
// TODO: Replace with a reference to the function's name from constant pool
|
|
||||||
throw std::runtime_error("cannot serialize native functions yet");
|
|
||||||
} break;
|
|
||||||
case function_t::Import: {
|
|
||||||
// TODO: Replace with a reference to the module's and function's name from constant pool
|
|
||||||
throw std::runtime_error("cannot serialize import functions yet");
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
write_u64(os, mod->m_bytecode.size());
|
|
||||||
os.write(reinterpret_cast<const char*>(mod->m_bytecode.data()),
|
|
||||||
static_cast<std::streamsize>(mod->m_bytecode.size()));
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace furvm
|
|
||||||
@@ -1,177 +0,0 @@
|
|||||||
// NOLINTBEGIN(cppcoreguidelines-no-malloc)
|
|
||||||
|
|
||||||
#include "furvm/thing.hpp"
|
|
||||||
|
|
||||||
#include "furvm/context.hpp" // IWYU pragma: keep
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstring>
|
|
||||||
#include <stdexcept>
|
|
||||||
|
|
||||||
namespace furvm {
|
|
||||||
|
|
||||||
std::size_t thing_type_size(thing_t type) {
|
|
||||||
switch (type) {
|
|
||||||
case thing_t::Int32: return 4;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
thing::thing(rzecz, thing_handle id, thing_t type, const context_p& context)
|
|
||||||
: m_id(id), m_type(type), m_context(context) {
|
|
||||||
std::size_t size = thing_type_size(type);
|
|
||||||
std::byte* data = nullptr;
|
|
||||||
if (!m_context->m_deadThingData.empty()) {
|
|
||||||
thing_t itType{};
|
|
||||||
for (auto it = m_context->m_deadThingData.rbegin(); it != m_context->m_deadThingData.rend(); ++it) {
|
|
||||||
std::memcpy(&itType, static_cast<std::byte*>(*it) - sizeof(itType), sizeof(itType));
|
|
||||||
if (size == thing_type_size(itType)) {
|
|
||||||
data = static_cast<std::byte*>(*it);
|
|
||||||
m_context->m_deadThingData.erase(std::next(it).base());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (data == nullptr) data = m_context->m_thingArena.allocate<std::byte>(sizeof(type) + size);
|
|
||||||
|
|
||||||
if (data == nullptr) throw std::runtime_error("failed to allocate data for new thing");
|
|
||||||
std::memcpy(data, &type, sizeof(type));
|
|
||||||
m_data = data + sizeof(type);
|
|
||||||
|
|
||||||
switch (m_type) {
|
|
||||||
// Primitives are zero-initialized.
|
|
||||||
default:
|
|
||||||
case thing_t::Int32: std::memset(m_data, 0, size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
thing::~thing() {
|
|
||||||
switch (m_type) {
|
|
||||||
// Primitives are not destructed.
|
|
||||||
default:
|
|
||||||
case thing_t::Int32: break;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_context->m_deadThingData.push_back(m_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
thing::thing(thing&& other) noexcept
|
|
||||||
: m_id(other.m_id),
|
|
||||||
m_type(other.m_type),
|
|
||||||
m_context(std::move(other.m_context)),
|
|
||||||
m_refCount(other.m_refCount),
|
|
||||||
m_data(other.m_data) {
|
|
||||||
other.m_id = {};
|
|
||||||
other.m_type = {};
|
|
||||||
other.m_refCount = {};
|
|
||||||
other.m_data = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
thing& thing::operator=(thing&& other) noexcept {
|
|
||||||
if (this == &other) return *this;
|
|
||||||
m_id = other.m_id;
|
|
||||||
m_type = other.m_type;
|
|
||||||
m_context = std::move(other.m_context);
|
|
||||||
m_refCount = other.m_refCount;
|
|
||||||
m_data = other.m_data;
|
|
||||||
|
|
||||||
other.m_id = {};
|
|
||||||
other.m_type = {};
|
|
||||||
other.m_refCount = {};
|
|
||||||
other.m_data = nullptr;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
static constexpr std::uint16_t thing_type_pair(thing_t lhs, thing_t rhs) {
|
|
||||||
return (static_cast<std::uint16_t>(lhs) << 8) | static_cast<std::uint16_t>(rhs);
|
|
||||||
}
|
|
||||||
|
|
||||||
thing_p operator+(const thing_p& lhs, const thing_p& rhs) {
|
|
||||||
switch (thing_type_pair(lhs->m_type, rhs->m_type)) {
|
|
||||||
case thing_type_pair(thing_t::Int32, thing_t::Int32): {
|
|
||||||
auto res = thing::create(lhs->m_context, thing_t::Int32);
|
|
||||||
res->int32() = lhs->int32() + rhs->int32();
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
default: throw std::runtime_error("unexpected operator");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
thing_p operator-(const thing_p& lhs, const thing_p& rhs) {
|
|
||||||
switch (thing_type_pair(lhs->m_type, rhs->m_type)) {
|
|
||||||
case thing_type_pair(thing_t::Int32, thing_t::Int32): {
|
|
||||||
auto res = thing::create(lhs->m_context, thing_t::Int32);
|
|
||||||
res->int32() = lhs->int32() - rhs->int32();
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
default: throw std::runtime_error("unexpected operator");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
thing_p operator*(const thing_p& lhs, const thing_p& rhs) {
|
|
||||||
switch (thing_type_pair(lhs->m_type, rhs->m_type)) {
|
|
||||||
case thing_type_pair(thing_t::Int32, thing_t::Int32): {
|
|
||||||
auto res = thing::create(lhs->m_context, thing_t::Int32);
|
|
||||||
res->int32() = lhs->int32() * rhs->int32();
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
default: throw std::runtime_error("unexpected operator");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
thing_p operator/(const thing_p& lhs, const thing_p& rhs) {
|
|
||||||
switch (thing_type_pair(lhs->m_type, rhs->m_type)) {
|
|
||||||
case thing_type_pair(thing_t::Int32, thing_t::Int32): {
|
|
||||||
auto res = thing::create(lhs->m_context, thing_t::Int32);
|
|
||||||
res->int32() = lhs->int32() / rhs->int32();
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
default: throw std::runtime_error("unexpected operator");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
thing_p operator%(const thing_p& lhs, const thing_p& rhs) {
|
|
||||||
switch (thing_type_pair(lhs->m_type, rhs->m_type)) {
|
|
||||||
case thing_type_pair(thing_t::Int32, thing_t::Int32): {
|
|
||||||
auto res = thing::create(lhs->m_context, thing_t::Int32);
|
|
||||||
res->int32() = lhs->int32() % rhs->int32();
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
default: throw std::runtime_error("unexpected operator");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
thing_p thing::clone(const thing_p& thing) {
|
|
||||||
thing_handle id = thing->m_context->m_things.size();
|
|
||||||
if (!thing->m_context->m_deadThings.empty()) {
|
|
||||||
id = thing->m_context->m_deadThings.front();
|
|
||||||
thing->m_context->m_deadThings.pop();
|
|
||||||
id += 1 << GENERATION_SIZE;
|
|
||||||
}
|
|
||||||
thing_handle idx = id & ((1ULL << ((sizeof(id) * 8) - GENERATION_SIZE)) - 1);
|
|
||||||
|
|
||||||
auto th = std::make_shared<class thing>(rzecz{}, id, thing->m_type, thing->m_context);
|
|
||||||
switch (thing->m_type) {
|
|
||||||
// Primitives
|
|
||||||
default: {
|
|
||||||
memcpy(th->m_data, thing->m_data, thing_type_size(thing->m_type));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
thing->m_context->m_things.emplace(thing->m_context->m_things.begin() + idx, th);
|
|
||||||
return std::move(th);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::int32_t& thing::int32() {
|
|
||||||
if (m_type != thing_t::Int32) throw bad_thing_access();
|
|
||||||
return *static_cast<std::int32_t*>(m_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::int32_t& thing::int32() const {
|
|
||||||
if (m_type != thing_t::Int32) throw bad_thing_access();
|
|
||||||
return *static_cast<std::int32_t*>(m_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace furvm
|
|
||||||
|
|
||||||
// NOLINTEND(cppcoreguidelines-no-malloc)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
#include "gtest/gtest.h" // IWYU pragma: keep
|
|
||||||
|
|
||||||
namespace {}
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#ifndef FURAS_GEN_HPP
|
||||||
|
#define FURAS_GEN_HPP
|
||||||
|
|
||||||
|
#include "furas/lexer.hpp"
|
||||||
|
#include "furvm/module.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace furas {
|
||||||
|
|
||||||
|
struct generator_error {
|
||||||
|
enum type {
|
||||||
|
Success = 0,
|
||||||
|
Eof = 1,
|
||||||
|
|
||||||
|
UnexpectedEof = -1,
|
||||||
|
UnexpectedToken = -2,
|
||||||
|
UnknownCharacter = -3,
|
||||||
|
UnknownType = -4,
|
||||||
|
} type = Success;
|
||||||
|
std::string message;
|
||||||
|
};
|
||||||
|
|
||||||
|
class generator {
|
||||||
|
public:
|
||||||
|
struct result {
|
||||||
|
generator_error error;
|
||||||
|
furvm::mod mod;
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
static result generate(lexer lexer);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace furas
|
||||||
|
|
||||||
|
#endif // FURAS_GEN_HPP
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
#ifndef FURAS_LEXER_HPP
|
||||||
|
#define FURAS_LEXER_HPP
|
||||||
|
|
||||||
|
#include "furas/token.hpp"
|
||||||
|
#include "furlang/result.hpp"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace furas {
|
||||||
|
|
||||||
|
struct lexer_location {
|
||||||
|
std::string_view filename;
|
||||||
|
std::size_t row, col;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct lexer_error {
|
||||||
|
enum type {
|
||||||
|
EndOfFile = 0,
|
||||||
|
UnknownCharacter,
|
||||||
|
} type;
|
||||||
|
|
||||||
|
lexer_location location;
|
||||||
|
std::string message;
|
||||||
|
};
|
||||||
|
|
||||||
|
using token_r = furlang::result<lexer_error, token>;
|
||||||
|
|
||||||
|
class lexer {
|
||||||
|
public:
|
||||||
|
lexer(std::string_view filename, std::string_view content)
|
||||||
|
: m_filename(filename), m_content(content) {}
|
||||||
|
|
||||||
|
token_r next_token();
|
||||||
|
private:
|
||||||
|
constexpr lexer_location location() const { return { m_filename, m_cursor - m_lineStart, m_column }; }
|
||||||
|
private:
|
||||||
|
std::string_view m_filename;
|
||||||
|
std::string_view m_content;
|
||||||
|
std::size_t m_cursor = 0;
|
||||||
|
std::size_t m_lineStart = 0;
|
||||||
|
std::size_t m_column = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace furas
|
||||||
|
|
||||||
|
#endif // FURAS_LEXER_HPP
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
#ifndef FURAS_TOKEN_HPP
|
||||||
|
#define FURAS_TOKEN_HPP
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace furas {
|
||||||
|
|
||||||
|
struct token {
|
||||||
|
enum type {
|
||||||
|
Identifier = 0, /**< An identifier. */
|
||||||
|
Signed, /**< A signed integer. */
|
||||||
|
Unsigned, /**< An unsigned integer. */
|
||||||
|
|
||||||
|
// Markers
|
||||||
|
Monkey, /**< Constant marker (`@`). */
|
||||||
|
Dolar, /**< Type marker(`$`). */
|
||||||
|
Sha256, /**< Label marker(`#`). */
|
||||||
|
Percent, /**< Variable marker(`%`). The more the better. */
|
||||||
|
|
||||||
|
EqSign, /**< `=` */
|
||||||
|
Dot, /**< . */
|
||||||
|
Colon, /**< `:` */
|
||||||
|
|
||||||
|
// Keywords
|
||||||
|
Func, /**< `func` keyword for defining functions. */
|
||||||
|
Type, /**< `type` keyword for defining types. */
|
||||||
|
Native, /**< `native` keyword for native functions. :v: */
|
||||||
|
Import, /**< `import` keyword for importing functions and types. */
|
||||||
|
Public, /**< `public` access specifier. */
|
||||||
|
Private, /**< `private` access specifier. */
|
||||||
|
Allocate, /**< `allocate` keyword for global variables. */
|
||||||
|
|
||||||
|
// Instructions
|
||||||
|
Push,
|
||||||
|
Array,
|
||||||
|
Slice,
|
||||||
|
Get,
|
||||||
|
Set,
|
||||||
|
Drop,
|
||||||
|
Dup,
|
||||||
|
Swap,
|
||||||
|
Clone,
|
||||||
|
Ref,
|
||||||
|
Add,
|
||||||
|
Sub,
|
||||||
|
Mul,
|
||||||
|
Div,
|
||||||
|
Mod,
|
||||||
|
Eq,
|
||||||
|
Neq,
|
||||||
|
Lt,
|
||||||
|
Gt,
|
||||||
|
Le,
|
||||||
|
Ge,
|
||||||
|
Ptrof,
|
||||||
|
Sizeof,
|
||||||
|
Lenof,
|
||||||
|
Load,
|
||||||
|
Store,
|
||||||
|
LoadGlobal,
|
||||||
|
StoreGlobal,
|
||||||
|
Call,
|
||||||
|
Jmp,
|
||||||
|
Jnz,
|
||||||
|
Ret,
|
||||||
|
|
||||||
|
Count
|
||||||
|
} type = Count;
|
||||||
|
union value {
|
||||||
|
std::nullptr_t null = nullptr;
|
||||||
|
std::string_view string;
|
||||||
|
std::int64_t integer;
|
||||||
|
std::uint64_t uint;
|
||||||
|
} value;
|
||||||
|
|
||||||
|
token(enum type type)
|
||||||
|
: type(type) {}
|
||||||
|
|
||||||
|
token(enum type type, std::string_view string)
|
||||||
|
: type(type) {
|
||||||
|
value.string = string;
|
||||||
|
}
|
||||||
|
|
||||||
|
token(std::uint64_t num)
|
||||||
|
: type(Unsigned) {
|
||||||
|
value.uint = num;
|
||||||
|
}
|
||||||
|
|
||||||
|
token(std::int64_t num)
|
||||||
|
: type(Signed) {
|
||||||
|
value.integer = num;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace furas
|
||||||
|
|
||||||
|
#endif // FURAS_TOKEN_HPP
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#ifndef FURC_BACK_FURVM_HPP
|
||||||
|
#define FURC_BACK_FURVM_HPP
|
||||||
|
|
||||||
|
#include "furc/middle/ir.hpp"
|
||||||
|
#include "furvm/module.hpp"
|
||||||
|
|
||||||
|
namespace furc {
|
||||||
|
|
||||||
|
class furvm_generator final {
|
||||||
|
public:
|
||||||
|
static furvm::mod generate(const ir_module& mod);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace furc
|
||||||
|
|
||||||
|
#endif // FURC_BACK_FURVM_HPP
|
||||||
@@ -0,0 +1,346 @@
|
|||||||
|
#ifndef FURC_FRONT_AST_HPP
|
||||||
|
#define FURC_FRONT_AST_HPP
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace furc {
|
||||||
|
|
||||||
|
struct comp_stmt_node;
|
||||||
|
struct if_stmt_node;
|
||||||
|
struct while_stmt_node;
|
||||||
|
struct return_stmt_node;
|
||||||
|
struct var_decl_node;
|
||||||
|
struct func_decl_node;
|
||||||
|
struct var_read_expr_node;
|
||||||
|
struct func_call_expr_node;
|
||||||
|
struct group_expr_node;
|
||||||
|
struct binary_op_expr_node;
|
||||||
|
struct unary_op_expr_node;
|
||||||
|
struct if_expr_node;
|
||||||
|
struct int_lit_node;
|
||||||
|
struct char_lit_node;
|
||||||
|
|
||||||
|
struct ast_visitor {
|
||||||
|
ast_visitor() = default;
|
||||||
|
virtual ~ast_visitor() = default;
|
||||||
|
|
||||||
|
ast_visitor(ast_visitor&&) noexcept = default;
|
||||||
|
ast_visitor& operator=(ast_visitor&&) noexcept = default;
|
||||||
|
|
||||||
|
ast_visitor(const ast_visitor&) = default;
|
||||||
|
ast_visitor& operator=(const ast_visitor&) = default;
|
||||||
|
|
||||||
|
virtual void visit_comp_stmt_node(const comp_stmt_node& node) {}
|
||||||
|
virtual void visit_if_stmt_node(const if_stmt_node& node) {}
|
||||||
|
virtual void visit_while_stmt_node(const while_stmt_node& node) {}
|
||||||
|
virtual void visit_return_stmt_node(const return_stmt_node& node) {}
|
||||||
|
|
||||||
|
virtual void visit_var_decl_node(const var_decl_node& node) {}
|
||||||
|
virtual void visit_func_decl_node(const func_decl_node& node) {}
|
||||||
|
|
||||||
|
virtual void visit_var_read_expr_node(const var_read_expr_node& node) {}
|
||||||
|
virtual void visit_func_call_expr_node(const func_call_expr_node& node) {}
|
||||||
|
virtual void visit_group_expr_node(const group_expr_node& node) {}
|
||||||
|
virtual void visit_binary_op_expr_node(const binary_op_expr_node& node) {}
|
||||||
|
virtual void visit_unary_op_expr_node(const unary_op_expr_node& node) {}
|
||||||
|
virtual void visit_if_expr_node(const if_expr_node& node) {}
|
||||||
|
|
||||||
|
virtual void visit_int_lit_node(const int_lit_node& node) {}
|
||||||
|
virtual void visit_char_lit_node(const char_lit_node& node) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ast_type {
|
||||||
|
enum type_e {
|
||||||
|
Void = 0,
|
||||||
|
S8,
|
||||||
|
U8,
|
||||||
|
S16,
|
||||||
|
U16,
|
||||||
|
S32,
|
||||||
|
U32,
|
||||||
|
S64,
|
||||||
|
U64,
|
||||||
|
} type = Void;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ast_node {
|
||||||
|
public:
|
||||||
|
enum category_e {
|
||||||
|
Statement,
|
||||||
|
Declaration,
|
||||||
|
Expression,
|
||||||
|
Literal,
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
ast_node() = default;
|
||||||
|
virtual ~ast_node() = default;
|
||||||
|
|
||||||
|
ast_node(ast_node&&) noexcept = default;
|
||||||
|
ast_node& operator=(ast_node&&) noexcept = default;
|
||||||
|
|
||||||
|
ast_node(const ast_node&) = delete;
|
||||||
|
ast_node& operator=(const ast_node&) = delete;
|
||||||
|
public:
|
||||||
|
virtual category_e category() const = 0;
|
||||||
|
|
||||||
|
virtual void accept(ast_visitor& visitor) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ast_node_cat = ast_node::category_e;
|
||||||
|
|
||||||
|
class stmt_node : public ast_node {
|
||||||
|
public:
|
||||||
|
enum stmt_type_e {
|
||||||
|
Declaration = 0,
|
||||||
|
Expression,
|
||||||
|
|
||||||
|
Compound,
|
||||||
|
If,
|
||||||
|
While,
|
||||||
|
Return,
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
category_e category() const override { return ast_node_cat::Statement; }
|
||||||
|
|
||||||
|
virtual stmt_type_e stmt_type() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct comp_stmt_node final : public stmt_node {
|
||||||
|
stmt_type_e stmt_type() const override { return Compound; }
|
||||||
|
|
||||||
|
void accept(ast_visitor& visitor) const override { visitor.visit_comp_stmt_node(*this); }
|
||||||
|
|
||||||
|
std::vector<stmt_node*> stmts;
|
||||||
|
};
|
||||||
|
|
||||||
|
class expr_node;
|
||||||
|
|
||||||
|
struct if_stmt_node final : public stmt_node {
|
||||||
|
stmt_type_e stmt_type() const override { return If; }
|
||||||
|
|
||||||
|
void accept(ast_visitor& visitor) const override { visitor.visit_if_stmt_node(*this); }
|
||||||
|
|
||||||
|
expr_node* cond = nullptr;
|
||||||
|
stmt_node* thenBranch = nullptr;
|
||||||
|
stmt_node* elseBranch = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct while_stmt_node final : public stmt_node {
|
||||||
|
stmt_type_e stmt_type() const override { return While; }
|
||||||
|
|
||||||
|
void accept(ast_visitor& visitor) const override { visitor.visit_while_stmt_node(*this); }
|
||||||
|
|
||||||
|
expr_node* cond = nullptr;
|
||||||
|
stmt_node* body = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct return_stmt_node final : public stmt_node {
|
||||||
|
stmt_type_e stmt_type() const override { return Return; }
|
||||||
|
|
||||||
|
void accept(ast_visitor& visitor) const override { visitor.visit_return_stmt_node(*this); }
|
||||||
|
|
||||||
|
expr_node* value = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class decl_node : public stmt_node {
|
||||||
|
public:
|
||||||
|
enum decl_type_e {
|
||||||
|
Variable,
|
||||||
|
Function
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
category_e category() const override { return ast_node_cat::Declaration; }
|
||||||
|
|
||||||
|
stmt_type_e stmt_type() const override { return Declaration; }
|
||||||
|
|
||||||
|
virtual decl_type_e decl_type() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct var_decl_node final : public decl_node {
|
||||||
|
decl_type_e decl_type() const override { return Variable; }
|
||||||
|
|
||||||
|
void accept(ast_visitor& visitor) const override { visitor.visit_var_decl_node(*this); }
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
ast_type type;
|
||||||
|
expr_node* init = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct func_decl_node final : public decl_node {
|
||||||
|
decl_type_e decl_type() const override { return Function; }
|
||||||
|
|
||||||
|
void accept(ast_visitor& visitor) const override { visitor.visit_func_decl_node(*this); }
|
||||||
|
|
||||||
|
struct def_s {
|
||||||
|
comp_stmt_node body;
|
||||||
|
std::vector<expr_node*> preConds;
|
||||||
|
std::vector<expr_node*> postConds;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
ast_type type;
|
||||||
|
std::vector<var_decl_node> params;
|
||||||
|
std::optional<def_s> def;
|
||||||
|
};
|
||||||
|
|
||||||
|
class expr_node : public stmt_node {
|
||||||
|
public:
|
||||||
|
enum expr_type_e {
|
||||||
|
Literal,
|
||||||
|
|
||||||
|
VarRead,
|
||||||
|
FunctionCall,
|
||||||
|
Group,
|
||||||
|
BinaryOp,
|
||||||
|
UnaryOp,
|
||||||
|
If,
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
category_e category() const override { return ast_node_cat::Expression; }
|
||||||
|
|
||||||
|
stmt_type_e stmt_type() const override { return Expression; }
|
||||||
|
|
||||||
|
virtual expr_type_e expr_type() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct var_read_expr_node final : public expr_node {
|
||||||
|
expr_type_e expr_type() const override { return VarRead; }
|
||||||
|
|
||||||
|
void accept(ast_visitor& visitor) const override { visitor.visit_var_read_expr_node(*this); }
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
|
||||||
|
var_read_expr_node(std::string&& name)
|
||||||
|
: name(std::move(name)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct func_call_expr_node final : public expr_node {
|
||||||
|
expr_type_e expr_type() const override { return FunctionCall; }
|
||||||
|
|
||||||
|
void accept(ast_visitor& visitor) const override { visitor.visit_func_call_expr_node(*this); }
|
||||||
|
|
||||||
|
expr_node* lhs = nullptr;
|
||||||
|
std::vector<expr_node*> args;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct group_expr_node final : public expr_node {
|
||||||
|
expr_type_e expr_type() const override { return Group; }
|
||||||
|
|
||||||
|
void accept(ast_visitor& visitor) const override { visitor.visit_group_expr_node(*this); }
|
||||||
|
|
||||||
|
expr_node* inner = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct binary_op_expr_node final : public expr_node {
|
||||||
|
enum binary_op_type {
|
||||||
|
Add = 0,
|
||||||
|
Sub,
|
||||||
|
Mul,
|
||||||
|
Div,
|
||||||
|
Mod,
|
||||||
|
|
||||||
|
Shl,
|
||||||
|
Shr,
|
||||||
|
BinAnd,
|
||||||
|
BinOr,
|
||||||
|
BinXor,
|
||||||
|
And,
|
||||||
|
Or,
|
||||||
|
|
||||||
|
Equals,
|
||||||
|
NotEquals,
|
||||||
|
LessThan,
|
||||||
|
LessEquals,
|
||||||
|
GreaterThan,
|
||||||
|
GreaterEquals,
|
||||||
|
};
|
||||||
|
|
||||||
|
expr_type_e expr_type() const override { return BinaryOp; }
|
||||||
|
|
||||||
|
void accept(ast_visitor& visitor) const override { visitor.visit_binary_op_expr_node(*this); }
|
||||||
|
|
||||||
|
expr_node* lhs = nullptr;
|
||||||
|
expr_node* rhs = nullptr;
|
||||||
|
binary_op_type type = Add;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct unary_op_expr_node final : public expr_node {
|
||||||
|
enum unary_op_type {
|
||||||
|
Positive = 0,
|
||||||
|
Negative,
|
||||||
|
PreInc,
|
||||||
|
PreDec,
|
||||||
|
PostInc,
|
||||||
|
PostDec,
|
||||||
|
BinNot,
|
||||||
|
Not,
|
||||||
|
|
||||||
|
Sizeof,
|
||||||
|
Pointerof,
|
||||||
|
Lengthof,
|
||||||
|
};
|
||||||
|
|
||||||
|
expr_type_e expr_type() const override { return UnaryOp; }
|
||||||
|
|
||||||
|
void accept(ast_visitor& visitor) const override { visitor.visit_unary_op_expr_node(*this); }
|
||||||
|
|
||||||
|
expr_node* lhs = nullptr;
|
||||||
|
unary_op_type type = Positive;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct if_expr_node final : public expr_node {
|
||||||
|
expr_type_e expr_type() const override { return If; }
|
||||||
|
|
||||||
|
void accept(ast_visitor& visitor) const override { visitor.visit_if_expr_node(*this); }
|
||||||
|
|
||||||
|
expr_node* cond = nullptr;
|
||||||
|
expr_node* thenExpr = nullptr;
|
||||||
|
expr_node* elseExpr = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class lit_node : public expr_node {
|
||||||
|
public:
|
||||||
|
enum lit_type_e {
|
||||||
|
Integer,
|
||||||
|
Char,
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
category_e category() const override { return ast_node_cat::Literal; }
|
||||||
|
|
||||||
|
expr_type_e expr_type() const override { return Literal; }
|
||||||
|
|
||||||
|
virtual lit_type_e lit_type() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct int_lit_node final : public lit_node {
|
||||||
|
int_lit_node(std::uint64_t value)
|
||||||
|
: value(value) {}
|
||||||
|
|
||||||
|
lit_type_e lit_type() const override { return Integer; }
|
||||||
|
|
||||||
|
void accept(ast_visitor& visitor) const override { visitor.visit_int_lit_node(*this); }
|
||||||
|
|
||||||
|
std::uint64_t value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct char_lit_node final : public lit_node {
|
||||||
|
char_lit_node(char value)
|
||||||
|
: value(value) {}
|
||||||
|
|
||||||
|
lit_type_e lit_type() const override { return Char; }
|
||||||
|
|
||||||
|
void accept(ast_visitor& visitor) const override { visitor.visit_char_lit_node(*this); }
|
||||||
|
|
||||||
|
char value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ast {
|
||||||
|
std::vector<decl_node*> decls;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace furc
|
||||||
|
|
||||||
|
#endif // FURC_FRONT_AST_HPP
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
#ifndef FURC_FRONT_LEXER_HPP
|
||||||
|
#define FURC_FRONT_LEXER_HPP
|
||||||
|
|
||||||
|
#include "furc/front/token.hpp"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <deque>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace furc {
|
||||||
|
|
||||||
|
class lexer {
|
||||||
|
public:
|
||||||
|
lexer(std::string_view filepath, std::string_view content)
|
||||||
|
: m_filepath(filepath), m_content(content) {}
|
||||||
|
|
||||||
|
~lexer() = default;
|
||||||
|
|
||||||
|
lexer(lexer&&) noexcept = default;
|
||||||
|
lexer& operator=(lexer&&) noexcept = default;
|
||||||
|
|
||||||
|
lexer(const lexer&) = delete;
|
||||||
|
lexer& operator=(const lexer&) = delete;
|
||||||
|
public:
|
||||||
|
token next_token();
|
||||||
|
token peek_token(std::size_t offset = 0);
|
||||||
|
private:
|
||||||
|
token get_token();
|
||||||
|
|
||||||
|
void next();
|
||||||
|
constexpr char get(std::size_t offset = 0) const;
|
||||||
|
void skip_spaces();
|
||||||
|
|
||||||
|
constexpr token::location location() const;
|
||||||
|
private:
|
||||||
|
std::string_view m_filepath;
|
||||||
|
std::string_view m_content;
|
||||||
|
std::size_t m_cursor = 0;
|
||||||
|
std::size_t m_row = 0;
|
||||||
|
std::size_t m_lineStart = 0;
|
||||||
|
|
||||||
|
std::deque<token> m_peekToken;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace furc
|
||||||
|
|
||||||
|
#endif // FURC_FRONT_LEXER_HPP
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#ifndef FURC_FRONT_PARSER_HPP
|
||||||
|
#define FURC_FRONT_PARSER_HPP
|
||||||
|
|
||||||
|
#include "furc/front/ast.hpp"
|
||||||
|
#include "furc/front/lexer.hpp"
|
||||||
|
#include "furlang/arena.hpp"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace furc {
|
||||||
|
|
||||||
|
class parser {
|
||||||
|
public:
|
||||||
|
parser(lexer&& lexer, furlang::arena& arena)
|
||||||
|
: m_lexer(std::move(lexer)), m_arena(&arena) {}
|
||||||
|
|
||||||
|
~parser() = default;
|
||||||
|
|
||||||
|
parser(parser&&) noexcept = default;
|
||||||
|
parser& operator=(parser&&) noexcept = default;
|
||||||
|
|
||||||
|
parser(const parser&) = delete;
|
||||||
|
parser& operator=(const parser&) = delete;
|
||||||
|
public:
|
||||||
|
ast parse();
|
||||||
|
private:
|
||||||
|
stmt_node* parse_stmt();
|
||||||
|
decl_node* parse_decl();
|
||||||
|
expr_node* parse_expr();
|
||||||
|
|
||||||
|
ast_type parse_type();
|
||||||
|
comp_stmt_node parse_comp();
|
||||||
|
|
||||||
|
expr_node* parse_expr_primary();
|
||||||
|
expr_node* parse_expr_unary();
|
||||||
|
expr_node* parse_expr_right(expr_node* lhs, std::uint32_t precedence = 15);
|
||||||
|
private:
|
||||||
|
template <typename... Types>
|
||||||
|
token eat_token(Types... types) {
|
||||||
|
auto token = m_lexer.next_token();
|
||||||
|
if (((token.type == types) || ...)) return token;
|
||||||
|
throw std::runtime_error("unexpected token");
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
lexer m_lexer;
|
||||||
|
furlang::arena* m_arena;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace furc
|
||||||
|
|
||||||
|
#endif // FURC_FRONT_PARSER_HPP
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
#ifndef FURC_FRONT_TOKEN_HPP
|
||||||
|
#define FURC_FRONT_TOKEN_HPP
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <ostream>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace furc {
|
||||||
|
|
||||||
|
struct token {
|
||||||
|
enum type {
|
||||||
|
Identifier = 0,
|
||||||
|
Integer,
|
||||||
|
String,
|
||||||
|
Char,
|
||||||
|
|
||||||
|
LParen, /**< `(` */
|
||||||
|
RParen, /**< `)` */
|
||||||
|
LBrace, /**< `{` */
|
||||||
|
RBrace, /**< `}` */
|
||||||
|
LBracket, /**< `[` */
|
||||||
|
RBracket, /**< `]` */
|
||||||
|
Semicolon, /**< `;` */
|
||||||
|
Colon, /**< `:` */
|
||||||
|
Comma, /**< `,` */
|
||||||
|
Dot, /**< `.` */
|
||||||
|
|
||||||
|
Plus, /**< `+` */
|
||||||
|
Minus, /**< `-` */
|
||||||
|
Star, /**< `*` */
|
||||||
|
Slash, /**< `/` */
|
||||||
|
Percent, /**< `%` */
|
||||||
|
DblLT, /**< `<<` */
|
||||||
|
DblGT, /**< `>>` */
|
||||||
|
Ampersand, /**< `&` */
|
||||||
|
Pipe, /**< `|` */
|
||||||
|
Hat, /**< `^` */
|
||||||
|
DblAmpersand, /**< `&&` */
|
||||||
|
DblPipe, /**< `||` */
|
||||||
|
|
||||||
|
DblPlus, /**< `++` */
|
||||||
|
DblMinus, /**< `--` */
|
||||||
|
Tilde, /**< `~` */
|
||||||
|
ExMark, /**< `!` */
|
||||||
|
CatEars, /**< `^^` */
|
||||||
|
|
||||||
|
Equals, /**< `=` */
|
||||||
|
PlusEquals, /**< `+=` */
|
||||||
|
MinusEquals, /**< `-=` */
|
||||||
|
StarEquals, /**< `*=` */
|
||||||
|
SlashEquals, /**< `/=` */
|
||||||
|
PercentEquals, /**< `%=` */
|
||||||
|
AmpersandEquals, /**< `&=` */
|
||||||
|
PipeEquals, /**< `|=` */
|
||||||
|
HatEquals, /**< `^=` */
|
||||||
|
|
||||||
|
DblEquals, /**< `==` */
|
||||||
|
ExEquals, /**< `!=` */
|
||||||
|
LessThan, /**< `<` */
|
||||||
|
LessEquals, /**< `<=` */
|
||||||
|
GreaterThan, /**< `>` */
|
||||||
|
GreaterEquals, /**< `>=` */
|
||||||
|
|
||||||
|
SlimArrow, /**< `->` */
|
||||||
|
// My brother just another me
|
||||||
|
FatArrow, /**< `=>` */
|
||||||
|
|
||||||
|
Monkey, /**< `@` */
|
||||||
|
Sha256, /**< `#` */
|
||||||
|
|
||||||
|
Func, /**< `func` */
|
||||||
|
Return, /**< `return` */
|
||||||
|
If, /**< `if` */
|
||||||
|
Else, /**< `else` */
|
||||||
|
While, /**< `while` */
|
||||||
|
Public, /**< `public` */
|
||||||
|
Private, /**< `private` */
|
||||||
|
Pre, /**< `pre` */
|
||||||
|
Post, /**< `post` */
|
||||||
|
|
||||||
|
Pointerof, /**< `pointerof` */
|
||||||
|
Sizeof, /**< `sizeof` */
|
||||||
|
Lengthof, /**< `lengthof` */
|
||||||
|
|
||||||
|
S8, /**< `s8` */
|
||||||
|
U8, /**< `u8` */
|
||||||
|
S16, /**< `s16` */
|
||||||
|
U16, /**< `u16` */
|
||||||
|
S32, /**< `s32` */
|
||||||
|
U32, /**< `u32` */
|
||||||
|
S64, /**< `s64` */
|
||||||
|
U64, /**< `u64` */
|
||||||
|
|
||||||
|
// Errors:
|
||||||
|
UnexpectedCharacter,
|
||||||
|
UnexpectedEOF,
|
||||||
|
InvalidInteger,
|
||||||
|
EndOfFile,
|
||||||
|
} type;
|
||||||
|
union value {
|
||||||
|
std::nullptr_t null = nullptr;
|
||||||
|
std::uint64_t integer;
|
||||||
|
std::string_view string;
|
||||||
|
char character;
|
||||||
|
} value;
|
||||||
|
|
||||||
|
struct location {
|
||||||
|
std::string_view filepath;
|
||||||
|
std::size_t row = 0;
|
||||||
|
std::size_t col = 0;
|
||||||
|
} loc;
|
||||||
|
|
||||||
|
token(location loc, enum type type)
|
||||||
|
: loc(loc), type(type) {}
|
||||||
|
|
||||||
|
token(location loc, std::uint64_t integer)
|
||||||
|
: loc(loc), type(Integer) {
|
||||||
|
value.integer = integer;
|
||||||
|
}
|
||||||
|
|
||||||
|
token(location loc, enum type type, std::string_view string)
|
||||||
|
: loc(loc), type(type) {
|
||||||
|
value.string = string;
|
||||||
|
}
|
||||||
|
|
||||||
|
token(location loc, enum type type, char character)
|
||||||
|
: loc(loc), type(type) {
|
||||||
|
value.character = character;
|
||||||
|
}
|
||||||
|
|
||||||
|
friend std::ostream& operator<<(std::ostream& os, const token& token) {
|
||||||
|
switch (token.type) {
|
||||||
|
case token::Identifier: return os << token.value.string;
|
||||||
|
case token::String: return os << '"' << token.value.string << '"';
|
||||||
|
case token::Char: return os << '\'' << token.value.character << '\'';
|
||||||
|
case token::Integer: return os << token.value.integer;
|
||||||
|
case token::LParen: return os << "(";
|
||||||
|
case token::RParen: return os << ")";
|
||||||
|
case token::LBrace: return os << "{";
|
||||||
|
case token::RBrace: return os << "}";
|
||||||
|
case token::LBracket: return os << "[";
|
||||||
|
case token::RBracket: return os << "]";
|
||||||
|
case token::Semicolon: return os << ";";
|
||||||
|
case token::Colon: return os << ":";
|
||||||
|
case token::Comma: return os << ",";
|
||||||
|
case token::Dot: return os << ".";
|
||||||
|
case token::Plus: return os << "+";
|
||||||
|
case token::Minus: return os << "-";
|
||||||
|
case token::Star: return os << "*";
|
||||||
|
case token::Slash: return os << "/";
|
||||||
|
case token::Percent: return os << "%";
|
||||||
|
case token::DblLT: return os << "<<";
|
||||||
|
case token::DblGT: return os << ">>";
|
||||||
|
case token::Ampersand: return os << "&";
|
||||||
|
case token::Pipe: return os << "|";
|
||||||
|
case token::Hat: return os << "^";
|
||||||
|
case token::DblAmpersand: return os << "&&";
|
||||||
|
case token::DblPipe: return os << "||";
|
||||||
|
case token::DblPlus: return os << "++";
|
||||||
|
case token::DblMinus: return os << "--";
|
||||||
|
case token::Tilde: return os << "~";
|
||||||
|
case token::ExMark: return os << "!";
|
||||||
|
case token::CatEars: return os << "^^";
|
||||||
|
case token::Equals: return os << "=";
|
||||||
|
case token::PlusEquals: return os << "+=";
|
||||||
|
case token::MinusEquals: return os << "-=";
|
||||||
|
case token::StarEquals: return os << "*=";
|
||||||
|
case token::SlashEquals: return os << "/=";
|
||||||
|
case token::PercentEquals: return os << "%=";
|
||||||
|
case token::AmpersandEquals: return os << "&=";
|
||||||
|
case token::PipeEquals: return os << "|=";
|
||||||
|
case token::HatEquals: return os << "^=";
|
||||||
|
case token::DblEquals: return os << "==";
|
||||||
|
case token::ExEquals: return os << "!=";
|
||||||
|
case token::LessThan: return os << "<";
|
||||||
|
case token::LessEquals: return os << "<=";
|
||||||
|
case token::GreaterThan: return os << ">";
|
||||||
|
case token::GreaterEquals: return os << ">=";
|
||||||
|
case token::SlimArrow: return os << "->";
|
||||||
|
case token::FatArrow: return os << "=>";
|
||||||
|
case token::Monkey: return os << "@";
|
||||||
|
case token::Sha256: return os << "#";
|
||||||
|
case token::Func: return os << "func";
|
||||||
|
case token::Return: return os << "return";
|
||||||
|
case token::If: return os << "if";
|
||||||
|
case token::Else: return os << "else";
|
||||||
|
case token::While: return os << "while";
|
||||||
|
case token::Public: return os << "public";
|
||||||
|
case token::Private: return os << "private";
|
||||||
|
case token::Pre: return os << "pre";
|
||||||
|
case token::Post: return os << "post";
|
||||||
|
case token::Pointerof: return os << "pointerof";
|
||||||
|
case token::Sizeof: return os << "sizeof";
|
||||||
|
case token::Lengthof: return os << "lengthof";
|
||||||
|
case token::S8: return os << "s8";
|
||||||
|
case token::U8: return os << "u8";
|
||||||
|
case token::S16: return os << "s16";
|
||||||
|
case token::U16: return os << "u16";
|
||||||
|
case token::S32: return os << "s32";
|
||||||
|
case token::U32: return os << "u32";
|
||||||
|
case token::S64: return os << "s64";
|
||||||
|
case token::U64: return os << "u64";
|
||||||
|
case token::UnexpectedCharacter: return os << "Unexpected character `" << token.value.character << "`";
|
||||||
|
case token::UnexpectedEOF: return os << "Unexpected End Of File";
|
||||||
|
case token::InvalidInteger: return os << "Invalid Integer";
|
||||||
|
case token::EndOfFile: return os << "End Of File";
|
||||||
|
}
|
||||||
|
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using token_t = enum token::type;
|
||||||
|
|
||||||
|
} // namespace furc
|
||||||
|
|
||||||
|
#endif // FURC_FRONT_TOKEN_HPP
|
||||||
@@ -0,0 +1,455 @@
|
|||||||
|
#ifndef FURC_MIDDLE_IR_HPP
|
||||||
|
#define FURC_MIDDLE_IR_HPP
|
||||||
|
|
||||||
|
#include "furc/front/ast.hpp"
|
||||||
|
#include "furlang/arena.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <initializer_list>
|
||||||
|
#include <optional>
|
||||||
|
#include <stack>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace furc {
|
||||||
|
|
||||||
|
struct ir_operand {
|
||||||
|
enum type_e {
|
||||||
|
Integer = 0,
|
||||||
|
Register,
|
||||||
|
Variable,
|
||||||
|
Global,
|
||||||
|
Function,
|
||||||
|
Block,
|
||||||
|
BlockPair,
|
||||||
|
PhiPair,
|
||||||
|
} type;
|
||||||
|
union value_u {
|
||||||
|
std::uint64_t integer;
|
||||||
|
struct register_s {
|
||||||
|
std::uint64_t name : 54;
|
||||||
|
std::uint64_t ver : 10;
|
||||||
|
|
||||||
|
register_s() = default;
|
||||||
|
|
||||||
|
register_s(std::uint64_t id)
|
||||||
|
: name((id >> 10) & ((1ULL << 54) - 1)), ver((id >> 0) & ((1 << 10) - 1)) {}
|
||||||
|
|
||||||
|
register_s(std::uint64_t name, std::uint64_t ver)
|
||||||
|
: name(name), ver(ver) {}
|
||||||
|
|
||||||
|
operator std::uint64_t() const { return name << 10 | ver; }
|
||||||
|
} reg;
|
||||||
|
std::uint16_t variable;
|
||||||
|
std::uint16_t global;
|
||||||
|
std::uint64_t function;
|
||||||
|
std::uint64_t block;
|
||||||
|
struct block_pair_s {
|
||||||
|
std::uint64_t first;
|
||||||
|
std::uint64_t second;
|
||||||
|
} blockPair;
|
||||||
|
struct phi_pair_s {
|
||||||
|
register_s reg;
|
||||||
|
std::uint64_t block;
|
||||||
|
} phiPair;
|
||||||
|
|
||||||
|
value_u() = default;
|
||||||
|
|
||||||
|
value_u(std::uint64_t integer)
|
||||||
|
: integer(integer) {}
|
||||||
|
|
||||||
|
value_u(std::uint16_t variable)
|
||||||
|
: variable(variable) {}
|
||||||
|
|
||||||
|
value_u(register_s reg)
|
||||||
|
: reg(reg) {}
|
||||||
|
|
||||||
|
value_u(std::uint64_t first, std::uint64_t second)
|
||||||
|
: blockPair({ first, second }) {}
|
||||||
|
|
||||||
|
value_u(register_s reg, std::uint64_t block)
|
||||||
|
: phiPair({ reg, block }) {}
|
||||||
|
} value;
|
||||||
|
|
||||||
|
template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<value_u, Args...>>>
|
||||||
|
ir_operand(type_e type, Args&&... args)
|
||||||
|
: type(type), value(std::forward<Args>(args)...) {}
|
||||||
|
|
||||||
|
static ir_operand reg(std::uint64_t name, std::uint64_t ver) {
|
||||||
|
return { Register, value_u::register_s{ name, ver } };
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const ir_operand& rhs) const {
|
||||||
|
if (type != rhs.type) return false;
|
||||||
|
switch (type) {
|
||||||
|
case Integer: return value.integer == rhs.value.integer;
|
||||||
|
case Register: return value.reg.name == rhs.value.reg.name && value.reg.ver == rhs.value.reg.ver;
|
||||||
|
case Variable: return value.variable == rhs.value.variable;
|
||||||
|
case Global: return value.global == rhs.value.global;
|
||||||
|
case Function: return value.function == rhs.value.function;
|
||||||
|
case Block: return value.block == rhs.value.block;
|
||||||
|
case BlockPair:
|
||||||
|
return value.blockPair.first == rhs.value.blockPair.first &&
|
||||||
|
value.blockPair.second == rhs.value.blockPair.second;
|
||||||
|
case PhiPair:
|
||||||
|
return value.phiPair.block == rhs.value.phiPair.block &&
|
||||||
|
value.phiPair.reg.name == rhs.value.phiPair.reg.name &&
|
||||||
|
value.phiPair.reg.ver == rhs.value.phiPair.reg.ver;
|
||||||
|
}
|
||||||
|
throw std::runtime_error("unreachable");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ir_type {
|
||||||
|
enum type_e {
|
||||||
|
Void = 0,
|
||||||
|
S8,
|
||||||
|
U8,
|
||||||
|
S16,
|
||||||
|
U16,
|
||||||
|
S32,
|
||||||
|
U32,
|
||||||
|
S64,
|
||||||
|
U64,
|
||||||
|
} type = Void;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Add data types to instructions (like mov QWORD ... in x86 assembly)
|
||||||
|
struct ir_instruction {
|
||||||
|
enum type_e {
|
||||||
|
Move = 0,
|
||||||
|
Call,
|
||||||
|
Branch,
|
||||||
|
BranchCond,
|
||||||
|
Return,
|
||||||
|
Phi,
|
||||||
|
|
||||||
|
Add,
|
||||||
|
Sub,
|
||||||
|
Mul,
|
||||||
|
Div,
|
||||||
|
Mod,
|
||||||
|
|
||||||
|
Shl,
|
||||||
|
Shr,
|
||||||
|
BinAnd,
|
||||||
|
BinOr,
|
||||||
|
BinXor,
|
||||||
|
And,
|
||||||
|
Or,
|
||||||
|
|
||||||
|
Eq,
|
||||||
|
NotEq,
|
||||||
|
LessThan,
|
||||||
|
LessEq,
|
||||||
|
GreaterThan,
|
||||||
|
GreaterEq,
|
||||||
|
|
||||||
|
Positive,
|
||||||
|
Negative,
|
||||||
|
Increment,
|
||||||
|
Decrement,
|
||||||
|
BinNot,
|
||||||
|
Not,
|
||||||
|
|
||||||
|
Sizeof,
|
||||||
|
Pointerof,
|
||||||
|
Lenof,
|
||||||
|
} type;
|
||||||
|
std::optional<ir_operand> destination;
|
||||||
|
std::vector<ir_operand> sources;
|
||||||
|
|
||||||
|
ir_instruction(type_e type,
|
||||||
|
std::optional<ir_operand> destination = {},
|
||||||
|
std::initializer_list<ir_operand> sources = {})
|
||||||
|
: type(type), destination(destination), sources(sources) {}
|
||||||
|
|
||||||
|
static constexpr bool is_terminating(type_e type) {
|
||||||
|
switch (type) {
|
||||||
|
case Branch:
|
||||||
|
case BranchCond:
|
||||||
|
case Return: return true;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const ir_instruction& rhs) const {
|
||||||
|
return type == rhs.type && destination == rhs.destination && sources == rhs.sources;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ir_basic_block {
|
||||||
|
std::vector<ir_instruction> instructions;
|
||||||
|
|
||||||
|
bool is_terminated() const {
|
||||||
|
return !instructions.empty() && ir_instruction::is_terminating(instructions.back().type);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ir_variable {
|
||||||
|
ir_variable() = default;
|
||||||
|
|
||||||
|
ir_variable(ir_type type)
|
||||||
|
: type(type) {}
|
||||||
|
|
||||||
|
virtual ~ir_variable() = default;
|
||||||
|
|
||||||
|
ir_variable(ir_variable&&) noexcept = default;
|
||||||
|
ir_variable& operator=(ir_variable&&) noexcept = default;
|
||||||
|
|
||||||
|
ir_variable(const ir_variable&) = default;
|
||||||
|
ir_variable& operator=(const ir_variable&) = default;
|
||||||
|
|
||||||
|
ir_type type;
|
||||||
|
|
||||||
|
virtual ir_operand operand() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ir_module_variable : ir_variable {
|
||||||
|
ir_module_variable(ir_type type, std::uint16_t name)
|
||||||
|
: ir_variable(type), name(name) {}
|
||||||
|
|
||||||
|
std::uint16_t name;
|
||||||
|
|
||||||
|
ir_operand operand() const final { return { ir_operand::Global, name }; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ir_function_variable : ir_variable {
|
||||||
|
ir_function_variable(ir_type type, std::uint64_t name)
|
||||||
|
: ir_variable(type), name(name) {}
|
||||||
|
|
||||||
|
std::uint64_t name;
|
||||||
|
|
||||||
|
ir_operand operand() const final { return { ir_operand::Variable, name }; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ir_scope {
|
||||||
|
ir_scope() = default;
|
||||||
|
virtual ~ir_scope() = default;
|
||||||
|
|
||||||
|
ir_scope(ir_scope&&) noexcept = default;
|
||||||
|
ir_scope& operator=(ir_scope&&) noexcept = default;
|
||||||
|
|
||||||
|
ir_scope(const ir_scope&) = default;
|
||||||
|
ir_scope& operator=(const ir_scope&) = default;
|
||||||
|
|
||||||
|
ir_scope* previous = nullptr;
|
||||||
|
|
||||||
|
std::unordered_map<std::string, ir_variable*> variables;
|
||||||
|
|
||||||
|
const ir_variable* variable(const std::string& name) const {
|
||||||
|
if (auto it = variables.find(name); it != variables.end()) return it->second;
|
||||||
|
return (previous != nullptr) ? previous->variable(name) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual const ir_variable* allocate(furlang::arena& arena, const std::string& name, ir_type type) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ir_function : ir_scope {
|
||||||
|
enum type_e {
|
||||||
|
Normal = 0,
|
||||||
|
Import,
|
||||||
|
Native,
|
||||||
|
} type = Normal;
|
||||||
|
enum access_e {
|
||||||
|
Public = 0,
|
||||||
|
Private,
|
||||||
|
} access = Public;
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
std::vector<ir_type> params;
|
||||||
|
ir_type retType;
|
||||||
|
std::vector<ir_basic_block> blocks;
|
||||||
|
|
||||||
|
std::uint64_t regCount = 0;
|
||||||
|
std::uint64_t varCount = 0;
|
||||||
|
|
||||||
|
const ir_variable* allocate(furlang::arena& arena, const std::string& name, ir_type type) final {
|
||||||
|
return variables[name] = arena.allocate<ir_function_variable>(type, varCount++);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ir_function from_name(std::string&& name) {
|
||||||
|
ir_function func;
|
||||||
|
func.name = std::move(name);
|
||||||
|
return func;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ir_module : ir_scope {
|
||||||
|
std::vector<ir_function*> functions;
|
||||||
|
furlang::arena arena;
|
||||||
|
|
||||||
|
std::uint16_t varCount = 0;
|
||||||
|
|
||||||
|
const ir_variable* allocate(furlang::arena& arena, const std::string& name, ir_type type) final {
|
||||||
|
return variables[name] = arena.allocate<ir_module_variable>(type, varCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
ir_function* add_function(ir_function&& function) {
|
||||||
|
return functions.emplace_back(arena.allocate<ir_function>(std::move(function)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ir_context {
|
||||||
|
ir_context(ir_function* function)
|
||||||
|
: function(function) {
|
||||||
|
if (function->blocks.empty()) new_last();
|
||||||
|
blockPtr = &function->blocks.front();
|
||||||
|
}
|
||||||
|
|
||||||
|
~ir_context() {
|
||||||
|
if (blockPtr == nullptr) return;
|
||||||
|
if (!blockPtr->is_terminated()) {
|
||||||
|
if (blockIdx + 1 == function->blocks.size()) {
|
||||||
|
add_instr(ir_instruction::Return);
|
||||||
|
} else {
|
||||||
|
add_instr(ir_instruction::Branch, ir_operand{ ir_operand::Block, blockIdx + 1 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ir_context(ir_context&& other) noexcept
|
||||||
|
: function(other.function), blockIdx(other.blockIdx), blockPtr(other.blockPtr) {
|
||||||
|
other.function = nullptr;
|
||||||
|
other.blockIdx = 0;
|
||||||
|
other.blockPtr = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ir_context& operator=(ir_context&& other) noexcept {
|
||||||
|
if (this == &other) return *this;
|
||||||
|
function = other.function;
|
||||||
|
blockIdx = other.blockIdx;
|
||||||
|
blockPtr = other.blockPtr;
|
||||||
|
|
||||||
|
other.function = nullptr;
|
||||||
|
other.blockIdx = 0;
|
||||||
|
other.blockPtr = nullptr;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ir_context(const ir_context&) = delete;
|
||||||
|
ir_context& operator=(const ir_context&) = delete;
|
||||||
|
|
||||||
|
template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<ir_instruction, Args...>>>
|
||||||
|
ir_instruction& add_instr(Args&&... args) {
|
||||||
|
auto it = blockPtr->instructions.end();
|
||||||
|
if (!blockPtr->instructions.empty() && ir_instruction::is_terminating(blockPtr->instructions.back().type)) --it;
|
||||||
|
it = blockPtr->instructions.emplace(it, std::forward<Args>(args)...);
|
||||||
|
if (ir_instruction::is_terminating(it->type) && it + 1 != blockPtr->instructions.end())
|
||||||
|
blockPtr->instructions.pop_back();
|
||||||
|
return *it;
|
||||||
|
}
|
||||||
|
|
||||||
|
void terminate() { add_instr(ir_instruction::Return); }
|
||||||
|
|
||||||
|
void terminate(ir_operand value) {
|
||||||
|
ir_instruction instr = { ir_instruction::Return };
|
||||||
|
instr.sources.emplace_back(value);
|
||||||
|
add_instr(std::move(instr));
|
||||||
|
}
|
||||||
|
|
||||||
|
void terminate(std::uint64_t block) { add_instr(ir_instruction::Branch, ir_operand{ ir_operand::Block, block }); }
|
||||||
|
|
||||||
|
ir_instruction* terminate(ir_operand cond, std::uint64_t thenBranch, std::uint64_t elseBranch) {
|
||||||
|
return &add_instr(ir_instruction{ ir_instruction::BranchCond,
|
||||||
|
ir_operand{ ir_operand::BlockPair, thenBranch, elseBranch },
|
||||||
|
{ cond } });
|
||||||
|
}
|
||||||
|
|
||||||
|
ir_context& new_next() {
|
||||||
|
if (blockPtr->instructions.empty()) return *this;
|
||||||
|
auto it = function->blocks.begin() + static_cast<std::ptrdiff_t>(++blockIdx);
|
||||||
|
if (!blockPtr->is_terminated()) terminate(blockIdx);
|
||||||
|
blockPtr = &*function->blocks.emplace(it);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ir_context& new_last() {
|
||||||
|
blockIdx = function->blocks.size();
|
||||||
|
blockPtr = &*function->blocks.emplace(function->blocks.end());
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ir_context& go(std::uint64_t block) {
|
||||||
|
blockIdx = std::min(block, function->blocks.size() - 1);
|
||||||
|
blockPtr = function->blocks.data() + static_cast<std::ptrdiff_t>(blockIdx);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ir_context& go_next() { return go(blockIdx + 1); }
|
||||||
|
ir_context& go_last() { return go(std::min<std::uint64_t>(0, function->blocks.size() - 1)); }
|
||||||
|
|
||||||
|
ir_operand last_register() const { return { ir_operand::Register, function->regCount - 1 }; }
|
||||||
|
ir_operand next_register() const { return { ir_operand::Register, function->regCount++ }; }
|
||||||
|
|
||||||
|
static ir_operand block_op(std::uint64_t blockIdx) { return { ir_operand::Block, blockIdx }; }
|
||||||
|
|
||||||
|
ir_function* function = nullptr;
|
||||||
|
std::uint64_t blockIdx = 0;
|
||||||
|
ir_basic_block* blockPtr = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ir_generator final : public ast_visitor {
|
||||||
|
public:
|
||||||
|
ir_generator()
|
||||||
|
: m_initContext(m_module.add_function(ir_function::from_name("module$init"))) {}
|
||||||
|
|
||||||
|
void finalize() {
|
||||||
|
m_module.functions.front()->blocks.emplace_back().instructions.push_back(
|
||||||
|
ir_instruction{ ir_instruction::Return });
|
||||||
|
}
|
||||||
|
|
||||||
|
ir_module build() {
|
||||||
|
m_scope = nullptr;
|
||||||
|
m_context = {};
|
||||||
|
m_initContext.blockPtr = nullptr;
|
||||||
|
return std::move(m_module);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ir_module generate(const ast_node& node) {
|
||||||
|
ir_generator gen;
|
||||||
|
node.accept(gen);
|
||||||
|
gen.finalize();
|
||||||
|
return gen.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
static ir_module generate(const ast& tree) {
|
||||||
|
ir_generator gen;
|
||||||
|
for (const auto& node : tree.decls)
|
||||||
|
node->accept(gen);
|
||||||
|
gen.finalize();
|
||||||
|
return gen.build();
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
void visit_comp_stmt_node(const comp_stmt_node& node) override;
|
||||||
|
void visit_if_stmt_node(const if_stmt_node& node) override;
|
||||||
|
void visit_while_stmt_node(const while_stmt_node& node) override;
|
||||||
|
void visit_return_stmt_node(const return_stmt_node& node) override;
|
||||||
|
void visit_var_decl_node(const var_decl_node& node) override;
|
||||||
|
void visit_func_decl_node(const func_decl_node& node) override;
|
||||||
|
void visit_var_read_expr_node(const var_read_expr_node& node) override;
|
||||||
|
void visit_func_call_expr_node(const func_call_expr_node& node) override;
|
||||||
|
void visit_group_expr_node(const group_expr_node& node) override;
|
||||||
|
void visit_binary_op_expr_node(const binary_op_expr_node& node) override;
|
||||||
|
void visit_unary_op_expr_node(const unary_op_expr_node& node) override;
|
||||||
|
void visit_if_expr_node(const if_expr_node& node) override;
|
||||||
|
void visit_int_lit_node(const int_lit_node& node) override;
|
||||||
|
void visit_char_lit_node(const char_lit_node& node) override;
|
||||||
|
private:
|
||||||
|
ir_context& context() { return m_context.top(); }
|
||||||
|
private:
|
||||||
|
ir_module m_module;
|
||||||
|
ir_scope* m_scope = &m_module;
|
||||||
|
std::stack<ir_context> m_context;
|
||||||
|
|
||||||
|
ir_context m_initContext;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace furc
|
||||||
|
|
||||||
|
#endif // FURC_MIDDLE_IR_HPP
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
/**
|
||||||
|
* SSA destruction (out-of-SSA phase) for register-based targets based on "Mechanizing Conventional SSA for a
|
||||||
|
* Verified Destruction with Coalescing" by Delphine Demange and Yon Fernandez de Retana
|
||||||
|
* (https://dl.acm.org/doi/pdf/10.1145/2892208.2892222 09/11/2026).
|
||||||
|
*/
|
||||||
|
#ifndef FURC_MIDDLE_REG_GEN_HPP
|
||||||
|
#define FURC_MIDDLE_REG_GEN_HPP
|
||||||
|
|
||||||
|
#include "furc/middle/ir.hpp"
|
||||||
|
#include "furc/middle/ssa.hpp"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace furc {
|
||||||
|
|
||||||
|
class reg_gen {
|
||||||
|
public:
|
||||||
|
class disjoint_set {
|
||||||
|
public:
|
||||||
|
std::uint64_t find(std::uint64_t var) {
|
||||||
|
if (m_parents.find(var) == m_parents.end()) m_parents.emplace(var, var);
|
||||||
|
if (m_parents[var] == var) return var;
|
||||||
|
return find(m_parents[var]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void unite(std::uint64_t var1, std::uint64_t var2) {
|
||||||
|
std::uint64_t rep1 = find(var1);
|
||||||
|
std::uint64_t rep2 = find(var2);
|
||||||
|
if (rep1 != rep2) m_parents[rep1] = rep2;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::unordered_map<std::uint64_t, std::uint64_t> m_parents;
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
struct block_info {
|
||||||
|
std::unordered_set<std::uint64_t> defs;
|
||||||
|
std::unordered_set<std::uint64_t> uses;
|
||||||
|
std::unordered_set<std::uint64_t> liveIn;
|
||||||
|
std::unordered_set<std::uint64_t> liveOut;
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
reg_gen(ir_function& func, ssa& ssa) {
|
||||||
|
std::vector<block_info> lifeBlocks;
|
||||||
|
live_analysis(lifeBlocks, func.blocks, ssa.cfgBlocks);
|
||||||
|
remove_interference(func.blocks, lifeBlocks);
|
||||||
|
merge(func.blocks);
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
static void live_analysis(std::vector<block_info>& lifeBlocks,
|
||||||
|
const std::vector<ir_basic_block>& irBlocks,
|
||||||
|
const std::vector<ssa::cfg_block>& cfgBlocks) {
|
||||||
|
lifeBlocks.resize(irBlocks.size());
|
||||||
|
for (std::uint64_t i = 0; i < irBlocks.size(); ++i) {
|
||||||
|
const auto& irBlock = irBlocks[i];
|
||||||
|
auto& block = lifeBlocks[i];
|
||||||
|
|
||||||
|
for (const auto& instr : irBlock.instructions) {
|
||||||
|
for (const auto& op : instr.sources) {
|
||||||
|
if (op.type != ir_operand::Register) continue;
|
||||||
|
if (block.defs.find(op.value.reg) != block.defs.end()) continue;
|
||||||
|
block.uses.insert(op.value.reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!instr.destination.has_value() || instr.destination->type != ir_operand::Register) continue;
|
||||||
|
block.defs.insert(instr.destination->value.reg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool changed = true;
|
||||||
|
while (changed) {
|
||||||
|
changed = false;
|
||||||
|
|
||||||
|
for (std::uint64_t i = 0; i < irBlocks.size(); ++i) {
|
||||||
|
const auto& irBlock = irBlocks[i];
|
||||||
|
auto& block = lifeBlocks[i];
|
||||||
|
|
||||||
|
std::unordered_set<std::uint64_t> newSet;
|
||||||
|
for (auto succ : cfgBlocks[i].sucs) {
|
||||||
|
newSet.insert(lifeBlocks[succ].liveIn.begin(), lifeBlocks[succ].liveIn.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newSet != block.liveOut) {
|
||||||
|
block.liveOut = newSet;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
newSet.clear();
|
||||||
|
newSet.insert(block.uses.begin(), block.uses.end());
|
||||||
|
for (const auto& var : block.liveOut) {
|
||||||
|
if (block.defs.find(var) != block.defs.end()) continue;
|
||||||
|
newSet.insert(var);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newSet != block.liveIn) {
|
||||||
|
block.liveIn = newSet;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void remove_interference(std::vector<ir_basic_block>& irBlocks, const std::vector<block_info>& lifeBlocks) {
|
||||||
|
for (std::uint64_t i = 0; i < irBlocks.size(); ++i) {
|
||||||
|
auto& irBlock = irBlocks[i];
|
||||||
|
|
||||||
|
for (auto it = irBlock.instructions.begin(), end = irBlock.instructions.end();
|
||||||
|
it != end && it->type == ir_instruction::Phi;
|
||||||
|
++it) {
|
||||||
|
assert(it->destination.has_value() && it->destination->type == ir_operand::Register);
|
||||||
|
const auto& phiDst = it->destination->value.reg;
|
||||||
|
|
||||||
|
for (auto& op : it->sources) {
|
||||||
|
assert(op.type == ir_operand::PhiPair);
|
||||||
|
const auto& predBlock = lifeBlocks[op.value.phiPair.block];
|
||||||
|
auto& phiArg = op.value.phiPair.reg;
|
||||||
|
if (predBlock.liveOut.count(phiArg) == 0 || phiArg == phiDst) continue;
|
||||||
|
|
||||||
|
auto oldArg = phiArg;
|
||||||
|
phiArg.ver = 0; // TODO: Allocate temporary registers
|
||||||
|
|
||||||
|
auto& irBlock = irBlocks[op.value.phiPair.block];
|
||||||
|
assert(!irBlock.instructions.empty());
|
||||||
|
auto it = irBlock.instructions.end() - 1;
|
||||||
|
if (ir_instruction::is_terminating(it->type)) --it;
|
||||||
|
irBlock.instructions.emplace(it,
|
||||||
|
ir_instruction{ ir_instruction::Move,
|
||||||
|
ir_operand::reg(phiArg.name, phiArg.ver),
|
||||||
|
{ ir_operand::reg(oldArg.name, oldArg.ver) } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void merge(std::vector<ir_basic_block>& irBlocks) {
|
||||||
|
disjoint_set dj;
|
||||||
|
|
||||||
|
for (const auto& block : irBlocks) {
|
||||||
|
for (const auto& instr : block.instructions) {
|
||||||
|
if (instr.type != ir_instruction::Phi) break;
|
||||||
|
|
||||||
|
assert(instr.destination.has_value() && instr.destination->type == ir_operand::Register);
|
||||||
|
const auto& phiDst = instr.destination->value.reg;
|
||||||
|
dj.find(phiDst);
|
||||||
|
|
||||||
|
for (const auto& op : instr.sources) {
|
||||||
|
assert(op.type == ir_operand::PhiPair);
|
||||||
|
const auto& predBlock = op.value.phiPair.block;
|
||||||
|
const auto& phiArg = op.value.phiPair.reg;
|
||||||
|
dj.unite(phiDst, phiArg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& block : irBlocks) {
|
||||||
|
auto it = block.instructions.begin();
|
||||||
|
while (it != block.instructions.end() && it->type == ir_instruction::Phi) {
|
||||||
|
it = block.instructions.erase(it);
|
||||||
|
}
|
||||||
|
for (; it != block.instructions.end(); ++it) {
|
||||||
|
for (auto& op : it->sources) {
|
||||||
|
if (op.type != ir_operand::Register) continue;
|
||||||
|
op.value.reg = dj.find(op.value.reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!it->destination.has_value() || it->destination->type != ir_operand::Register) continue;
|
||||||
|
it->destination->value.reg = dj.find(it->destination->value.reg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace furc
|
||||||
|
|
||||||
|
#endif // FURC_MIDDLE_REG_GEN_HPP
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
#ifndef FURC_MIDDLE_SSA_HPP
|
||||||
|
#define FURC_MIDDLE_SSA_HPP
|
||||||
|
|
||||||
|
#include "furc/middle/ir.hpp"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <limits>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace furc {
|
||||||
|
|
||||||
|
class ssa {
|
||||||
|
public:
|
||||||
|
struct cfg_block {
|
||||||
|
std::unordered_set<std::uint64_t> preds;
|
||||||
|
std::unordered_set<std::uint64_t> sucs;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ssa_block {
|
||||||
|
std::size_t order = 0;
|
||||||
|
|
||||||
|
std::uint64_t idom = -1;
|
||||||
|
|
||||||
|
std::unordered_set<std::uint64_t> children; // Children of the block in dominator tree
|
||||||
|
|
||||||
|
// Dominance Frontiers
|
||||||
|
std::unordered_set<std::uint64_t> df;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct register_info {
|
||||||
|
std::unordered_set<std::uint64_t> sites; // Definition Sites
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
ssa(ir_function& func) {
|
||||||
|
registers.resize(func.regCount);
|
||||||
|
compute_cfg(func.blocks, cfgBlocks);
|
||||||
|
collect_registers(func.blocks, registers, globals);
|
||||||
|
|
||||||
|
std::vector<std::uint64_t> order;
|
||||||
|
compute_rpo(cfgBlocks, ssaBlocks, order);
|
||||||
|
|
||||||
|
build_dtree(cfgBlocks, ssaBlocks, order);
|
||||||
|
compute_dfrontiers(cfgBlocks, ssaBlocks);
|
||||||
|
|
||||||
|
ssaification(func.blocks, cfgBlocks, ssaBlocks, registers, globals);
|
||||||
|
rename(func.blocks, func.regCount, cfgBlocks, ssaBlocks, order);
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
static void compute_cfg(const std::vector<ir_basic_block>& irBlocks, std::vector<cfg_block>& cfgBlocks);
|
||||||
|
|
||||||
|
static void collect_registers(const std::vector<ir_basic_block>& irBlocks,
|
||||||
|
std::vector<register_info>& registers,
|
||||||
|
std::unordered_set<std::uint64_t>& globals);
|
||||||
|
|
||||||
|
static void build_dtree(const std::vector<cfg_block>& cfgBlocks,
|
||||||
|
std::vector<ssa_block>& ssaBlocks,
|
||||||
|
const std::vector<std::size_t>& order);
|
||||||
|
|
||||||
|
static void compute_dfrontiers(const std::vector<cfg_block>& cfgBlocks, std::vector<ssa_block>& ssaBlocks);
|
||||||
|
|
||||||
|
static void compute_rpo(std::vector<cfg_block>& cfgBlocks,
|
||||||
|
std::vector<ssa_block>& ssaBlocks,
|
||||||
|
std::vector<std::size_t>& order);
|
||||||
|
|
||||||
|
static void ssaification(std::vector<ir_basic_block>& irBlocks,
|
||||||
|
const std::vector<cfg_block>& cfgBlocks,
|
||||||
|
const std::vector<ssa_block>& ssaBlocks,
|
||||||
|
const std::vector<register_info>& registers,
|
||||||
|
const std::unordered_set<std::uint64_t>& globals);
|
||||||
|
|
||||||
|
static void rename(std::vector<ir_basic_block>& irBlocks,
|
||||||
|
std::size_t regCount,
|
||||||
|
const std::vector<cfg_block>& cfgBlocks,
|
||||||
|
std::vector<ssa_block>& ssaBlocks,
|
||||||
|
const std::vector<std::uint64_t>& order);
|
||||||
|
private:
|
||||||
|
static void rename_rec(std::vector<std::uint64_t>& counters,
|
||||||
|
std::vector<std::stack<std::uint64_t>>& stacks,
|
||||||
|
std::vector<ir_basic_block>& irBlocks,
|
||||||
|
const std::vector<cfg_block>& cfgBlocks,
|
||||||
|
const std::vector<ssa_block>& ssaBlocks,
|
||||||
|
std::size_t blockIdx);
|
||||||
|
private:
|
||||||
|
static void rpo_dfs(std::unordered_set<std::size_t>& visited,
|
||||||
|
std::vector<std::size_t>& order,
|
||||||
|
std::size_t block,
|
||||||
|
const std::vector<cfg_block>& blocks);
|
||||||
|
|
||||||
|
static std::size_t intersect(std::vector<ssa_block>& m_blocks, std::size_t b1, std::size_t b2);
|
||||||
|
public:
|
||||||
|
std::vector<cfg_block> cfgBlocks;
|
||||||
|
std::vector<ssa_block> ssaBlocks;
|
||||||
|
std::vector<register_info> registers;
|
||||||
|
std::unordered_set<std::uint64_t> globals;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace furc
|
||||||
|
|
||||||
|
#endif // FURC_MIDDLE_SSA_HPP
|
||||||
@@ -106,6 +106,55 @@ private:
|
|||||||
region* m_tail = nullptr;
|
region* m_tail = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class arena_allocator {
|
||||||
|
template <typename>
|
||||||
|
friend class arena_allocator;
|
||||||
|
public:
|
||||||
|
using value_type = T;
|
||||||
|
public:
|
||||||
|
explicit arena_allocator(arena& arena) noexcept
|
||||||
|
: m_arena(&arena) {}
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
arena_allocator(const arena_allocator<U>& other) noexcept
|
||||||
|
: m_arena(other.m_arena) {}
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
arena_allocator& operator=(const arena_allocator<U>& other) noexcept {
|
||||||
|
if (this == &other) return *this;
|
||||||
|
m_arena = other.m_arena;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
arena_allocator(arena_allocator<U>&& other) noexcept
|
||||||
|
: m_arena(std::move(other.m_arena)) {}
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
arena_allocator& operator=(arena_allocator<U>&& other) noexcept {
|
||||||
|
if (this == &other) return *this;
|
||||||
|
m_arena = std::move(other.m_arena);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
[[nodiscard]] T* allocate(std::size_t count = 1) { return m_arena->allocate<T>(count); }
|
||||||
|
|
||||||
|
void deallocate(T* ptr, std::size_t count) noexcept {}
|
||||||
|
public:
|
||||||
|
template <typename U>
|
||||||
|
bool operator==(const arena_allocator<U>& other) const noexcept {
|
||||||
|
return m_arena == other.m_arena;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
bool operator!=(const arena_allocator<U>& other) const noexcept {
|
||||||
|
return m_arena != other.m_arena;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
arena* m_arena;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace furlang
|
} // namespace furlang
|
||||||
|
|
||||||
#endif // FURLANG_ARENA_HPP
|
#endif // FURLANG_ARENA_HPP
|
||||||
@@ -2,7 +2,9 @@
|
|||||||
#define FURLANG_RESULT_HPP
|
#define FURLANG_RESULT_HPP
|
||||||
|
|
||||||
#include <exception>
|
#include <exception>
|
||||||
|
#include <optional>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
namespace furlang {
|
namespace furlang {
|
||||||
@@ -43,6 +45,8 @@ public:
|
|||||||
const char* what() const noexcept override { return "bad result access"; }
|
const char* what() const noexcept override { return "bad result access"; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct error_tag {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Result.
|
* @brief Result.
|
||||||
*
|
*
|
||||||
@@ -51,7 +55,7 @@ public:
|
|||||||
* @tparam R Value type.
|
* @tparam R Value type.
|
||||||
* @tparam E Error type.
|
* @tparam E Error type.
|
||||||
*/
|
*/
|
||||||
template <typename R, typename E>
|
template <typename E, typename R = void>
|
||||||
class result {
|
class result {
|
||||||
public:
|
public:
|
||||||
using value_type = std::remove_reference_t<R>; /**< Value type. */
|
using value_type = std::remove_reference_t<R>; /**< Value type. */
|
||||||
@@ -63,6 +67,10 @@ public:
|
|||||||
using error_reference = error_type&; /**< Error reference type. */
|
using error_reference = error_type&; /**< Error reference type. */
|
||||||
using error_const_reference = const error_type&; /**< Error const reference type. */
|
using error_const_reference = const error_type&; /**< Error const reference type. */
|
||||||
public:
|
public:
|
||||||
|
template <typename Other>
|
||||||
|
result(const result<E, Other>& error)
|
||||||
|
: result(error_tag{}, error.error()) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Construct a new result.
|
* @brief Construct a new result.
|
||||||
*
|
*
|
||||||
@@ -82,7 +90,7 @@ public:
|
|||||||
*
|
*
|
||||||
* @param args Variadic arguments to construct the value with.
|
* @param args Variadic arguments to construct the value with.
|
||||||
*/
|
*/
|
||||||
template <typename... Args>
|
template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<value_type, Args...>>>
|
||||||
result(Args&&... args) {
|
result(Args&&... args) {
|
||||||
new (&m_value.result) value_type(std::forward<Args>(args)...);
|
new (&m_value.result) value_type(std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
@@ -92,7 +100,7 @@ public:
|
|||||||
*
|
*
|
||||||
* @param error Error to copy.
|
* @param error Error to copy.
|
||||||
*/
|
*/
|
||||||
explicit result(const error_type& error)
|
result(error_tag tag, const error_type& error)
|
||||||
: m_error(true) {
|
: m_error(true) {
|
||||||
new (&m_value.error) error_type(error);
|
new (&m_value.error) error_type(error);
|
||||||
}
|
}
|
||||||
@@ -102,7 +110,7 @@ public:
|
|||||||
*
|
*
|
||||||
* @param error Error to move.
|
* @param error Error to move.
|
||||||
*/
|
*/
|
||||||
explicit result(error_type&& error)
|
result(error_tag tag, error_type&& error)
|
||||||
: m_error(true) {
|
: m_error(true) {
|
||||||
new (&m_value.error) error_type(std::move(error));
|
new (&m_value.error) error_type(std::move(error));
|
||||||
}
|
}
|
||||||
@@ -166,6 +174,16 @@ public:
|
|||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
public:
|
||||||
|
template <typename ResultFwd, typename = std::enable_if_t<std::is_constructible_v<R, ResultFwd>>>
|
||||||
|
static result ok(ResultFwd&& value) {
|
||||||
|
return { std::forward<ResultFwd>(value) };
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ErrorFwd, typename = std::enable_if_t<std::is_constructible_v<E, ErrorFwd>>>
|
||||||
|
static result error(ErrorFwd&& value) {
|
||||||
|
return { error_tag{}, std::forward<ErrorFwd>(value) };
|
||||||
|
}
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Checks if this result contains a value.
|
* @brief Checks if this result contains a value.
|
||||||
@@ -364,6 +382,34 @@ private:
|
|||||||
bool m_error = false;
|
bool m_error = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename E>
|
||||||
|
class result<E, void> {
|
||||||
|
public:
|
||||||
|
using value_type = std::remove_reference_t<E>;
|
||||||
|
using reference = value_type&;
|
||||||
|
using const_reference = const value_type&;
|
||||||
|
public:
|
||||||
|
result() = default;
|
||||||
|
|
||||||
|
result(const value_type& value)
|
||||||
|
: m_error(true), m_value(value) {}
|
||||||
|
|
||||||
|
result(value_type&& value)
|
||||||
|
: m_error(true), m_value(std::move(value)) {}
|
||||||
|
|
||||||
|
template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<value_type, Args...>>>
|
||||||
|
result(Args&&... args)
|
||||||
|
: m_error(true), m_value(std::forward<Args>(args)...) {}
|
||||||
|
public:
|
||||||
|
bool has_value() const { return !m_error; }
|
||||||
|
bool has_error() const { return m_error; }
|
||||||
|
|
||||||
|
const value_type& error() const { return *m_value; }
|
||||||
|
private:
|
||||||
|
std::optional<value_type> m_value;
|
||||||
|
bool m_error = false;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace furlang
|
} // namespace furlang
|
||||||
|
|
||||||
#endif // FURLANG_RESULT_HPP
|
#endif // FURLANG_RESULT_HPP
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
#ifndef FURLANG_SERIALIZATION_CODEC_HPP
|
||||||
|
#define FURLANG_SERIALIZATION_CODEC_HPP
|
||||||
|
|
||||||
|
#include "furlang/result.hpp"
|
||||||
|
#include "furlang/serialization/io.hpp"
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace furlang {
|
||||||
|
namespace serialization {
|
||||||
|
|
||||||
|
template <typename Codec, typename T>
|
||||||
|
using codec_encode_result_t = decltype(std::declval<Codec>().encode(std::declval<writer&>(), std::declval<const T&>()));
|
||||||
|
|
||||||
|
template <typename Codec, typename T>
|
||||||
|
using codec_decode_result_t = decltype(std::declval<Codec>().decode(std::declval<reader&>()));
|
||||||
|
|
||||||
|
template <typename Codec, typename T, typename = void>
|
||||||
|
struct is_codec : std::false_type {};
|
||||||
|
|
||||||
|
template <typename Codec, typename T>
|
||||||
|
struct is_codec<Codec, T, std::void_t<codec_encode_result_t<Codec, T>, codec_decode_result_t<Codec, T>>>
|
||||||
|
: std::true_type {};
|
||||||
|
|
||||||
|
template <typename Codec, typename T>
|
||||||
|
constexpr bool is_codec_v = is_codec<Codec, T>::value;
|
||||||
|
|
||||||
|
template <typename Codec, typename T, typename = std::enable_if_t<is_codec_v<Codec, T>>>
|
||||||
|
result<error> encode(Codec& codec, writer& writer, const T& value) {
|
||||||
|
return codec.encode(writer, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Codec, typename T, typename = std::enable_if_t<is_codec_v<Codec, T>>>
|
||||||
|
result<error, T> decode(Codec& codec, reader& reader) {
|
||||||
|
return codec.decode(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename = void>
|
||||||
|
class codec;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct codec<T, std::enable_if_t<std::is_integral_v<T>>> {
|
||||||
|
result<error> encode(writer& writer, const T& value) { return writer.write_int(value); }
|
||||||
|
result<error, T> decode(reader& reader) { return reader.read_int(T{}); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct codec<std::string> {
|
||||||
|
result<error> encode(writer& writer, const std::string& value) { return writer.write_string(value); }
|
||||||
|
|
||||||
|
result<error, std::string> decode(reader& reader) { return reader.read_string(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace serialization
|
||||||
|
} // namespace furlang
|
||||||
|
|
||||||
|
#endif // FURLANG_SERIALIZATION_CODEC_HPP
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#ifndef FURLANG_SERIALIZATION_ERROR_HPP
|
||||||
|
#define FURLANG_SERIALIZATION_ERROR_HPP
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace furlang {
|
||||||
|
namespace serialization {
|
||||||
|
|
||||||
|
enum class error_code {
|
||||||
|
EndOfFile,
|
||||||
|
InvalidData,
|
||||||
|
InvalidTag,
|
||||||
|
InvalidVersion,
|
||||||
|
IntegerOverflow,
|
||||||
|
SizeLimit,
|
||||||
|
DuplicateId,
|
||||||
|
UnknownId,
|
||||||
|
TypeMismatch,
|
||||||
|
Unsupported,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct error {
|
||||||
|
error_code code;
|
||||||
|
std::string message;
|
||||||
|
|
||||||
|
std::size_t offset = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace serialization
|
||||||
|
} // namespace furlang
|
||||||
|
|
||||||
|
#endif // FURLANG_SERIALIZATION_ERROR_HPP
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
#ifndef FURLANG_SERIALIZATION_IO_HPP
|
||||||
|
#define FURLANG_SERIALIZATION_IO_HPP
|
||||||
|
|
||||||
|
#include "furlang/result.hpp"
|
||||||
|
#include "furlang/serialization/error.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace furlang {
|
||||||
|
namespace serialization {
|
||||||
|
|
||||||
|
class writer {
|
||||||
|
public:
|
||||||
|
writer() = default;
|
||||||
|
virtual ~writer() = default;
|
||||||
|
|
||||||
|
writer(writer&&) noexcept = default;
|
||||||
|
writer& operator=(writer&&) noexcept = default;
|
||||||
|
writer(const writer&) = default;
|
||||||
|
writer& operator=(const writer&) = default;
|
||||||
|
public:
|
||||||
|
virtual result<error> write_s8(std::int8_t value) = 0;
|
||||||
|
virtual result<error> write_u8(std::uint8_t value) = 0;
|
||||||
|
virtual result<error> write_s16(std::int16_t value) = 0;
|
||||||
|
virtual result<error> write_u16(std::uint16_t value) = 0;
|
||||||
|
virtual result<error> write_s32(std::int32_t value) = 0;
|
||||||
|
virtual result<error> write_u32(std::uint32_t value) = 0;
|
||||||
|
virtual result<error> write_s64(std::int64_t value) = 0;
|
||||||
|
virtual result<error> write_u64(std::uint64_t value) = 0;
|
||||||
|
|
||||||
|
result<error> write_int(std::int8_t value) { return write_s8(value); }
|
||||||
|
result<error> write_int(std::uint8_t value) { return write_u8(value); }
|
||||||
|
result<error> write_int(std::int16_t value) { return write_s16(value); }
|
||||||
|
result<error> write_int(std::uint16_t value) { return write_u16(value); }
|
||||||
|
result<error> write_int(std::int32_t value) { return write_s32(value); }
|
||||||
|
result<error> write_int(std::uint32_t value) { return write_u32(value); }
|
||||||
|
result<error> write_int(std::int64_t value) { return write_s64(value); }
|
||||||
|
result<error> write_int(std::uint64_t value) { return write_u64(value); }
|
||||||
|
|
||||||
|
virtual result<error> write_string(const char* string) = 0;
|
||||||
|
virtual result<error> write_string(std::string_view string) = 0;
|
||||||
|
virtual result<error> write_string(const std::string& string) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class reader {
|
||||||
|
public:
|
||||||
|
reader() = default;
|
||||||
|
virtual ~reader() = default;
|
||||||
|
|
||||||
|
reader(reader&&) noexcept = default;
|
||||||
|
reader& operator=(reader&&) noexcept = default;
|
||||||
|
reader(const reader&) = default;
|
||||||
|
reader& operator=(const reader&) = default;
|
||||||
|
public:
|
||||||
|
virtual result<error, std::int8_t> read_s8() = 0;
|
||||||
|
virtual result<error, std::uint8_t> read_u8() = 0;
|
||||||
|
virtual result<error, std::int16_t> read_s16() = 0;
|
||||||
|
virtual result<error, std::uint16_t> read_u16() = 0;
|
||||||
|
virtual result<error, std::int32_t> read_s32() = 0;
|
||||||
|
virtual result<error, std::uint32_t> read_u32() = 0;
|
||||||
|
virtual result<error, std::int64_t> read_s64() = 0;
|
||||||
|
virtual result<error, std::uint64_t> read_u64() = 0;
|
||||||
|
|
||||||
|
result<error, std::int8_t> read_int(std::int8_t) { return read_s8(); }
|
||||||
|
result<error, std::uint8_t> read_int(std::uint8_t) { return read_u8(); }
|
||||||
|
result<error, std::int16_t> read_int(std::int16_t) { return read_s16(); }
|
||||||
|
result<error, std::uint16_t> read_int(std::uint16_t) { return read_u16(); }
|
||||||
|
result<error, std::int32_t> read_int(std::int32_t) { return read_s32(); }
|
||||||
|
result<error, std::uint32_t> read_int(std::uint32_t) { return read_u32(); }
|
||||||
|
result<error, std::int64_t> read_int(std::int64_t) { return read_s64(); }
|
||||||
|
result<error, std::uint64_t> read_int(std::uint64_t) { return read_u64(); }
|
||||||
|
|
||||||
|
virtual result<error, std::string> read_string() = 0;
|
||||||
|
|
||||||
|
virtual std::size_t offset() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class endianness {
|
||||||
|
Little = 0,
|
||||||
|
Big = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
class byte_writer : public writer {
|
||||||
|
public:
|
||||||
|
byte_writer(endianness endianness = endianness::Big)
|
||||||
|
: m_endianness(endianness) {}
|
||||||
|
public:
|
||||||
|
result<error> write_s8(std::int8_t value) override;
|
||||||
|
result<error> write_u8(std::uint8_t value) override;
|
||||||
|
result<error> write_s16(std::int16_t value) override;
|
||||||
|
result<error> write_u16(std::uint16_t value) override;
|
||||||
|
result<error> write_s32(std::int32_t value) override;
|
||||||
|
result<error> write_u32(std::uint32_t value) override;
|
||||||
|
result<error> write_s64(std::int64_t value) override;
|
||||||
|
result<error> write_u64(std::uint64_t value) override;
|
||||||
|
|
||||||
|
result<error> write_string(const char* string) override;
|
||||||
|
result<error> write_string(std::string_view string) override;
|
||||||
|
result<error> write_string(const std::string& string) override;
|
||||||
|
private:
|
||||||
|
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
|
||||||
|
result<error> write_integral_le(T value) {
|
||||||
|
return write_integral_le(value, std::make_index_sequence<sizeof(T)>{});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>, std::size_t... I>
|
||||||
|
result<error> write_integral_le(T value, std::index_sequence<I...>) {
|
||||||
|
auto usig = static_cast<std::make_unsigned_t<T>>(value);
|
||||||
|
(m_bytes.push_back(usig >> (I * 8)), ...);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
|
||||||
|
void write_integral_be(T value) {
|
||||||
|
write_integral_be(value, std::make_index_sequence<sizeof(T)>{});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>, std::size_t... I>
|
||||||
|
void write_integral_be(T value, std::index_sequence<I...>) {
|
||||||
|
auto usig = static_cast<std::make_unsigned_t<T>>(value);
|
||||||
|
(m_bytes.push_back(usig >> ((sizeof(T) - 1 - I) * 8)), ...);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
endianness m_endianness;
|
||||||
|
std::vector<std::uint8_t> m_bytes;
|
||||||
|
};
|
||||||
|
|
||||||
|
class byte_reader : public reader {
|
||||||
|
public:
|
||||||
|
byte_reader(const std::uint8_t* bytes, std::size_t length, endianness endianness = endianness::Big)
|
||||||
|
: m_endianness(endianness), m_bytes(bytes), m_length(length) {}
|
||||||
|
public:
|
||||||
|
result<error, std::int8_t> read_s8() override;
|
||||||
|
result<error, std::uint8_t> read_u8() override;
|
||||||
|
result<error, std::int16_t> read_s16() override;
|
||||||
|
result<error, std::uint16_t> read_u16() override;
|
||||||
|
result<error, std::int32_t> read_s32() override;
|
||||||
|
result<error, std::uint32_t> read_u32() override;
|
||||||
|
result<error, std::int64_t> read_s64() override;
|
||||||
|
result<error, std::uint64_t> read_u64() override;
|
||||||
|
|
||||||
|
result<error, std::string> read_string() override;
|
||||||
|
|
||||||
|
std::size_t offset() const override;
|
||||||
|
private:
|
||||||
|
result<error, std::uint8_t> read_byte() {
|
||||||
|
if (m_offset >= m_length)
|
||||||
|
return result<error, std::uint8_t>::error(error{ error_code::EndOfFile, "", m_offset });
|
||||||
|
return { m_bytes[m_offset++] };
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
|
||||||
|
result<error, T> read_integral_le() {
|
||||||
|
using U = std::make_unsigned_t<T>;
|
||||||
|
U usig = 0;
|
||||||
|
for (std::size_t i = 0; i < sizeof(T); ++i) {
|
||||||
|
auto res = read_byte();
|
||||||
|
if (res.has_error()) return res;
|
||||||
|
usig |= static_cast<U>(res.value()) << (i * 8);
|
||||||
|
}
|
||||||
|
return { static_cast<T>(usig) };
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
|
||||||
|
result<error, T> read_integral_be() {
|
||||||
|
using U = std::make_unsigned_t<T>;
|
||||||
|
U usig = 0;
|
||||||
|
for (std::size_t i = 0; i < sizeof(T); ++i) {
|
||||||
|
auto res = read_byte();
|
||||||
|
if (res.has_error()) return res;
|
||||||
|
usig |= static_cast<U>(res.value()) << ((sizeof(T) - 1 - i) * 8);
|
||||||
|
}
|
||||||
|
return { static_cast<T>(usig) };
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
endianness m_endianness;
|
||||||
|
const std::uint8_t* m_bytes;
|
||||||
|
std::size_t m_length;
|
||||||
|
std::size_t m_offset = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace serialization
|
||||||
|
} // namespace furlang
|
||||||
|
|
||||||
|
#endif // FURLANG_SERIALIZATION_IO_HPP
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
#ifndef FURLANG_UTILITY_HASH_HPP
|
||||||
|
#define FURLANG_UTILITY_HASH_HPP
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace furlang {
|
||||||
|
namespace utility {
|
||||||
|
|
||||||
|
// Source - https://stackoverflow.com/a/27952689
|
||||||
|
// Posted by Yakk - Adam Nevraumont, modified by community. See post 'Timeline' for change history
|
||||||
|
// Retrieved 2026-07-07, License - CC BY-SA 4.0
|
||||||
|
static inline std::size_t hash_combine(std::size_t lhs, std::size_t rhs) {
|
||||||
|
if constexpr (sizeof(std::size_t) >= 8) {
|
||||||
|
lhs ^= rhs + 0x517cc1b727220a95 + (lhs << 6) + (lhs >> 2);
|
||||||
|
} else {
|
||||||
|
lhs ^= rhs + 0x9e3779b9 + (lhs << 6) + (lhs >> 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Source - https://stackoverflow.com/a/20602159
|
||||||
|
// Posted by Casey, modified by community. See post 'Timeline' for change history
|
||||||
|
// Retrieved 2026-07-07, License - CC BY-SA 3.0
|
||||||
|
template <typename T, typename U, typename FirstHash = std::hash<T>, typename SecondHash = std::hash<U>>
|
||||||
|
struct pair_hash {
|
||||||
|
std::size_t operator()(const std::pair<T, U>& pair) const {
|
||||||
|
return hash_combine(FirstHash()(pair.first), SecondHash()(pair.second));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, typename Hash = std::hash<T>>
|
||||||
|
struct vector_hash {
|
||||||
|
std::size_t operator()(const std::vector<T>& vec) const {
|
||||||
|
std::size_t seed = 0;
|
||||||
|
for (const auto& element : vec) {
|
||||||
|
hash_combine(seed, Hash()(element));
|
||||||
|
}
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace utility
|
||||||
|
} // namespace furlang
|
||||||
|
|
||||||
|
#endif // FURLANG_UTILITY_HASH_HPP
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#ifndef FURLANG_VIEW_HPP
|
||||||
|
#define FURLANG_VIEW_HPP
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <limits>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace furlang {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class view {
|
||||||
|
public:
|
||||||
|
constexpr view() noexcept = default;
|
||||||
|
|
||||||
|
constexpr view(const T* data, std::size_t size) noexcept
|
||||||
|
: m_data(data), m_size(size) {}
|
||||||
|
public:
|
||||||
|
constexpr view subview(std::size_t offset, std::size_t count = std::numeric_limits<std::size_t>::max()) {
|
||||||
|
if (count > 0 && offset >= m_size) throw std::runtime_error("offset too large");
|
||||||
|
return { m_data + offset, std::min(m_size - offset, count) };
|
||||||
|
}
|
||||||
|
|
||||||
|
const T& operator[](std::size_t offset) const {
|
||||||
|
if (offset >= m_size) throw std::runtime_error("out of bounds");
|
||||||
|
return m_data[offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const T* data() const { return m_data; }
|
||||||
|
constexpr std::size_t size() const { return m_size; }
|
||||||
|
private:
|
||||||
|
const T* m_data = nullptr;
|
||||||
|
std::size_t m_size = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace furlang
|
||||||
|
|
||||||
|
#endif // FURLANG_VIEW_HPP
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#ifndef FURVM_CONSTANT_HPP
|
||||||
|
#define FURVM_CONSTANT_HPP
|
||||||
|
|
||||||
|
#include "furvm/fwd.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace furvm {
|
||||||
|
|
||||||
|
// TODO: Array constants
|
||||||
|
struct constant {
|
||||||
|
enum type_e {
|
||||||
|
S32 = 0,
|
||||||
|
U32,
|
||||||
|
S64,
|
||||||
|
U64,
|
||||||
|
String,
|
||||||
|
} type = S32;
|
||||||
|
union {
|
||||||
|
std::int32_t s32;
|
||||||
|
std::uint32_t u32;
|
||||||
|
std::int64_t s64;
|
||||||
|
std::uint64_t u64;
|
||||||
|
std::string_view string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace furvm
|
||||||
|
|
||||||
|
#endif // FURVM_CONSTANT_HPP
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
#ifndef FURVM_CONTEXT_HPP
|
||||||
|
#define FURVM_CONTEXT_HPP
|
||||||
|
|
||||||
|
#include "furvm/executor.hpp"
|
||||||
|
#include "furvm/fwd.hpp"
|
||||||
|
#include "furvm/handle.hpp"
|
||||||
|
#include "furvm/module.hpp" // IWYU pragma: keep
|
||||||
|
#include "furvm/thing.hpp" // IWYU pragma: keep
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace furvm {
|
||||||
|
|
||||||
|
class context : public handle_container<mod_h> {
|
||||||
|
public:
|
||||||
|
friend class executor;
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructs a context.
|
||||||
|
*/
|
||||||
|
context() {}
|
||||||
|
|
||||||
|
~context() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move constructor.
|
||||||
|
*/
|
||||||
|
context(context&&) noexcept = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move constructor.
|
||||||
|
*/
|
||||||
|
context& operator=(context&&) noexcept = default;
|
||||||
|
|
||||||
|
context(const context&) = delete;
|
||||||
|
context& operator=(const context&) = delete;
|
||||||
|
public:
|
||||||
|
template <typename... Args>
|
||||||
|
auto& allocate_executor() {
|
||||||
|
executor executor(this);
|
||||||
|
return m_executors.emplace_back(std::move(executor));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an executor from the context.
|
||||||
|
*
|
||||||
|
* @param args Id of the executor.
|
||||||
|
* @return A handle to the executor.
|
||||||
|
*/
|
||||||
|
template <typename... Args>
|
||||||
|
auto& executor_at(Args&&... args) {
|
||||||
|
return m_executors.at(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an executor from the context.
|
||||||
|
*
|
||||||
|
* @param args Id of the executor.
|
||||||
|
* @return A handle to the executor.
|
||||||
|
*/
|
||||||
|
template <typename... Args>
|
||||||
|
const auto& executor_at(Args&&... args) const {
|
||||||
|
return m_executors.at(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<executor>& executors() const { return m_executors; }
|
||||||
|
public:
|
||||||
|
thing_type_store& tt_store() { return m_thingTypeStore; }
|
||||||
|
public:
|
||||||
|
template <typename... Args>
|
||||||
|
thing<> allocate_thing(Args&&... args) {
|
||||||
|
thing<> thing = { std::forward<Args>(args)... };
|
||||||
|
m_heap.push_back(thing.raw());
|
||||||
|
return std::move(thing);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
handle_container<mod_h> m_modules;
|
||||||
|
std::vector<executor> m_executors;
|
||||||
|
|
||||||
|
class thing_type_store m_thingTypeStore;
|
||||||
|
|
||||||
|
// A list of things on the heap
|
||||||
|
std::vector<std::byte*> m_heap;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace furvm
|
||||||
|
|
||||||
|
#endif // FURVM_CONTEXT_HPP
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#ifndef FURVM_DETAIL_HANDLE_HPP
|
||||||
|
#define FURVM_DETAIL_HANDLE_HPP
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace furvm {
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Default specialization for header_has_refcount type trait.
|
||||||
|
*/
|
||||||
|
template <typename Header, typename = void>
|
||||||
|
struct header_has_refcount : std::false_type {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Specialization for header_has_refcount type trait.
|
||||||
|
*/
|
||||||
|
template <typename Header>
|
||||||
|
struct header_has_refcount<Header,
|
||||||
|
std::void_t<decltype(std::declval<Header&>().acquire()),
|
||||||
|
decltype(std::declval<Header&>().release()),
|
||||||
|
decltype(std::declval<Header&>().reference_count())>> : std::true_type {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief An alias for header_has_refcount's value.
|
||||||
|
*/
|
||||||
|
template <typename Header>
|
||||||
|
static constexpr auto header_has_refcount_v = header_has_refcount<Header>::value;
|
||||||
|
|
||||||
|
template <typename Handle, typename IdHash = std::hash<typename Handle::id_type>>
|
||||||
|
struct handle_hash {
|
||||||
|
std::size_t operator()(const Handle& handle) const { return IdHash{}(handle.id()); }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
} // namespace furvm
|
||||||
|
|
||||||
|
#endif // FURVM_DETAIL_HANDLE_HPP
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
#ifndef FURVM_DETAIL_SERIALIZATION_HPP
|
||||||
|
#define FURVM_DETAIL_SERIALIZATION_HPP
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <ostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace furvm {
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Serializes an integer.
|
||||||
|
*
|
||||||
|
* @param os Output stream.
|
||||||
|
* @param value Integer.
|
||||||
|
* @return The output stream.
|
||||||
|
*/
|
||||||
|
std::ostream& serialize(std::ostream& os, std::int8_t value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Serializes an integer.
|
||||||
|
*
|
||||||
|
* @param os Output stream.
|
||||||
|
* @param value Integer.
|
||||||
|
* @return The output stream.
|
||||||
|
*/
|
||||||
|
std::ostream& serialize(std::ostream& os, std::int16_t value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Serializes an integer.
|
||||||
|
*
|
||||||
|
* @param os Output stream.
|
||||||
|
* @param value Integer.
|
||||||
|
* @return The output stream.
|
||||||
|
*/
|
||||||
|
std::ostream& serialize(std::ostream& os, std::int32_t value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Serializes an integer.
|
||||||
|
*
|
||||||
|
* @param os Output stream.
|
||||||
|
* @param value Integer.
|
||||||
|
* @return The output stream.
|
||||||
|
*/
|
||||||
|
std::ostream& serialize(std::ostream& os, std::int64_t value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Serializes an integer.
|
||||||
|
*
|
||||||
|
* @param os Output stream.
|
||||||
|
* @param value Integer.
|
||||||
|
* @return The output stream.
|
||||||
|
*/
|
||||||
|
std::ostream& serialize(std::ostream& os, std::uint8_t value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Serializes an integer.
|
||||||
|
*
|
||||||
|
* @param os Output stream.
|
||||||
|
* @param value Integer.
|
||||||
|
* @return The output stream.
|
||||||
|
*/
|
||||||
|
std::ostream& serialize(std::ostream& os, std::uint16_t value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Serializes an integer.
|
||||||
|
*
|
||||||
|
* @param os Output stream.
|
||||||
|
* @param value Integer.
|
||||||
|
* @return The output stream.
|
||||||
|
*/
|
||||||
|
std::ostream& serialize(std::ostream& os, std::uint32_t value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Serializes an integer.
|
||||||
|
*
|
||||||
|
* @param os Output stream.
|
||||||
|
* @param value Integer.
|
||||||
|
* @return The output stream.
|
||||||
|
*/
|
||||||
|
std::ostream& serialize(std::ostream& os, std::uint64_t value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Serializes a string.
|
||||||
|
*
|
||||||
|
* @param os Output stream.
|
||||||
|
* @param value String.
|
||||||
|
* @return The output stream.
|
||||||
|
*/
|
||||||
|
std::ostream& serialize(std::ostream& os, const std::string& value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deserializes an integer.
|
||||||
|
*
|
||||||
|
* @param is Input stream.
|
||||||
|
* @param value Integer.
|
||||||
|
* @return The input stream.
|
||||||
|
*/
|
||||||
|
std::istream& load(std::istream& is, std::int8_t& value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deserializes an integer.
|
||||||
|
*
|
||||||
|
* @param is Input stream.
|
||||||
|
* @param value Integer.
|
||||||
|
* @return The input stream.
|
||||||
|
*/
|
||||||
|
std::istream& load(std::istream& is, std::int16_t& value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deserializes an integer.
|
||||||
|
*
|
||||||
|
* @param is Input stream.
|
||||||
|
* @param value Integer.
|
||||||
|
* @return The input stream.
|
||||||
|
*/
|
||||||
|
std::istream& load(std::istream& is, std::int32_t& value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deserializes an integer.
|
||||||
|
*
|
||||||
|
* @param is Input stream.
|
||||||
|
* @param value Integer.
|
||||||
|
* @return The input stream.
|
||||||
|
*/
|
||||||
|
std::istream& load(std::istream& is, std::int64_t& value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deserializes an integer.
|
||||||
|
*
|
||||||
|
* @param is Input stream.
|
||||||
|
* @param value Integer.
|
||||||
|
* @return The input stream.
|
||||||
|
*/
|
||||||
|
std::istream& load(std::istream& is, std::uint8_t& value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deserializes an integer.
|
||||||
|
*
|
||||||
|
* @param is Input stream.
|
||||||
|
* @param value Integer.
|
||||||
|
* @return The input stream.
|
||||||
|
*/
|
||||||
|
std::istream& load(std::istream& is, std::uint16_t& value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deserializes an integer.
|
||||||
|
*
|
||||||
|
* @param is Input stream.
|
||||||
|
* @param value Integer.
|
||||||
|
* @return The input stream.
|
||||||
|
*/
|
||||||
|
std::istream& load(std::istream& is, std::uint32_t& value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deserializes an integer.
|
||||||
|
*
|
||||||
|
* @param is Input stream.
|
||||||
|
* @param value Integer.
|
||||||
|
* @return The input stream.
|
||||||
|
*/
|
||||||
|
std::istream& load(std::istream& is, std::uint64_t& value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deserializes a string.
|
||||||
|
*
|
||||||
|
* @param is Input stream.
|
||||||
|
* @param value String.
|
||||||
|
* @return The input stream.
|
||||||
|
*/
|
||||||
|
std::istream& load(std::istream& is, std::string& value);
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
} // namespace furvm
|
||||||
|
|
||||||
|
#endif // FURVM_DETAIL_SERIALIZATION_HPP
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
#ifndef FURVM_EXCEPTIONS_HPP
|
||||||
|
#define FURVM_EXCEPTIONS_HPP
|
||||||
|
|
||||||
|
#include <exception>
|
||||||
|
|
||||||
|
namespace furvm {
|
||||||
|
|
||||||
|
class bad_thing_access : public std::exception {
|
||||||
|
public:
|
||||||
|
bad_thing_access() = default;
|
||||||
|
~bad_thing_access() override = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move constructor.
|
||||||
|
*/
|
||||||
|
bad_thing_access(bad_thing_access&&) noexcept = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move constructor.
|
||||||
|
*/
|
||||||
|
bad_thing_access& operator=(bad_thing_access&&) noexcept = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy constructor.
|
||||||
|
*/
|
||||||
|
bad_thing_access(const bad_thing_access&) = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy constructor.
|
||||||
|
*/
|
||||||
|
bad_thing_access& operator=(const bad_thing_access&) = default;
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Returns a C-style string describing the cause of the error.
|
||||||
|
*
|
||||||
|
* @return The cause of the error.
|
||||||
|
*/
|
||||||
|
const char* what() const noexcept override { return "bad thing access"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Bad constant access exception.
|
||||||
|
*/
|
||||||
|
class bad_constant_access : public std::exception {
|
||||||
|
public:
|
||||||
|
bad_constant_access() = default;
|
||||||
|
~bad_constant_access() override = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move constructor.
|
||||||
|
*/
|
||||||
|
bad_constant_access(bad_constant_access&&) noexcept = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move constructor.
|
||||||
|
*/
|
||||||
|
bad_constant_access& operator=(bad_constant_access&&) noexcept = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy constructor.
|
||||||
|
*/
|
||||||
|
bad_constant_access(const bad_constant_access&) = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy constructor.
|
||||||
|
*/
|
||||||
|
bad_constant_access& operator=(const bad_constant_access&) = default;
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Returns a C-style string describing the cause of the error.
|
||||||
|
*
|
||||||
|
* @return The cause of the error.
|
||||||
|
*/
|
||||||
|
const char* what() const noexcept override { return "bad constant access"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class stack_underflow : public std::exception {
|
||||||
|
public:
|
||||||
|
stack_underflow() = default;
|
||||||
|
~stack_underflow() override = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move constructor.
|
||||||
|
*/
|
||||||
|
stack_underflow(stack_underflow&&) noexcept = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move constructor.
|
||||||
|
*/
|
||||||
|
stack_underflow& operator=(stack_underflow&&) noexcept = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy constructor.
|
||||||
|
*/
|
||||||
|
stack_underflow(const stack_underflow&) = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy constructor.
|
||||||
|
*/
|
||||||
|
stack_underflow& operator=(const stack_underflow&) = default;
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Returns a C-style string describing the cause of the error.
|
||||||
|
*
|
||||||
|
* @return The cause of the error.
|
||||||
|
*/
|
||||||
|
const char* what() const noexcept override { return "stack underflow"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace furvm
|
||||||
|
|
||||||
|
#endif // FURVM_EXCEPTIONS_HPP
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
#ifndef FURVM_EXECUTOR_HPP
|
||||||
|
#define FURVM_EXECUTOR_HPP
|
||||||
|
|
||||||
|
#include "furvm/fwd.hpp"
|
||||||
|
#include "furvm/module.hpp" // IWYU pragma: keep
|
||||||
|
#include "furvm/stack.hpp"
|
||||||
|
#include "furvm/thing.hpp" // IWYU pragma: keep
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <stack>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace furvm {
|
||||||
|
|
||||||
|
enum class executor_flags : std::uint32_t {
|
||||||
|
Suspended = (1 << 0), /**< Execution suspended. */
|
||||||
|
Done = (1 << 1), /**< Execution is finished. */
|
||||||
|
|
||||||
|
JustHit = (1 << 16), /**< Executor just hit a breakpoint. */
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline executor_flags operator|(executor_flags lhs, executor_flags rhs) {
|
||||||
|
return executor_flags(static_cast<std::uint32_t>(lhs) | static_cast<std::uint32_t>(rhs));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline executor_flags operator&(executor_flags lhs, executor_flags rhs) {
|
||||||
|
return executor_flags(static_cast<std::uint32_t>(lhs) & static_cast<std::uint32_t>(rhs));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline executor_flags operator~(executor_flags flags) {
|
||||||
|
return executor_flags(~static_cast<std::uint32_t>(flags));
|
||||||
|
}
|
||||||
|
|
||||||
|
class executor {
|
||||||
|
friend class context;
|
||||||
|
private:
|
||||||
|
executor(context* context)
|
||||||
|
: m_context(context) {}
|
||||||
|
public:
|
||||||
|
static constexpr executor_flags STATE_FLAGS = executor_flags::JustHit;
|
||||||
|
|
||||||
|
using new_frame_callback = std::function<void(executor&)>;
|
||||||
|
|
||||||
|
using stack_thing = thing<stack_allocator>;
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Executor frame.
|
||||||
|
*
|
||||||
|
* Call frame.
|
||||||
|
*/
|
||||||
|
struct frame {
|
||||||
|
mod_h mod; /**< Handle to the frame's module. */
|
||||||
|
std::size_t position; /**< Cursor to a current instruction in the bytecode. */
|
||||||
|
std::size_t stackBase; /**< Snapshot of the stack size before this frame. */
|
||||||
|
|
||||||
|
thing_type* returnType; /**< Return type. */
|
||||||
|
std::vector<stack_thing> variables; /**< Frame variables. */
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
~executor() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move constructor.
|
||||||
|
*/
|
||||||
|
executor(executor&&) noexcept = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move constructor.
|
||||||
|
*/
|
||||||
|
executor& operator=(executor&&) noexcept = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy constructor.
|
||||||
|
*/
|
||||||
|
executor(const executor&) = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy constructor.
|
||||||
|
*/
|
||||||
|
executor& operator=(const executor&) = default;
|
||||||
|
public:
|
||||||
|
template <typename CallbackFwd>
|
||||||
|
void set_new_frame_callback(CallbackFwd&& callback) {
|
||||||
|
m_newFrameCb = std::forward<CallbackFwd>(callback);
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Returns flags of this executor.
|
||||||
|
*
|
||||||
|
* @return The flags.
|
||||||
|
*/
|
||||||
|
executor_flags flags() const { return m_flags; }
|
||||||
|
|
||||||
|
bool done() const { return (m_flags & executor_flags::Done) == executor_flags::Done; }
|
||||||
|
|
||||||
|
bool suspended() const { return (m_flags & executor_flags::Suspended) == executor_flags::Suspended; }
|
||||||
|
|
||||||
|
void unsuspend() { m_flags = m_flags & ~executor_flags::Suspended; }
|
||||||
|
|
||||||
|
void clear_flags() {
|
||||||
|
m_flags = m_flags & STATE_FLAGS;
|
||||||
|
m_flags = m_frames.empty() ? executor_flags::Done : furvm::executor_flags{ 0 };
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Pushes a new frame.
|
||||||
|
*
|
||||||
|
* @param mod Handle to the frame's module.
|
||||||
|
* @param function Frame's function.
|
||||||
|
*/
|
||||||
|
void push_frame(const mod_h& mod, function function);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Pops the top frame.
|
||||||
|
*
|
||||||
|
* @return The popped frame.
|
||||||
|
*/
|
||||||
|
frame pop_frame();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the top frame.
|
||||||
|
*
|
||||||
|
* @return The frame.
|
||||||
|
*/
|
||||||
|
frame top_frame() const;
|
||||||
|
|
||||||
|
const std::stack<frame>& frames() const { return m_frames; }
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Pushes a thing onto the stack.
|
||||||
|
*
|
||||||
|
* Registers a new thing and pushes its handle onto the stack.
|
||||||
|
*
|
||||||
|
* @param thing Thing.
|
||||||
|
* @return The pushed handle.
|
||||||
|
*/
|
||||||
|
stack_thing& push_thing(stack_thing&& thing);
|
||||||
|
|
||||||
|
stack_thing& push_thing(const stack_thing& thing);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Pops a thing from the stack.
|
||||||
|
*
|
||||||
|
* @return A handle to the popped thing.
|
||||||
|
*/
|
||||||
|
stack_thing pop_thing();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the top thing on the stack.
|
||||||
|
*
|
||||||
|
* @return A handle to the top thing.
|
||||||
|
*/
|
||||||
|
stack_thing& top_thing();
|
||||||
|
|
||||||
|
const stack_thing& top_thing() const;
|
||||||
|
|
||||||
|
const std::vector<stack_thing>& stack() const { return m_stack; }
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Stores a thing in a frame variable.
|
||||||
|
*
|
||||||
|
* @param variable Id of the variable in which the handle will be put.
|
||||||
|
* @param thing Thing handle.
|
||||||
|
*/
|
||||||
|
void store_thing(variable_t variable, const stack_thing& thing);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Stores a thing in a frame variable.
|
||||||
|
*
|
||||||
|
* @param variable Id of the variable in which the handle will be put.
|
||||||
|
* @param thing Thing handle.
|
||||||
|
*/
|
||||||
|
void store_thing(variable_t variable, stack_thing&& thing);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a thing stored in a variable.
|
||||||
|
*
|
||||||
|
* @param variable Id of the variable from which the handle will be fetched.
|
||||||
|
* @return A handle stored in the variable.
|
||||||
|
*/
|
||||||
|
stack_thing& load_thing(variable_t variable);
|
||||||
|
|
||||||
|
const stack_thing& load_thing(variable_t variable) const;
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Executes next instruction.
|
||||||
|
*/
|
||||||
|
void step();
|
||||||
|
private:
|
||||||
|
thing_type thing_type_impl(mod_h mod, mod_type type) const;
|
||||||
|
|
||||||
|
thing_type* mod_to_thing_type(const mod_h& mod, const mod_type& type) const;
|
||||||
|
private:
|
||||||
|
static bool compare_thing_types(const thing_type& lhs, const thing_type& rhs);
|
||||||
|
private:
|
||||||
|
executor_flags m_flags = executor_flags::Done;
|
||||||
|
context* m_context;
|
||||||
|
furvm::stack<std::byte> m_stackStorage;
|
||||||
|
|
||||||
|
std::stack<frame> m_frames;
|
||||||
|
std::vector<stack_thing> m_stack;
|
||||||
|
|
||||||
|
new_frame_callback m_newFrameCb = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace furvm
|
||||||
|
|
||||||
|
#endif // FURVM_EXECUTOR_HPP
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
#ifndef FURVM_FUNCTION_HPP
|
||||||
|
#define FURVM_FUNCTION_HPP
|
||||||
|
|
||||||
|
#include "furvm/fwd.hpp"
|
||||||
|
#include "furvm/handle.hpp" // IWYU pragma: keep
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace furvm {
|
||||||
|
|
||||||
|
enum class function_t : std::uint8_t {
|
||||||
|
Normal = 0, /**< A normal bytecode function. */
|
||||||
|
Native, /**< A native function implemented through furvm API. */
|
||||||
|
Import, /**< A function imported from another module. */
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A native function.
|
||||||
|
*/
|
||||||
|
using native_function = std::string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A function import.
|
||||||
|
*/
|
||||||
|
struct import_function {
|
||||||
|
mod_id mod;
|
||||||
|
function_id function;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Function signature.
|
||||||
|
*/
|
||||||
|
struct function_sig {
|
||||||
|
std::vector<mod_type_h> params;
|
||||||
|
std::optional<mod_type_h> returnType;
|
||||||
|
|
||||||
|
bool operator==(const function_sig& rhs) const { return params == rhs.params; }
|
||||||
|
|
||||||
|
bool operator!=(const function_sig& rhs) const { return !this->operator==(rhs); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class function {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructs a normal function.
|
||||||
|
*
|
||||||
|
* @param signature Function's signature.
|
||||||
|
* @param position Offset in bytecode of the function.
|
||||||
|
*/
|
||||||
|
template <typename SigFwd, typename = std::enable_if_t<std::is_constructible_v<function_sig, SigFwd>>>
|
||||||
|
function(SigFwd&& signature, bytecode_pos position)
|
||||||
|
: m_type(function_t::Normal), m_signature(std::forward<SigFwd>(signature)), m_value(position) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs a native function.
|
||||||
|
*
|
||||||
|
* @param signature Function's signature.
|
||||||
|
* @param native Native function tag.
|
||||||
|
*/
|
||||||
|
template <typename SigFwd,
|
||||||
|
typename Native,
|
||||||
|
typename = std::enable_if_t<std::is_constructible_v<native_function, Native> &&
|
||||||
|
std::is_constructible_v<function_sig, SigFwd>>>
|
||||||
|
function(SigFwd&& signature, Native&& native)
|
||||||
|
: m_type(function_t::Native),
|
||||||
|
m_signature(std::forward<SigFwd>(signature)),
|
||||||
|
m_value(std::forward<Native>(native)) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs an import function.
|
||||||
|
*
|
||||||
|
* @param mod Module's id.
|
||||||
|
* @param function Function's id.
|
||||||
|
*/
|
||||||
|
template <typename ModFwd, typename = std::enable_if_t<std::is_constructible_v<mod_id, ModFwd>>>
|
||||||
|
function(ModFwd&& mod, function_id function)
|
||||||
|
: m_type(function_t::Import), m_signature(), m_value(import_function{ std::forward<ModFwd>(mod), function }) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs an import function.
|
||||||
|
*
|
||||||
|
* @param mod Module.
|
||||||
|
* @param function Function.
|
||||||
|
*/
|
||||||
|
function(const mod_h& mod, const function_h& function);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destructs a function.
|
||||||
|
*/
|
||||||
|
~function();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move constructor.
|
||||||
|
*/
|
||||||
|
function(function&&) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move constructor.
|
||||||
|
*/
|
||||||
|
function& operator=(function&&) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy constructor.
|
||||||
|
*/
|
||||||
|
function(const function&);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy constructor.
|
||||||
|
*/
|
||||||
|
function& operator=(const function&);
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Returns a type of this function.
|
||||||
|
*
|
||||||
|
* @return The type.
|
||||||
|
*/
|
||||||
|
constexpr function_t type() const { return m_type; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns this function's signature.
|
||||||
|
*
|
||||||
|
* @return The signature.
|
||||||
|
*/
|
||||||
|
function_sig signature() const { return m_signature; }
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Returns normal function's value.
|
||||||
|
*
|
||||||
|
* @return The value.
|
||||||
|
*/
|
||||||
|
std::size_t position() const {
|
||||||
|
if (m_type != function_t::Normal) throw std::runtime_error("function type mismatch");
|
||||||
|
return m_value.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns native function's value.
|
||||||
|
*
|
||||||
|
* @return The value.
|
||||||
|
*/
|
||||||
|
const native_function& native() const {
|
||||||
|
if (m_type != function_t::Native) throw std::runtime_error("function type mismatch");
|
||||||
|
return m_value.native;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns import function's value.
|
||||||
|
*
|
||||||
|
* @return The value.
|
||||||
|
*/
|
||||||
|
const import_function& imp() const {
|
||||||
|
if (m_type != function_t::Import) throw std::runtime_error("function type mismatch");
|
||||||
|
return m_value.imp;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
function_t m_type;
|
||||||
|
function_sig m_signature;
|
||||||
|
|
||||||
|
union value {
|
||||||
|
std::size_t position = 0;
|
||||||
|
native_function native;
|
||||||
|
import_function imp;
|
||||||
|
|
||||||
|
value() = default;
|
||||||
|
|
||||||
|
value(std::size_t position)
|
||||||
|
: position(position) {}
|
||||||
|
|
||||||
|
template <typename Native, typename = std::enable_if_t<std::is_constructible_v<native_function, Native>>>
|
||||||
|
value(Native&& native)
|
||||||
|
: native(std::forward<Native>(native)) {}
|
||||||
|
|
||||||
|
value(const import_function& imp)
|
||||||
|
: imp(imp) {}
|
||||||
|
|
||||||
|
~value() {}
|
||||||
|
|
||||||
|
value(value&& other) = delete;
|
||||||
|
value& operator=(value&& other) = delete;
|
||||||
|
value(const value& other) = delete;
|
||||||
|
value& operator=(const value& other) = delete;
|
||||||
|
} m_value;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
struct function_sig_hash {
|
||||||
|
std::size_t operator()(const function_sig& signature) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
} // namespace furvm
|
||||||
|
|
||||||
|
#endif // FURVM_FUNCTION_HPP
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#ifndef FURVM_HPP
|
||||||
|
#define FURVM_HPP
|
||||||
|
|
||||||
|
#include "furvm/context.hpp" // IWYU pragma: export
|
||||||
|
#include "furvm/executor.hpp" // IWYU pragma: export
|
||||||
|
#include "furvm/function.hpp" // IWYU pragma: export
|
||||||
|
#include "furvm/fwd.hpp" // IWYU pragma: export
|
||||||
|
#include "furvm/handle.hpp" // IWYU pragma: export
|
||||||
|
#include "furvm/instruction.hpp" // IWYU pragma: export
|
||||||
|
#include "furvm/thing.hpp" // IWYU pragma: export
|
||||||
|
|
||||||
|
#endif // FURVM_HPP
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <cstddef> // IWYU pragma: export
|
#include <cstddef> // IWYU pragma: export
|
||||||
#include <cstdint> // IWYU pragma: export
|
#include <cstdint> // IWYU pragma: export
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Furlang's virtual machine.
|
* @brief Furlang's virtual machine.
|
||||||
@@ -17,6 +18,40 @@ namespace furvm {
|
|||||||
*/
|
*/
|
||||||
using byte = std::uint8_t;
|
using byte = std::uint8_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief An offset into bytecode.
|
||||||
|
*/
|
||||||
|
using bytecode_pos = std::uint64_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handle header with reference count.
|
||||||
|
*/
|
||||||
|
template <typename Id>
|
||||||
|
class refcount_header;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generic handle header.
|
||||||
|
*/
|
||||||
|
template <typename Id>
|
||||||
|
class generic_header;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generic furvm object handle.
|
||||||
|
*
|
||||||
|
* @tparam Value Type of the handle's value.
|
||||||
|
* @tparam Header Type of the handle's header.
|
||||||
|
*/
|
||||||
|
template <typename Value, typename Header>
|
||||||
|
class handle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Container for the handles.
|
||||||
|
*
|
||||||
|
* @tparam Handle Type of the container's handle.
|
||||||
|
*/
|
||||||
|
template <typename Handle, typename = void>
|
||||||
|
class handle_container;
|
||||||
|
|
||||||
// constant.hpp
|
// constant.hpp
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,11 +75,7 @@ class constant;
|
|||||||
|
|
||||||
// instruction.hpp
|
// instruction.hpp
|
||||||
|
|
||||||
/**
|
struct instruction_argument;
|
||||||
* @enum instruction_t
|
|
||||||
* @brief Furvm's instruction type.
|
|
||||||
*/
|
|
||||||
enum class instruction_t : byte;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @struct instruction
|
* @struct instruction
|
||||||
@@ -68,18 +99,24 @@ enum class function_t : std::uint8_t;
|
|||||||
*/
|
*/
|
||||||
class function;
|
class function;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief An alias to a function shared pointer.
|
|
||||||
*/
|
|
||||||
using function_p = std::shared_ptr<function>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Furvm function's index.
|
* @brief Furvm function's index.
|
||||||
*/
|
*/
|
||||||
using function_handle = std::uint16_t;
|
using function_id = std::uint16_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A handle to a furvm function.
|
||||||
|
*/
|
||||||
|
using function_h = handle<function, refcount_header<function_id>>;
|
||||||
|
|
||||||
// module.hpp
|
// module.hpp
|
||||||
|
|
||||||
|
struct mod_type;
|
||||||
|
|
||||||
|
using mod_type_id = std::uint32_t;
|
||||||
|
|
||||||
|
using mod_type_h = handle<mod_type, generic_header<mod_type_id>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class mod
|
* @class mod
|
||||||
* @brief Module.
|
* @brief Module.
|
||||||
@@ -94,17 +131,16 @@ class mod;
|
|||||||
using mod_p = std::shared_ptr<mod>;
|
using mod_p = std::shared_ptr<mod>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Furvm module's index.
|
* @brief An alias for a module's identifier.
|
||||||
*/
|
*/
|
||||||
using module_handle = std::uint32_t;
|
using mod_id = std::string;
|
||||||
|
|
||||||
// thing.hpp
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @enum thing_t
|
* @brief A handle to a furvm module.
|
||||||
* @brief Thing type.
|
|
||||||
*/
|
*/
|
||||||
enum class thing_t : std::uint8_t;
|
using mod_h = handle<mod, refcount_header<mod_id>>;
|
||||||
|
|
||||||
|
// thing.hpp
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class bad_thing_access
|
* @class bad_thing_access
|
||||||
@@ -112,23 +148,21 @@ enum class thing_t : std::uint8_t;
|
|||||||
*/
|
*/
|
||||||
class bad_thing_access;
|
class bad_thing_access;
|
||||||
|
|
||||||
|
using thing_type_id = std::uint32_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class thing
|
* @class thing
|
||||||
* @brief Furvm thing.
|
* @brief Furvm thing.
|
||||||
*
|
*
|
||||||
* A stack element. Think of it like of a value in C++ or I guess a class in java.
|
* A stack element. Think of it like of a value in C++ or I guess a class in java.
|
||||||
*/
|
*/
|
||||||
|
template <template <typename> typename Allocator = std::allocator>
|
||||||
class thing;
|
class thing;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief An alias to a thing shared pointer.
|
|
||||||
*/
|
|
||||||
using thing_p = std::shared_ptr<thing>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Furvm thing's index.
|
* @brief Furvm thing's index.
|
||||||
*/
|
*/
|
||||||
using thing_handle = std::uint32_t;
|
using thing_id = std::uint32_t;
|
||||||
|
|
||||||
// executor.hpp
|
// executor.hpp
|
||||||
|
|
||||||
@@ -151,15 +185,10 @@ enum class executor_flags : std::uint32_t;
|
|||||||
*/
|
*/
|
||||||
class executor;
|
class executor;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief An alias to a executor shared pointer.
|
|
||||||
*/
|
|
||||||
using executor_p = std::shared_ptr<executor>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Furvm executor's index.
|
* @brief Furvm executor's index.
|
||||||
*/
|
*/
|
||||||
using executor_handle = std::uint32_t;
|
using executor_id = std::uint32_t;
|
||||||
|
|
||||||
// context.hpp
|
// context.hpp
|
||||||
|
|
||||||
@@ -0,0 +1,448 @@
|
|||||||
|
#ifndef FURVM_HANDLE_HPP
|
||||||
|
#define FURVM_HANDLE_HPP
|
||||||
|
|
||||||
|
#include "furvm/detail/handle.hpp"
|
||||||
|
#include "furvm/fwd.hpp"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <functional>
|
||||||
|
#include <tuple>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace furvm {
|
||||||
|
|
||||||
|
// TODO: Implement generational indexes
|
||||||
|
|
||||||
|
template <typename Id>
|
||||||
|
class refcount_header {
|
||||||
|
public:
|
||||||
|
using id_type = Id; /**< Id type. */
|
||||||
|
using refcount_type = std::uint32_t; /**< Reference count type. */
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructs a reference counting header.
|
||||||
|
*
|
||||||
|
* @param id Identifier of the handle's value.
|
||||||
|
* @param refCount Handle's reference count.
|
||||||
|
* @param onRelease Callback function.
|
||||||
|
*/
|
||||||
|
template <typename IdFwd, typename Func>
|
||||||
|
refcount_header(IdFwd&& id, refcount_type refCount, Func&& onRelease)
|
||||||
|
: m_id(std::forward<IdFwd>(id)), m_refCount(refCount), m_onRelease(std::forward<Func>(onRelease)) {}
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Returns the header's reference count.
|
||||||
|
*
|
||||||
|
* @return The reference count.
|
||||||
|
*/
|
||||||
|
refcount_type reference_count() const { return m_refCount; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Increments the header's reference count.
|
||||||
|
*/
|
||||||
|
void acquire() { ++m_refCount; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Decrements the header's reference count.
|
||||||
|
*
|
||||||
|
* If the reference count reaches 0, the onRelease callback passed in the constructor will be called.
|
||||||
|
*/
|
||||||
|
void release() {
|
||||||
|
--m_refCount;
|
||||||
|
if (m_refCount == 0) m_onRelease(m_id);
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Returns the header's identifier.
|
||||||
|
*
|
||||||
|
* @return The identifier.
|
||||||
|
*/
|
||||||
|
id_type id() const { return m_id; }
|
||||||
|
private:
|
||||||
|
id_type m_id;
|
||||||
|
std::atomic<refcount_type> m_refCount;
|
||||||
|
std::function<void(const id_type&)> m_onRelease;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Id>
|
||||||
|
class generic_header {
|
||||||
|
public:
|
||||||
|
using id_type = Id; /**< Id type. */
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructs a generic header.
|
||||||
|
*
|
||||||
|
* @param id Identifier of the handle.
|
||||||
|
*/
|
||||||
|
generic_header(id_type id)
|
||||||
|
: m_id(id) {}
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Returns the header's identifier.
|
||||||
|
*/
|
||||||
|
id_type id() const { return m_id; }
|
||||||
|
private:
|
||||||
|
id_type m_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Value, typename Header = refcount_header<std::uint32_t>>
|
||||||
|
class handle {
|
||||||
|
public:
|
||||||
|
using value_type = Value; /** Value type. */
|
||||||
|
using reference = Value&; /** Reference type. */
|
||||||
|
using const_reference = const Value&; /** Constant reference type. */
|
||||||
|
using pointer = Value*; /** Pointer type. */
|
||||||
|
using const_pointer = const Value*; /** Constant pointer type. */
|
||||||
|
public:
|
||||||
|
using id_type = typename Header::id_type; /** Id type of the header. */
|
||||||
|
|
||||||
|
using header_type = Header;
|
||||||
|
public:
|
||||||
|
using pair_type = std::pair<Header, Value>; /** Type of a header-value pair. */
|
||||||
|
public:
|
||||||
|
handle() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs a handle.
|
||||||
|
*
|
||||||
|
* @param value A pointer to the header-value pair.
|
||||||
|
*/
|
||||||
|
handle(pair_type* value)
|
||||||
|
: m_value(value) {
|
||||||
|
if constexpr (detail::header_has_refcount_v<Header>) {
|
||||||
|
m_value->first.acquire();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destructs a handle.
|
||||||
|
*/
|
||||||
|
~handle() {
|
||||||
|
if constexpr (detail::header_has_refcount_v<Header>) {
|
||||||
|
if (m_value != nullptr) m_value->first.release();
|
||||||
|
}
|
||||||
|
m_value = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move constructor.
|
||||||
|
*/
|
||||||
|
handle(handle&& other) noexcept
|
||||||
|
: m_value(other.m_value) {
|
||||||
|
other.m_value = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move constructor.
|
||||||
|
*/
|
||||||
|
handle& operator=(handle&& other) noexcept {
|
||||||
|
if (this == &other) return *this;
|
||||||
|
m_value = other.m_value;
|
||||||
|
other.m_value = nullptr;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy constructor.
|
||||||
|
*/
|
||||||
|
handle(const handle& other)
|
||||||
|
: m_value(other.m_value) {
|
||||||
|
if constexpr (detail::header_has_refcount_v<Header>) {
|
||||||
|
m_value->first.acquire();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy constructor.
|
||||||
|
*/
|
||||||
|
handle& operator=(const handle& other) {
|
||||||
|
if (this == &other) return *this;
|
||||||
|
m_value = other.m_value;
|
||||||
|
if constexpr (detail::header_has_refcount_v<Header>) {
|
||||||
|
m_value->first.acquire();
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Returns an identifier of the handle's header.
|
||||||
|
*
|
||||||
|
* @return The header's identifier.
|
||||||
|
*/
|
||||||
|
id_type id() const { return m_value->first.id(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns whether the handle is empty.
|
||||||
|
*
|
||||||
|
* @return true if the handle is empty.
|
||||||
|
*/
|
||||||
|
bool empty() const { return m_value == nullptr; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the handle's header reference count.
|
||||||
|
*
|
||||||
|
* @return The reference count.
|
||||||
|
*/
|
||||||
|
template <typename U = Header, typename = std::enable_if_t<detail::header_has_refcount_v<U>>>
|
||||||
|
auto reference_count() const {
|
||||||
|
return m_value->first.reference_count();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a pointer to the handle's value.
|
||||||
|
*
|
||||||
|
* @return The value pointer.
|
||||||
|
*/
|
||||||
|
pointer operator->() { return &m_value->second; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a pointer to the handle's value.
|
||||||
|
*
|
||||||
|
* @return The value pointer.
|
||||||
|
*/
|
||||||
|
const_pointer operator->() const { return &m_value->second; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a reference to the handle's value.
|
||||||
|
*
|
||||||
|
* @return The value reference.
|
||||||
|
*/
|
||||||
|
reference operator*() { return m_value->second; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a reference to the handle's value.
|
||||||
|
*
|
||||||
|
* @return The value reference.
|
||||||
|
*/
|
||||||
|
const_reference operator*() const { return m_value->second; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a reference to the handle's value.
|
||||||
|
*
|
||||||
|
* @return The value reference.
|
||||||
|
*/
|
||||||
|
reference value() { return m_value->second; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a reference to the handle's value.
|
||||||
|
*
|
||||||
|
* @return The value reference.
|
||||||
|
*/
|
||||||
|
const_reference value() const { return m_value->second; }
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Invalidates the handle without releasing.
|
||||||
|
*/
|
||||||
|
void dispatch() { m_value = nullptr; }
|
||||||
|
public:
|
||||||
|
bool operator==(const handle& rhs) const { return m_value == rhs.m_value; }
|
||||||
|
|
||||||
|
bool operator!=(const handle& rhs) const { return !this->operator==(rhs); }
|
||||||
|
private:
|
||||||
|
pair_type* m_value = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Handle>
|
||||||
|
class handle_container<Handle, std::enable_if_t<!std::is_integral_v<typename Handle::id_type>>> {
|
||||||
|
private:
|
||||||
|
using pair_type = typename Handle::pair_type; /**< Handle's pair type. */
|
||||||
|
public:
|
||||||
|
using value_type = std::remove_cv_t<std::remove_reference_t<Handle>>; /**< Handle type. */
|
||||||
|
using const_value = std::add_const_t<value_type>; /**< Constant handle type. */
|
||||||
|
|
||||||
|
using id_type = typename Handle::id_type; /**< Handle's header identifier type. */
|
||||||
|
public:
|
||||||
|
handle_container() = default;
|
||||||
|
~handle_container() = default;
|
||||||
|
|
||||||
|
handle_container(handle_container&&) noexcept = default;
|
||||||
|
handle_container& operator=(handle_container&&) noexcept = default;
|
||||||
|
|
||||||
|
handle_container(const handle_container&) = delete;
|
||||||
|
handle_container& operator=(const handle_container&) = delete;
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Emplaces a new value.
|
||||||
|
*
|
||||||
|
* @param id Identifier of the emplaced value.
|
||||||
|
* @param args Arguments passed to the Handle's value type constructor.
|
||||||
|
* @return A handle to the emplaced value.
|
||||||
|
*/
|
||||||
|
template <typename IdFwd,
|
||||||
|
typename... Args,
|
||||||
|
typename = std::enable_if_t<std::is_constructible_v<typename pair_type::second_type, Args...>>>
|
||||||
|
value_type emplace(IdFwd&& id, Args&&... args) {
|
||||||
|
id_type idFwd = std::forward<IdFwd>(id);
|
||||||
|
if (auto it = m_pairs.find(idFwd); it != m_pairs.end()) delete it->second;
|
||||||
|
auto pair = new pair_type(std::piecewise_construct,
|
||||||
|
std::forward_as_tuple(idFwd, 0, [&](const id_type& id) { erase(id); }),
|
||||||
|
std::forward_as_tuple(std::forward<Args>(args)...));
|
||||||
|
m_pairs.emplace(std::move(idFwd), pair);
|
||||||
|
return { pair };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a handle to the container's value.
|
||||||
|
*
|
||||||
|
* @param id Idenfifier of the value.
|
||||||
|
* @return The value.
|
||||||
|
*/
|
||||||
|
template <typename IdFwd>
|
||||||
|
value_type at(IdFwd&& id) {
|
||||||
|
return { m_pairs.at(std::forward<IdFwd>(id)) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a handle to the container's value.
|
||||||
|
*
|
||||||
|
* @param id Idenfifier of the value.
|
||||||
|
* @return The value.
|
||||||
|
*/
|
||||||
|
template <typename IdFwd>
|
||||||
|
const_value at(IdFwd&& id) const {
|
||||||
|
return { m_pairs.at(std::forward<IdFwd>(id)) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Erases a value from the container.
|
||||||
|
*
|
||||||
|
* @param id Identifier of the value.
|
||||||
|
*/
|
||||||
|
template <typename IdFwd>
|
||||||
|
void erase(IdFwd&& id) {
|
||||||
|
auto it = m_pairs.find(std::forward<IdFwd>(id));
|
||||||
|
if (it == m_pairs.end()) return;
|
||||||
|
delete it->second;
|
||||||
|
m_pairs.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks whether a handle exists inside.
|
||||||
|
*
|
||||||
|
* @param id Identifier of the handle.
|
||||||
|
* @return true if the handle exists insdie of this container.
|
||||||
|
*/
|
||||||
|
template <typename IdFwd>
|
||||||
|
constexpr bool contains(IdFwd&& id) const {
|
||||||
|
return m_pairs.find(std::forward<IdFwd>(id)) != m_pairs.end();
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::unordered_map<id_type, pair_type*> m_pairs;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Handle>
|
||||||
|
class handle_container<Handle, std::enable_if_t<std::is_integral_v<typename Handle::id_type>>> {
|
||||||
|
private:
|
||||||
|
using pair_type = typename Handle::pair_type; /**< Handle's pair type. */
|
||||||
|
public:
|
||||||
|
using value_type = std::remove_cv_t<std::remove_reference_t<Handle>>; /**< Handle type. */
|
||||||
|
using const_value = std::add_const_t<value_type>; /**< Constant handle type. */
|
||||||
|
|
||||||
|
using id_type = typename Handle::id_type; /**< Handle's header identifier type. */
|
||||||
|
public:
|
||||||
|
handle_container() = default;
|
||||||
|
~handle_container() = default;
|
||||||
|
|
||||||
|
handle_container(handle_container&&) noexcept = default;
|
||||||
|
handle_container& operator=(handle_container&&) noexcept = default;
|
||||||
|
|
||||||
|
handle_container(const handle_container&) = delete;
|
||||||
|
handle_container& operator=(const handle_container&) = delete;
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Emplaces a new value.
|
||||||
|
*
|
||||||
|
* @param id Identifier of the emplaced value.
|
||||||
|
* @param args Arguments passed to the Handle's value type constructor.
|
||||||
|
* @return A handle to the emplaced value.
|
||||||
|
*/
|
||||||
|
template <typename... Args,
|
||||||
|
typename = std::enable_if_t<std::is_constructible_v<typename pair_type::second_type, Args...>>>
|
||||||
|
value_type emplace(id_type id, Args&&... args) {
|
||||||
|
if (id >= m_pairs.size()) {
|
||||||
|
m_pairs.resize(id + 1, nullptr);
|
||||||
|
} else if (m_pairs[id] != nullptr) {
|
||||||
|
delete m_pairs[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
pair_type* newPair = nullptr;
|
||||||
|
if constexpr (detail::header_has_refcount_v<typename Handle::header_type>) {
|
||||||
|
newPair = new pair_type(std::piecewise_construct,
|
||||||
|
std::forward_as_tuple(id, 0, [&](const id_type& id) { erase(id); }),
|
||||||
|
std::forward_as_tuple(std::forward<Args>(args)...));
|
||||||
|
} else {
|
||||||
|
newPair = new pair_type(std::piecewise_construct,
|
||||||
|
std::forward_as_tuple(id),
|
||||||
|
std::forward_as_tuple(std::forward<Args>(args)...));
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pairs[id] = newPair;
|
||||||
|
return { newPair };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Emplaces a new value.
|
||||||
|
*
|
||||||
|
* Emplaces a new value with an automatically-assigned identifier.
|
||||||
|
*
|
||||||
|
* @param args Arguments passed to the Handle's value type constructor.
|
||||||
|
* @return A handle to the emplaced value.
|
||||||
|
*/
|
||||||
|
template <typename... Args,
|
||||||
|
typename = std::enable_if_t<std::is_constructible_v<typename Handle::pair_type::second_type, Args...>>>
|
||||||
|
value_type emplace_back(Args&&... args) {
|
||||||
|
return emplace(static_cast<id_type>(m_pairs.size()), std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a handle to a value.
|
||||||
|
*
|
||||||
|
* @param id Identifier of the value.
|
||||||
|
* @return The value.
|
||||||
|
*/
|
||||||
|
value_type at(id_type id) { return { m_pairs.at(id) }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a handle to a value.
|
||||||
|
*
|
||||||
|
* @param id Identifier of the value.
|
||||||
|
* @return The value.
|
||||||
|
*/
|
||||||
|
const_value at(id_type id) const { return { m_pairs.at(id) }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Erases a value from the container.
|
||||||
|
*
|
||||||
|
* @param id Identifier of the value.
|
||||||
|
*/
|
||||||
|
void erase(id_type id) {
|
||||||
|
if (id >= m_pairs.size()) return;
|
||||||
|
delete m_pairs[id];
|
||||||
|
m_pairs[id] = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks whether a handle exists inside.
|
||||||
|
*
|
||||||
|
* @param id Identifier of the handle.
|
||||||
|
* @return true if the handle exists insdie of this container.
|
||||||
|
*/
|
||||||
|
constexpr bool contains(id_type id) const { return id < m_pairs.size() && m_pairs[id] != nullptr; }
|
||||||
|
public:
|
||||||
|
auto begin() { return m_pairs.begin(); }
|
||||||
|
auto begin() const { return m_pairs.begin(); }
|
||||||
|
auto cbegin() const { return m_pairs.cbegin(); }
|
||||||
|
|
||||||
|
auto end() { return m_pairs.end(); }
|
||||||
|
auto end() const { return m_pairs.end(); }
|
||||||
|
auto cend() const { return m_pairs.cend(); }
|
||||||
|
private:
|
||||||
|
std::vector<pair_type*> m_pairs;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace furvm
|
||||||
|
|
||||||
|
#endif // FURVM_HANDLE_HPP
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
#ifndef FURVM_INSTRUCTION_HPP
|
||||||
|
#define FURVM_INSTRUCTION_HPP
|
||||||
|
|
||||||
|
#include "furlang/view.hpp"
|
||||||
|
#include "furvm/fwd.hpp"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace furvm {
|
||||||
|
|
||||||
|
struct instruction_argument {
|
||||||
|
enum type_e {
|
||||||
|
None = 0,
|
||||||
|
S8,
|
||||||
|
U8,
|
||||||
|
S16,
|
||||||
|
U16,
|
||||||
|
S32,
|
||||||
|
U32,
|
||||||
|
Constant,
|
||||||
|
Type,
|
||||||
|
Variable,
|
||||||
|
GlobalVariable,
|
||||||
|
Function,
|
||||||
|
Offset,
|
||||||
|
|
||||||
|
Count,
|
||||||
|
} type;
|
||||||
|
union {
|
||||||
|
std::int8_t s8;
|
||||||
|
std::uint8_t u8;
|
||||||
|
std::int16_t s16;
|
||||||
|
std::uint16_t u16;
|
||||||
|
std::int32_t s32;
|
||||||
|
std::uint32_t u32;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const std::size_t s_sizes[Count];
|
||||||
|
static const bool s_signedness[Count];
|
||||||
|
|
||||||
|
std::size_t size() const { return s_sizes[type]; }
|
||||||
|
bool is_signed() const { return s_signedness[type]; }
|
||||||
|
};
|
||||||
|
|
||||||
|
using instruction_argument_t = instruction_argument::type_e;
|
||||||
|
|
||||||
|
struct instruction {
|
||||||
|
enum type_e : byte {
|
||||||
|
NoOperation = 0,
|
||||||
|
PushS8,
|
||||||
|
PushU8,
|
||||||
|
PushS16,
|
||||||
|
PushU16,
|
||||||
|
PushS32,
|
||||||
|
PushU32,
|
||||||
|
PushConstant,
|
||||||
|
Array,
|
||||||
|
Slice,
|
||||||
|
Get,
|
||||||
|
Set,
|
||||||
|
Drop,
|
||||||
|
Duplicate,
|
||||||
|
Swap,
|
||||||
|
Clone,
|
||||||
|
Reference,
|
||||||
|
Add,
|
||||||
|
Sub,
|
||||||
|
Mul,
|
||||||
|
Div,
|
||||||
|
Mod,
|
||||||
|
Equals,
|
||||||
|
NotEquals,
|
||||||
|
LessThan,
|
||||||
|
GreaterThan,
|
||||||
|
LessEqual,
|
||||||
|
GreaterEqual,
|
||||||
|
Pointerof,
|
||||||
|
Sizeof,
|
||||||
|
Lengthof,
|
||||||
|
Load,
|
||||||
|
Store,
|
||||||
|
LoadGlobal,
|
||||||
|
StoreGlobal,
|
||||||
|
Call,
|
||||||
|
Jump,
|
||||||
|
JumpNotZero,
|
||||||
|
Return,
|
||||||
|
|
||||||
|
Count,
|
||||||
|
} type;
|
||||||
|
instruction_argument arg;
|
||||||
|
|
||||||
|
static const instruction_argument_t s_arguments[Count];
|
||||||
|
|
||||||
|
std::size_t read(furlang::view<std::uint8_t> in);
|
||||||
|
std::size_t write(std::vector<std::uint8_t>& out) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
using instruction_t = instruction::type_e;
|
||||||
|
|
||||||
|
} // namespace furvm
|
||||||
|
|
||||||
|
#endif // FURVM_INSTRUCTION_HPP
|
||||||
@@ -0,0 +1,452 @@
|
|||||||
|
#ifndef FURVM_MODULE_HPP
|
||||||
|
#define FURVM_MODULE_HPP
|
||||||
|
|
||||||
|
#include "furlang/utility/hash.hpp"
|
||||||
|
#include "furlang/view.hpp"
|
||||||
|
#include "furvm/constant.hpp"
|
||||||
|
#include "furvm/function.hpp"
|
||||||
|
#include "furvm/fwd.hpp"
|
||||||
|
#include "furvm/handle.hpp"
|
||||||
|
#include "furvm/thing.hpp"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <istream>
|
||||||
|
#include <ostream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace furvm {
|
||||||
|
|
||||||
|
struct mod_type {
|
||||||
|
struct array_value {
|
||||||
|
mod_type_id typeId;
|
||||||
|
std::size_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct slice_value {
|
||||||
|
mod_type_id typeId;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct import_value {
|
||||||
|
mod_id modId;
|
||||||
|
mod_type_id typeId;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum type {
|
||||||
|
S8 = 0,
|
||||||
|
S16,
|
||||||
|
S32,
|
||||||
|
S64,
|
||||||
|
U8,
|
||||||
|
U16,
|
||||||
|
U32,
|
||||||
|
U64,
|
||||||
|
Ptr,
|
||||||
|
Ref,
|
||||||
|
Array,
|
||||||
|
Slice,
|
||||||
|
|
||||||
|
Import,
|
||||||
|
Count,
|
||||||
|
} type;
|
||||||
|
union value {
|
||||||
|
std::nullptr_t null = nullptr;
|
||||||
|
mod_type_id typeRef;
|
||||||
|
array_value array;
|
||||||
|
slice_value slice;
|
||||||
|
import_value imprt;
|
||||||
|
|
||||||
|
value() = default;
|
||||||
|
|
||||||
|
value(mod_type_id id)
|
||||||
|
: typeRef(id) {}
|
||||||
|
|
||||||
|
value(mod_type_id id, std::size_t size)
|
||||||
|
: array({}) {
|
||||||
|
array.typeId = id;
|
||||||
|
array.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ModIdFwd, typename = std::enable_if_t<std::is_constructible_v<mod_id, ModIdFwd>>>
|
||||||
|
value(ModIdFwd&& modId, mod_type_id typeId)
|
||||||
|
: imprt({}) {
|
||||||
|
imprt.modId = std::forward<ModIdFwd>(modId);
|
||||||
|
imprt.typeId = typeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
~value() {}
|
||||||
|
|
||||||
|
value(value&& other) = delete;
|
||||||
|
value& operator=(value&& other) = delete;
|
||||||
|
value(const value& other) = delete;
|
||||||
|
value& operator=(const value& other) = delete;
|
||||||
|
} value;
|
||||||
|
|
||||||
|
mod_type(enum type type)
|
||||||
|
: type(type) {}
|
||||||
|
|
||||||
|
mod_type(enum type type, mod_type_id typeRef)
|
||||||
|
: type(type), value(typeRef) {}
|
||||||
|
|
||||||
|
mod_type(mod_type_id id, std::size_t size)
|
||||||
|
: type(Array), value(id, size) {}
|
||||||
|
|
||||||
|
template <typename ModIdFwd, typename = std::enable_if_t<std::is_constructible_v<mod_id, ModIdFwd>>>
|
||||||
|
mod_type(ModIdFwd&& modId, mod_type_id typeId)
|
||||||
|
: type(Import), value(std::forward<ModIdFwd>(modId), typeId) {}
|
||||||
|
|
||||||
|
~mod_type() {
|
||||||
|
switch (type) {
|
||||||
|
case Array: value.array.~array_value(); break;
|
||||||
|
case Slice: value.slice.~slice_value(); break;
|
||||||
|
case Import: value.imprt.~import_value(); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod_type(mod_type&& other) noexcept
|
||||||
|
: type(other.type) {
|
||||||
|
switch (type) {
|
||||||
|
case Array: new (&value.array) array_value(other.value.array); break;
|
||||||
|
case Slice: new (&value.slice) slice_value(other.value.slice); break;
|
||||||
|
case Import: new (&value.imprt) import_value(std::move(other.value.imprt)); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
other.type = Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
mod_type& operator=(mod_type&& other) noexcept {
|
||||||
|
if (this == &other) return *this;
|
||||||
|
type = other.type;
|
||||||
|
switch (type) {
|
||||||
|
case Array: new (&value.array) array_value(other.value.array); break;
|
||||||
|
case Slice: new (&value.slice) slice_value(other.value.slice); break;
|
||||||
|
case Import: new (&value.imprt) import_value(std::move(other.value.imprt)); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
other.type = Count;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
mod_type(const mod_type& other)
|
||||||
|
: type(other.type) {
|
||||||
|
switch (type) {
|
||||||
|
case Array: new (&value.array) array_value(other.value.array); break;
|
||||||
|
case Slice: new (&value.slice) slice_value(other.value.slice); break;
|
||||||
|
case Import: new (&value.imprt) import_value(other.value.imprt); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod_type& operator=(const mod_type& other) {
|
||||||
|
if (this == &other) return *this;
|
||||||
|
type = other.type;
|
||||||
|
switch (type) {
|
||||||
|
case Array: new (&value.array) array_value(other.value.array); break;
|
||||||
|
case Slice: new (&value.slice) slice_value(other.value.slice); break;
|
||||||
|
case Import: new (&value.imprt) import_value(other.value.imprt); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct breakpoint {
|
||||||
|
std::function<void(executor&, void*)> callback;
|
||||||
|
void* data = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class mod {
|
||||||
|
friend class function;
|
||||||
|
friend class serializer;
|
||||||
|
public:
|
||||||
|
using bytecode_t = std::vector<byte>; /**< An alias to a vector of bytes. */
|
||||||
|
|
||||||
|
static constexpr char MAGIC[4] = { 'F', 'u', 'r', 'M' }; /** Furvm module file magic. */
|
||||||
|
|
||||||
|
using native_function = std::function<void(executor&)>;
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructs a module.
|
||||||
|
*
|
||||||
|
* @param name Name of the module.
|
||||||
|
* @param args Arguments forwarded to bytecode's constructor.
|
||||||
|
*/
|
||||||
|
template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<bytecode_t, Args...>>>
|
||||||
|
mod(Args&&... args)
|
||||||
|
: m_bytecode(std::forward<Args>(args)...) {}
|
||||||
|
|
||||||
|
~mod() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move constructor.
|
||||||
|
*/
|
||||||
|
mod(mod&&) = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move constructor.
|
||||||
|
*/
|
||||||
|
mod& operator=(mod&&) = default;
|
||||||
|
|
||||||
|
mod(const mod&) = delete;
|
||||||
|
mod& operator=(const mod&) = delete;
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Returns a byte from bytecode of this module.
|
||||||
|
*
|
||||||
|
* @param offset An offset of the byte.
|
||||||
|
* @return The byte.
|
||||||
|
*/
|
||||||
|
byte byte_at(std::size_t offset) const { return m_bytecode.at(offset); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the module's bytecode.
|
||||||
|
*
|
||||||
|
* @return A reference to the bytecode.
|
||||||
|
*/
|
||||||
|
constexpr bytecode_t& bytecode() { return m_bytecode; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the module's bytecode.
|
||||||
|
*
|
||||||
|
* @return A constant reference to the bytecode.
|
||||||
|
*/
|
||||||
|
furlang::view<std::uint8_t> bytecode_view() const { return { m_bytecode.data(), m_bytecode.size() }; }
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Emplaces a function in the module's function container.
|
||||||
|
*
|
||||||
|
* Emplaces the function in module's function container and name to function map and public functions map.
|
||||||
|
*
|
||||||
|
* @param args Arguments forwarded into the container's emplace_back function.
|
||||||
|
* @return A handle to the emplaced function.
|
||||||
|
*/
|
||||||
|
template <typename... Args>
|
||||||
|
function_h emplace_function(Args&&... args) {
|
||||||
|
function_h function;
|
||||||
|
if constexpr (std::is_constructible_v<class function, Args...>) {
|
||||||
|
function = std::move(m_functions.emplace_back(std::forward<Args>(args)...));
|
||||||
|
} else {
|
||||||
|
function = std::move(m_functions.emplace(std::forward<Args>(args)...));
|
||||||
|
}
|
||||||
|
return std::move(function);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Emplaces a function in the module's function container.
|
||||||
|
*
|
||||||
|
* Emplaces the function in module's function container and name to function map.
|
||||||
|
*
|
||||||
|
* @param name Name of the function.
|
||||||
|
* @param args Arguments forwarded into the container's emplace_back function.
|
||||||
|
* @return A handle to the emplaced function.
|
||||||
|
*/
|
||||||
|
template <typename NameFwd,
|
||||||
|
typename... Args,
|
||||||
|
typename = std::enable_if_t<std::is_constructible_v<std::string, NameFwd>>>
|
||||||
|
function_h emplace_function(NameFwd&& name, Args&&... args) {
|
||||||
|
function_h function;
|
||||||
|
if constexpr (std::is_constructible_v<class function, Args...>) {
|
||||||
|
function = std::move(m_functions.emplace_back(std::forward<Args>(args)...));
|
||||||
|
} else {
|
||||||
|
function = std::move(m_functions.emplace(std::forward<Args>(args)...));
|
||||||
|
}
|
||||||
|
auto pair = std::make_pair(std::forward<NameFwd>(name), function->signature());
|
||||||
|
m_functionMap[function.id()] = pair;
|
||||||
|
m_functionSigs[std::move(pair)] = function.id();
|
||||||
|
return std::move(function);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a function from the module.
|
||||||
|
*
|
||||||
|
* @param id Identifier of the function.
|
||||||
|
* @return A handle to the function.
|
||||||
|
*/
|
||||||
|
auto function_at(function_id id) { return m_functions.at(id); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a function from the module.
|
||||||
|
*
|
||||||
|
* @param id Identifier of the function.
|
||||||
|
* @return A handle to the function.
|
||||||
|
*/
|
||||||
|
auto function_at(function_id id) const { return m_functions.at(id); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a function from the module.
|
||||||
|
*
|
||||||
|
* @param name Name of the function.
|
||||||
|
* @return A handle to the function.
|
||||||
|
*/
|
||||||
|
template <typename NameFwd,
|
||||||
|
typename SigFwd,
|
||||||
|
typename = std::enable_if_t<std::is_constructible_v<std::string, NameFwd> &&
|
||||||
|
std::is_constructible_v<function_sig, SigFwd>>>
|
||||||
|
auto function_at(NameFwd&& name, SigFwd&& signature) {
|
||||||
|
return function_at(
|
||||||
|
m_functionSigs.at(std::make_pair<>(std::forward<NameFwd>(name), std::forward<SigFwd>(signature))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Erases a function from the module's function container.
|
||||||
|
*
|
||||||
|
* @param id Identifier of the function.
|
||||||
|
*/
|
||||||
|
void erase_function(function_id id) {
|
||||||
|
m_functions.erase(id);
|
||||||
|
if (auto it = m_functionMap.find(id); it != m_functionMap.end()) {
|
||||||
|
m_functionSigs.erase(it->second);
|
||||||
|
m_functionMap.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handle_container<function_h>& functions() const { return m_functions; }
|
||||||
|
|
||||||
|
const auto& function_map() const { return m_functionMap; }
|
||||||
|
public:
|
||||||
|
template <typename NameFwd, typename Func>
|
||||||
|
void set_native_function(NameFwd&& name, Func&& func) {
|
||||||
|
m_nativeFunctions.emplace(std::forward<NameFwd>(name), std::forward<Func>(func));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename NameFwd>
|
||||||
|
native_function get_native_function(NameFwd&& name) const {
|
||||||
|
return m_nativeFunctions.at(std::forward<NameFwd>(name));
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Emplaces a type in the context.
|
||||||
|
*
|
||||||
|
* @param args Arguments forwarded to the type constructor.
|
||||||
|
* @return The emplaced type.
|
||||||
|
*/
|
||||||
|
template <typename... Args>
|
||||||
|
auto emplace_type(Args&&... args) {
|
||||||
|
if constexpr (std::is_constructible_v<mod_type, Args...>) {
|
||||||
|
return m_types.emplace_back(std::forward<Args>(args)...);
|
||||||
|
} else {
|
||||||
|
return m_types.emplace(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a type from the context.
|
||||||
|
*
|
||||||
|
* @param args type's id.
|
||||||
|
* @return A handle to the type.
|
||||||
|
*/
|
||||||
|
template <typename... Args>
|
||||||
|
auto type_at(Args&&... args) {
|
||||||
|
return m_types.at(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a type from the context.
|
||||||
|
*
|
||||||
|
* @param args type's id.
|
||||||
|
* @return A handle to the type.
|
||||||
|
*/
|
||||||
|
template <typename... Args>
|
||||||
|
auto type_at(Args&&... args) const {
|
||||||
|
return m_types.at(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Erases a type from the context.
|
||||||
|
*
|
||||||
|
* @param args type's id.
|
||||||
|
*/
|
||||||
|
template <typename... Args>
|
||||||
|
void erase_type(Args&&... args) {
|
||||||
|
m_types.erase(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handle_container<mod_type_h>& types() const { return m_types; }
|
||||||
|
public:
|
||||||
|
void set_global_variable_count(std::uint16_t count) {
|
||||||
|
m_globalVariables.resize(count);
|
||||||
|
m_globalVariables.shrink_to_fit();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint16_t get_global_variable_count() const { return static_cast<std::uint16_t>(m_globalVariables.size()); }
|
||||||
|
|
||||||
|
void store_global_variable(std::uint16_t var, thing<>&& thing) {
|
||||||
|
if (var >= m_globalVariables.size()) throw std::runtime_error("invalid slot");
|
||||||
|
m_globalVariables.emplace(m_globalVariables.cbegin() + var, std::move(thing));
|
||||||
|
}
|
||||||
|
|
||||||
|
void store_global_variable(std::uint16_t var, const thing<>& thing) {
|
||||||
|
if (var >= m_globalVariables.size()) throw std::runtime_error("invalid slot");
|
||||||
|
m_globalVariables.emplace(m_globalVariables.cbegin() + var, thing);
|
||||||
|
}
|
||||||
|
|
||||||
|
thing<>& load_global_variable(std::uint16_t var) {
|
||||||
|
if (var >= m_globalVariables.size()) throw std::runtime_error("invalid slot");
|
||||||
|
return m_globalVariables[var];
|
||||||
|
}
|
||||||
|
|
||||||
|
const thing<>& load_global_variable(std::uint16_t var) const {
|
||||||
|
if (var >= m_globalVariables.size()) throw std::runtime_error("invalid slot");
|
||||||
|
return m_globalVariables[var];
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<constant, Args...>>>
|
||||||
|
void emplace_constant(Args&&... args) {
|
||||||
|
m_constants.emplace_back(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
const constant& constant_at(constant_index index) const { return m_constants.at(index); }
|
||||||
|
public:
|
||||||
|
template <typename Fwd, typename = std::enable_if_t<std::is_constructible_v<breakpoint, Fwd>>>
|
||||||
|
void set_breakpoint(bytecode_pos pos, Fwd&& breakpoint) {
|
||||||
|
m_breakpoints[pos] = std::forward<Fwd>(breakpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool has_breakpoint(bytecode_pos pos) const { return m_breakpoints.find(pos) != m_breakpoints.end(); }
|
||||||
|
|
||||||
|
const breakpoint& breakpoint_at(bytecode_pos pos) const { return m_breakpoints.at(pos); }
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Prints the module in a bytecode form to an output stream.
|
||||||
|
*
|
||||||
|
* @param os Output stream.
|
||||||
|
* @return The output stream.
|
||||||
|
*/
|
||||||
|
std::ostream& serialize(std::ostream& os) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Loads a module in a bytecode form from an input stream.
|
||||||
|
*
|
||||||
|
* @param is Input stream.
|
||||||
|
* @return The loaded module.
|
||||||
|
*/
|
||||||
|
static mod load(std::istream& is);
|
||||||
|
private:
|
||||||
|
bytecode_t m_bytecode;
|
||||||
|
|
||||||
|
using pair_type = std::pair<std::string, function_sig>;
|
||||||
|
using pair_hash =
|
||||||
|
furlang::utility::pair_hash<std::string, function_sig, std::hash<std::string>, detail::function_sig_hash>;
|
||||||
|
std::unordered_map<pair_type, function_id, pair_hash> m_functionSigs;
|
||||||
|
std::unordered_map<function_id, pair_type> m_functionMap;
|
||||||
|
handle_container<function_h> m_functions;
|
||||||
|
|
||||||
|
handle_container<mod_type_h> m_types;
|
||||||
|
|
||||||
|
std::vector<thing<>> m_globalVariables;
|
||||||
|
|
||||||
|
std::vector<constant> m_constants;
|
||||||
|
|
||||||
|
std::unordered_map<std::string, native_function> m_nativeFunctions;
|
||||||
|
|
||||||
|
std::unordered_map<bytecode_pos, breakpoint> m_breakpoints;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace furvm
|
||||||
|
|
||||||
|
#endif // FURVM_MODULE_HPP
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
#ifndef FURVM_STACK_HPP
|
||||||
|
#define FURVM_STACK_HPP
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <new>
|
||||||
|
#include <stack>
|
||||||
|
|
||||||
|
namespace furvm {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct stack {
|
||||||
|
stack(std::size_t capacity = (1024ULL * 1024ULL) / sizeof(T))
|
||||||
|
: begin(new T[capacity]()), cursor(begin), capacity(capacity) {}
|
||||||
|
|
||||||
|
T* begin;
|
||||||
|
T* cursor;
|
||||||
|
std::size_t capacity;
|
||||||
|
std::stack<T*> frames;
|
||||||
|
|
||||||
|
void push_frame() { frames.push(cursor); }
|
||||||
|
|
||||||
|
void pop_frame() {
|
||||||
|
cursor = frames.top();
|
||||||
|
frames.pop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class stack_allocator {
|
||||||
|
public:
|
||||||
|
stack_allocator() = default;
|
||||||
|
|
||||||
|
stack_allocator(stack<T>& stack)
|
||||||
|
: m_ref(&stack) {}
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
constexpr stack_allocator(const stack_allocator<U>& other) noexcept
|
||||||
|
: m_ref(other.m_ref) {}
|
||||||
|
public:
|
||||||
|
T* allocate(std::size_t n) {
|
||||||
|
if (m_ref == nullptr) throw std::bad_alloc();
|
||||||
|
if (m_ref->capacity - (m_ref->cursor - m_ref->begin) < n) throw std::bad_alloc();
|
||||||
|
|
||||||
|
T* ptr = m_ref->cursor;
|
||||||
|
m_ref->cursor += n;
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void deallocate(T* ptr, std::size_t n) {}
|
||||||
|
private:
|
||||||
|
stack<T>* m_ref = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace furvm
|
||||||
|
|
||||||
|
#endif // FURVM_STACK_HPP
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,226 @@
|
|||||||
|
#ifndef FURVM_TYPES_HPP
|
||||||
|
#define FURVM_TYPES_HPP
|
||||||
|
|
||||||
|
#include "furvm/fwd.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <limits>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace furvm {
|
||||||
|
|
||||||
|
using s8 = std::int8_t;
|
||||||
|
using s16 = std::int16_t;
|
||||||
|
using s32 = std::int32_t;
|
||||||
|
using s64 = std::int64_t;
|
||||||
|
using u8 = std::uint8_t;
|
||||||
|
using u16 = std::uint16_t;
|
||||||
|
using u32 = std::uint32_t;
|
||||||
|
using u64 = std::uint64_t;
|
||||||
|
|
||||||
|
struct thing_type {
|
||||||
|
struct array_value {
|
||||||
|
thing_type* type;
|
||||||
|
std::size_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct slice_value {
|
||||||
|
thing_type* type;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum type { // NOLINT
|
||||||
|
S8 = 0,
|
||||||
|
S16,
|
||||||
|
S32,
|
||||||
|
S64,
|
||||||
|
U8,
|
||||||
|
U16,
|
||||||
|
U32,
|
||||||
|
U64,
|
||||||
|
String,
|
||||||
|
Ptr,
|
||||||
|
Ref,
|
||||||
|
Array,
|
||||||
|
Slice,
|
||||||
|
|
||||||
|
Count,
|
||||||
|
} type = Count;
|
||||||
|
union value {
|
||||||
|
std::nullptr_t null = nullptr;
|
||||||
|
thing_type* typeRef;
|
||||||
|
array_value array;
|
||||||
|
slice_value slice;
|
||||||
|
|
||||||
|
value() = default;
|
||||||
|
|
||||||
|
value(thing_type* type)
|
||||||
|
: typeRef(type) {}
|
||||||
|
|
||||||
|
value(thing_type* type, std::size_t size)
|
||||||
|
: array({}) {
|
||||||
|
array.type = type;
|
||||||
|
array.size = size;
|
||||||
|
}
|
||||||
|
} value;
|
||||||
|
|
||||||
|
static constexpr thing_type_id INVALID_ID = std::numeric_limits<thing_type_id>::max();
|
||||||
|
|
||||||
|
thing_type_id id = INVALID_ID;
|
||||||
|
|
||||||
|
bool operator==(const thing_type& other) const {
|
||||||
|
if (type != other.type) return false;
|
||||||
|
switch (type) {
|
||||||
|
case S8:
|
||||||
|
case S16:
|
||||||
|
case S32:
|
||||||
|
case S64:
|
||||||
|
case U8:
|
||||||
|
case U16:
|
||||||
|
case U32:
|
||||||
|
case U64:
|
||||||
|
case String: return true;
|
||||||
|
case Ptr:
|
||||||
|
case Ref: return *value.typeRef == *other.value.typeRef;
|
||||||
|
case Array: return *value.array.type == *other.value.array.type && value.array.size == other.value.array.size;
|
||||||
|
case Slice: return *value.slice.type == *other.value.slice.type;
|
||||||
|
case Count: break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const thing_type& other) const { return !this->operator==(other); }
|
||||||
|
|
||||||
|
static bool is_primitive(enum type type) {
|
||||||
|
switch (type) {
|
||||||
|
case S8:
|
||||||
|
case S16:
|
||||||
|
case S32:
|
||||||
|
case S64:
|
||||||
|
case U8:
|
||||||
|
case U16:
|
||||||
|
case U32:
|
||||||
|
case U64: return true;
|
||||||
|
case String:
|
||||||
|
case Ptr:
|
||||||
|
case Ref:
|
||||||
|
case Array:
|
||||||
|
case Slice: return false;
|
||||||
|
case Count: break;
|
||||||
|
}
|
||||||
|
throw std::runtime_error("unreachable");
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::size_t primitive_size(enum type type) {
|
||||||
|
switch (type) {
|
||||||
|
case thing_type::S8: return sizeof(s8);
|
||||||
|
case thing_type::S16: return sizeof(s16);
|
||||||
|
case thing_type::S32: return sizeof(s32);
|
||||||
|
case thing_type::S64: return sizeof(s64);
|
||||||
|
case thing_type::U8: return sizeof(u8);
|
||||||
|
case thing_type::U16: return sizeof(u16);
|
||||||
|
case thing_type::U32: return sizeof(u32);
|
||||||
|
case thing_type::U64: return sizeof(u64);
|
||||||
|
case thing_type::String:
|
||||||
|
case Ptr:
|
||||||
|
case Ref:
|
||||||
|
case Array:
|
||||||
|
case Slice: return 0;
|
||||||
|
case Count: break;
|
||||||
|
}
|
||||||
|
throw std::runtime_error("unreachable");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
template <typename T, typename = void>
|
||||||
|
struct overrides_thing_type_matching : std::false_type {};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct overrides_thing_type_matching<T, std::void_t<decltype(T::matches(std::declval<const thing_type&>()))>>
|
||||||
|
: std::is_same<decltype(T::matches(std::declval<const thing_type&>())), bool> {};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct thing_traits {
|
||||||
|
bool operator()(const thing_type& type) const {
|
||||||
|
if constexpr (overrides_thing_type_matching<T>::value) {
|
||||||
|
return T::matches(type);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct thing_traits<s8> {
|
||||||
|
bool operator()(const thing_type& type) const { return type.type == thing_type::S8; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct thing_traits<u8> {
|
||||||
|
bool operator()(const thing_type& type) const { return type.type == thing_type::U8; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct thing_traits<s16> {
|
||||||
|
bool operator()(const thing_type& type) const { return type.type == thing_type::S16; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct thing_traits<u16> {
|
||||||
|
bool operator()(const thing_type& type) const { return type.type == thing_type::U16; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct thing_traits<s32> {
|
||||||
|
bool operator()(const thing_type& type) const { return type.type == thing_type::S32; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct thing_traits<u32> {
|
||||||
|
bool operator()(const thing_type& type) const { return type.type == thing_type::U32; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct thing_traits<s64> {
|
||||||
|
bool operator()(const thing_type& type) const { return type.type == thing_type::S64; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct thing_traits<u64> {
|
||||||
|
bool operator()(const thing_type& type) const { return type.type == thing_type::U64; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Inner>
|
||||||
|
struct thing_traits<Inner*> {
|
||||||
|
bool operator()(const thing_type& type) const {
|
||||||
|
return (type.type == thing_type::Ptr || type.type == thing_type::Ref) &&
|
||||||
|
thing_traits<Inner>{}(*type.value.typeRef);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, typename Thing, typename = void>
|
||||||
|
struct cassignable_to_thing : std::false_type {};
|
||||||
|
|
||||||
|
template <typename T, typename Thing>
|
||||||
|
struct cassignable_to_thing<T,
|
||||||
|
Thing,
|
||||||
|
std::void_t<decltype(std::declval<thing_traits<T>>().assign_to(std::declval<Thing&>(), std::declval<const T&>()))>>
|
||||||
|
: std::true_type {};
|
||||||
|
|
||||||
|
template <typename T, typename Thing, typename = void>
|
||||||
|
struct massignable_to_thing : std::false_type {};
|
||||||
|
|
||||||
|
template <typename T, typename Thing>
|
||||||
|
struct massignable_to_thing<T,
|
||||||
|
Thing,
|
||||||
|
std::void_t<decltype(std::declval<thing_traits<T>>().assign_to(std::declval<Thing&>(), std::declval<T&&>()))>>
|
||||||
|
: std::true_type {};
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
} // namespace furvm
|
||||||
|
|
||||||
|
#endif // FURVM_TYPES_HPP
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
file(GLOB_RECURSE DISFURAS_SRCS "${CMAKE_SOURCE_DIR}/src/disfuras/**.cpp")
|
||||||
|
add_executable(disfuras ${DISFURAS_SRCS})
|
||||||
|
target_include_directories(disfuras PRIVATE ${CMAKE_SOURCE_DIR}/include/)
|
||||||
|
target_link_libraries(disfuras PUBLIC furlang libfurc libfurvm)
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
#include "furvm/instruction.hpp"
|
||||||
|
#include "furvm/module.hpp"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <exception>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
void print_type(const furvm::mod_type& type, const furvm::mod& mod) {
|
||||||
|
switch (type.type) {
|
||||||
|
case furvm::mod_type::S8: std::cout << "$s8"; return;
|
||||||
|
case furvm::mod_type::S16: std::cout << "$s16"; return;
|
||||||
|
case furvm::mod_type::S32: std::cout << "$s32"; return;
|
||||||
|
case furvm::mod_type::S64: std::cout << "$s64"; return;
|
||||||
|
case furvm::mod_type::U8: std::cout << "$u8"; return;
|
||||||
|
case furvm::mod_type::U16: std::cout << "$u16"; return;
|
||||||
|
case furvm::mod_type::U32: std::cout << "$u32"; return;
|
||||||
|
case furvm::mod_type::U64: std::cout << "$u64"; return;
|
||||||
|
case furvm::mod_type::Ptr:
|
||||||
|
std::cout << "ptr ";
|
||||||
|
print_type(*mod.type_at(type.value.typeRef), mod);
|
||||||
|
return;
|
||||||
|
case furvm::mod_type::Ref:
|
||||||
|
std::cout << "ref ";
|
||||||
|
print_type(*mod.type_at(type.value.typeRef), mod);
|
||||||
|
return;
|
||||||
|
case furvm::mod_type::Array:
|
||||||
|
std::cout << "array ";
|
||||||
|
print_type(*mod.type_at(type.value.array.typeId), mod);
|
||||||
|
if (type.value.array.size == 0) {
|
||||||
|
std::cout << " dynamic";
|
||||||
|
} else {
|
||||||
|
std::cout << ' ' << type.value.array.size;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case furvm::mod_type::Import: std::cout << "import " << type.value.imprt.modId << "::t" << type.value.imprt.typeId;
|
||||||
|
case furvm::mod_type::Count: break;
|
||||||
|
}
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* typeNames[furvm::instruction::Count] = {
|
||||||
|
// NoOperation:
|
||||||
|
"nop",
|
||||||
|
// PushS8:
|
||||||
|
"push $__t0",
|
||||||
|
// PushU8:
|
||||||
|
"push $__t1",
|
||||||
|
// PushS16:
|
||||||
|
"push $__t2",
|
||||||
|
// PushU16:
|
||||||
|
"push $__t3",
|
||||||
|
// PushS32:
|
||||||
|
"push $__t4",
|
||||||
|
// PushU32:
|
||||||
|
"push $__t5",
|
||||||
|
// PushConstant:
|
||||||
|
"push",
|
||||||
|
// Array:
|
||||||
|
"array",
|
||||||
|
// Get:
|
||||||
|
"get",
|
||||||
|
// Set:
|
||||||
|
"set",
|
||||||
|
// Drop:
|
||||||
|
"drop",
|
||||||
|
// Duplicate:
|
||||||
|
"dup",
|
||||||
|
// Swap:
|
||||||
|
"swap",
|
||||||
|
// Clone:
|
||||||
|
"clone",
|
||||||
|
// Reference:
|
||||||
|
"ref",
|
||||||
|
// Add:
|
||||||
|
"add",
|
||||||
|
// Sub:
|
||||||
|
"sub",
|
||||||
|
// Mul:
|
||||||
|
"mul",
|
||||||
|
// Div:
|
||||||
|
"div",
|
||||||
|
// Mod:
|
||||||
|
"mod",
|
||||||
|
// Equals:
|
||||||
|
"eq",
|
||||||
|
// NotEquals:
|
||||||
|
"ne",
|
||||||
|
// LessThan:
|
||||||
|
"lt",
|
||||||
|
// GreaterThan:
|
||||||
|
"gt",
|
||||||
|
// LessEqual:
|
||||||
|
"le",
|
||||||
|
// GreaterEqual:
|
||||||
|
"ge",
|
||||||
|
// Pointerof:
|
||||||
|
"pointerof",
|
||||||
|
// Sizeof:
|
||||||
|
"sizeof",
|
||||||
|
// Lengthof:
|
||||||
|
"lengthof",
|
||||||
|
// Load:
|
||||||
|
"load",
|
||||||
|
// Store:
|
||||||
|
"store",
|
||||||
|
// LoadGlobal:
|
||||||
|
"loadg",
|
||||||
|
// StoreGlobal:
|
||||||
|
"storeg",
|
||||||
|
// Call:
|
||||||
|
"call",
|
||||||
|
// Jump:
|
||||||
|
"jmp",
|
||||||
|
// JumpNotZero:
|
||||||
|
"jnz",
|
||||||
|
// Return:
|
||||||
|
"ret",
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
if (argc != 2) {
|
||||||
|
std::cerr << "Usage: " << argv[0] << " <module.fmod>\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::ifstream file(argv[1], std::ios::binary | std::ios::in);
|
||||||
|
|
||||||
|
furvm::mod mod;
|
||||||
|
try {
|
||||||
|
mod = furvm::mod::load(file);
|
||||||
|
} catch (const std::exception& ex) {
|
||||||
|
std::cerr << "Failed to load module " << argv[1] << ": " << ex.what() << '\n';
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "; Generated with disfuras\n";
|
||||||
|
|
||||||
|
std::cout << "; Types:\n";
|
||||||
|
for (const auto& ptr : mod.types()) {
|
||||||
|
const auto& [header, type] = *ptr;
|
||||||
|
std::cout << "type __t" << header.id() << " = ";
|
||||||
|
print_type(type, mod);
|
||||||
|
std::cout << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_map<furvm::function_id, std::string> funcNames;
|
||||||
|
std::size_t unnamedCounter = 0;
|
||||||
|
|
||||||
|
std::unordered_map<std::size_t, std::string> labels;
|
||||||
|
std::unordered_set<std::size_t> deadLabels;
|
||||||
|
std::size_t labelCounter = 0;
|
||||||
|
|
||||||
|
std::cout << "\n; Functions:\n";
|
||||||
|
for (const auto& ptr : mod.functions()) {
|
||||||
|
const auto& [header, func] = *ptr;
|
||||||
|
auto it = mod.function_map().find(header.id());
|
||||||
|
if (it != mod.function_map().end()) {
|
||||||
|
std::cout << "public func " << (funcNames[header.id()] = it->second.first);
|
||||||
|
} else {
|
||||||
|
std::cout << "func " << (funcNames[header.id()] = "__f"s + std::to_string(unnamedCounter++));
|
||||||
|
}
|
||||||
|
for (const auto& param : func.signature().params) {
|
||||||
|
std::cout << " $__t" << param.id();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << " = ";
|
||||||
|
if (func.signature().returnType.has_value())
|
||||||
|
std::cout << "$__t" << func.signature().returnType->id() << ' '; // NOLINT
|
||||||
|
switch (func.type()) {
|
||||||
|
case furvm::function_t::Normal: {
|
||||||
|
auto it = labels.find(func.position());
|
||||||
|
if (it == labels.end()) it = labels.emplace(func.position(), funcNames[header.id()]).first;
|
||||||
|
deadLabels.insert(func.position());
|
||||||
|
std::cout << '#' << it->second;
|
||||||
|
} break;
|
||||||
|
case furvm::function_t::Native: {
|
||||||
|
std::cout << "native " << func.native();
|
||||||
|
} break;
|
||||||
|
case furvm::function_t::Import: {
|
||||||
|
std::cout << "import " << func.imp().mod << "::" << func.imp().function;
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
std::cout << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mod.get_global_variable_count() > 0) {
|
||||||
|
std::cout << "\n; Global Variables:\n";
|
||||||
|
for (std::uint16_t i = 0; i < mod.get_global_variable_count(); ++i) {
|
||||||
|
std::cout << "allocate __g" << i << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "\n; Bytecode:\n";
|
||||||
|
// Label pass:
|
||||||
|
for (std::size_t off = 0; off < mod.bytecode().size();) {
|
||||||
|
furvm::instruction instr{};
|
||||||
|
off += instr.read(mod.bytecode_view().subview(off));
|
||||||
|
if (instr.type != furvm::instruction::Jump && instr.type != furvm::instruction::JumpNotZero) continue;
|
||||||
|
if (labels.find(off + instr.arg.s8) == labels.end())
|
||||||
|
labels[off + instr.arg.s8] = "__l"s + std::to_string(labelCounter++);
|
||||||
|
deadLabels.insert(off + instr.arg.s8);
|
||||||
|
}
|
||||||
|
// Actual printing pass:
|
||||||
|
for (std::size_t off = 0; off < mod.bytecode().size();) {
|
||||||
|
if (auto it = labels.find(off); it != labels.end()) {
|
||||||
|
std::cout << it->second << ":\n";
|
||||||
|
deadLabels.erase(off);
|
||||||
|
}
|
||||||
|
furvm::instruction instr{};
|
||||||
|
off += instr.read(mod.bytecode_view().subview(off));
|
||||||
|
std::cout << " " << typeNames[instr.type];
|
||||||
|
switch (instr.arg.type) {
|
||||||
|
case furvm::instruction_argument::None: break;
|
||||||
|
case furvm::instruction_argument::S8: std::cout << ' ' << std::to_string(instr.arg.s8); break;
|
||||||
|
case furvm::instruction_argument::U8: std::cout << ' ' << std::to_string(instr.arg.u8); break;
|
||||||
|
case furvm::instruction_argument::S16: std::cout << ' ' << instr.arg.s16; break;
|
||||||
|
case furvm::instruction_argument::U16: std::cout << ' ' << instr.arg.u16; break;
|
||||||
|
case furvm::instruction_argument::S32: std::cout << ' ' << std::to_string(instr.arg.s8); break;
|
||||||
|
case furvm::instruction_argument::U32: std::cout << ' ' << std::to_string(instr.arg.u8); break;
|
||||||
|
case furvm::instruction_argument::Constant: throw std::runtime_error("unimplemented");
|
||||||
|
case furvm::instruction_argument::Type: std::cout << " $__t" << instr.arg.u16; break;
|
||||||
|
case furvm::instruction_argument::Variable: std::cout << " %" << instr.arg.u16; break;
|
||||||
|
case furvm::instruction_argument::GlobalVariable: std::cout << " %__g" << instr.arg.u16; break;
|
||||||
|
case furvm::instruction_argument::Function: std::cout << ' ' << funcNames[instr.arg.s16]; break;
|
||||||
|
case furvm::instruction_argument::Offset: std::cout << " #" << labels[instr.arg.s16]; break;
|
||||||
|
case furvm::instruction_argument::Count: break;
|
||||||
|
}
|
||||||
|
std::cout << '\n';
|
||||||
|
}
|
||||||
|
if (!deadLabels.empty()) {
|
||||||
|
std::cerr << "Malformed module: invalid jumps\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} catch (const std::exception& ex) {
|
||||||
|
std::cerr << "Exception uncaught: " << ex.what() << '\n';
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Disassemble into FIR (furlang's IR)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
file(GLOB_RECURSE FURAS_SRCS "${CMAKE_SOURCE_DIR}/src/furas/**.cpp")
|
||||||
|
file(GLOB_RECURSE FURAS_HDRS "${CMAKE_SOURCE_DIR}/include/furas/**.hpp")
|
||||||
|
add_executable(furas ${FURAS_SRCS} ${FURAS_HDRS})
|
||||||
|
target_include_directories(furas PUBLIC ${CMAKE_SOURCE_DIR}/include/)
|
||||||
|
target_link_libraries(furas PRIVATE furlang libfurvm)
|
||||||
@@ -0,0 +1,622 @@
|
|||||||
|
#include "furas/gen.hpp"
|
||||||
|
|
||||||
|
#include "furas/token.hpp"
|
||||||
|
#include "furvm/function.hpp"
|
||||||
|
#include "furvm/fwd.hpp"
|
||||||
|
#include "furvm/instruction.hpp"
|
||||||
|
#include "furvm/module.hpp"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <limits>
|
||||||
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace furas {
|
||||||
|
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// NOLINTBEGIN
|
||||||
|
std::unordered_map<enum token::type, furvm::instruction_t> instructions = {
|
||||||
|
{ token::Array, furvm::instruction_t::Array },
|
||||||
|
{ token::Slice, furvm::instruction_t::Slice },
|
||||||
|
{ token::Get, furvm::instruction_t::Get },
|
||||||
|
{ token::Set, furvm::instruction_t::Set },
|
||||||
|
{ token::Drop, furvm::instruction_t::Drop },
|
||||||
|
{ token::Dup, furvm::instruction_t::Duplicate },
|
||||||
|
{ token::Swap, furvm::instruction_t::Swap },
|
||||||
|
{ token::Clone, furvm::instruction_t::Clone },
|
||||||
|
{ token::Ref, furvm::instruction_t::Reference },
|
||||||
|
{ token::Add, furvm::instruction_t::Add },
|
||||||
|
{ token::Sub, furvm::instruction_t::Sub },
|
||||||
|
{ token::Mul, furvm::instruction_t::Mul },
|
||||||
|
{ token::Div, furvm::instruction_t::Div },
|
||||||
|
{ token::Mod, furvm::instruction_t::Mod },
|
||||||
|
{ token::Eq, furvm::instruction_t::Equals },
|
||||||
|
{ token::Neq, furvm::instruction_t::NotEquals },
|
||||||
|
{ token::Lt, furvm::instruction_t::LessThan },
|
||||||
|
{ token::Gt, furvm::instruction_t::GreaterThan },
|
||||||
|
{ token::Le, furvm::instruction_t::LessEqual },
|
||||||
|
{ token::Ge, furvm::instruction_t::GreaterEqual },
|
||||||
|
{ token::Ptrof, furvm::instruction_t::Pointerof },
|
||||||
|
{ token::Sizeof, furvm::instruction_t::Sizeof },
|
||||||
|
{ token::Lenof, furvm::instruction_t::Lengthof },
|
||||||
|
{ token::Load, furvm::instruction_t::Load },
|
||||||
|
{ token::Store, furvm::instruction_t::Store },
|
||||||
|
{ token::LoadGlobal, furvm::instruction_t::LoadGlobal },
|
||||||
|
{ token::StoreGlobal, furvm::instruction_t::StoreGlobal },
|
||||||
|
{ token::Call, furvm::instruction_t::Call },
|
||||||
|
{ token::Jmp, furvm::instruction_t::Jump },
|
||||||
|
{ token::Jnz, furvm::instruction_t::JumpNotZero },
|
||||||
|
{ token::Ret, furvm::instruction_t::Return },
|
||||||
|
};
|
||||||
|
// NOLINTEND
|
||||||
|
|
||||||
|
const char* token_type(enum token::type type) {
|
||||||
|
switch (type) {
|
||||||
|
case token::Identifier: return "identifier";
|
||||||
|
case token::Signed: return "signed";
|
||||||
|
case token::Unsigned: return "unsigned";
|
||||||
|
case token::Monkey: return "'@'";
|
||||||
|
case token::Dolar: return "'$'";
|
||||||
|
case token::Sha256: return "'#'";
|
||||||
|
case token::Percent: return "'%'";
|
||||||
|
case token::EqSign: return "'='";
|
||||||
|
case token::Dot: return "'.'";
|
||||||
|
case token::Colon: return "':'";
|
||||||
|
case token::Func: return "func";
|
||||||
|
case token::Type: return "type";
|
||||||
|
case token::Native: return "native";
|
||||||
|
case token::Import: return "import";
|
||||||
|
case token::Public: return "public";
|
||||||
|
case token::Private: return "private";
|
||||||
|
case token::Allocate: return "allocate";
|
||||||
|
case token::Push: return "push";
|
||||||
|
case token::Array: return "array";
|
||||||
|
case token::Slice: return "slice";
|
||||||
|
case token::Get: return "get";
|
||||||
|
case token::Set: return "set";
|
||||||
|
case token::Drop: return "drop";
|
||||||
|
case token::Dup: return "dup";
|
||||||
|
case token::Swap: return "swap";
|
||||||
|
case token::Clone: return "clone";
|
||||||
|
case token::Ref: return "ref";
|
||||||
|
case token::Add: return "add";
|
||||||
|
case token::Sub: return "sub";
|
||||||
|
case token::Mul: return "mul";
|
||||||
|
case token::Div: return "div";
|
||||||
|
case token::Mod: return "mod";
|
||||||
|
case token::Eq: return "eq";
|
||||||
|
case token::Neq: return "neq";
|
||||||
|
case token::Lt: return "lt";
|
||||||
|
case token::Gt: return "gt";
|
||||||
|
case token::Le: return "le";
|
||||||
|
case token::Ge: return "ge";
|
||||||
|
case token::Ptrof: return "ptrof";
|
||||||
|
case token::Sizeof: return "sizeof";
|
||||||
|
case token::Lenof: return "lenof";
|
||||||
|
case token::Load: return "load";
|
||||||
|
case token::Store: return "store";
|
||||||
|
case token::LoadGlobal: return "loadg";
|
||||||
|
case token::StoreGlobal: return "storeg";
|
||||||
|
case token::Call: return "call";
|
||||||
|
case token::Jmp: return "jmp";
|
||||||
|
case token::Jnz: return "jnz";
|
||||||
|
case token::Ret: return "ret";
|
||||||
|
case token::Count: break;
|
||||||
|
}
|
||||||
|
throw std::runtime_error("unreachable");
|
||||||
|
}
|
||||||
|
|
||||||
|
struct mod_context {
|
||||||
|
struct label_context {
|
||||||
|
struct function_info {
|
||||||
|
std::string name;
|
||||||
|
furvm::function_sig signature;
|
||||||
|
bool pub;
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr std::size_t INVALID = std::numeric_limits<std::size_t>::max();
|
||||||
|
|
||||||
|
std::size_t offset = INVALID;
|
||||||
|
std::vector<function_info> functions;
|
||||||
|
std::vector<std::size_t> unknowns;
|
||||||
|
};
|
||||||
|
|
||||||
|
furvm::mod mod;
|
||||||
|
std::unordered_map<std::string, std::vector<furvm::function_h>> functions;
|
||||||
|
std::unordered_map<std::string, furvm::mod_type_h> types;
|
||||||
|
std::unordered_map<std::string, std::uint16_t> variables;
|
||||||
|
|
||||||
|
std::unordered_map<std::string, label_context> labels;
|
||||||
|
|
||||||
|
struct token_result {
|
||||||
|
generator_error error;
|
||||||
|
token value;
|
||||||
|
|
||||||
|
token* operator->() { return &value; }
|
||||||
|
token& operator*() { return value; }
|
||||||
|
|
||||||
|
bool operator!() const { return error.type != generator_error::Success; }
|
||||||
|
};
|
||||||
|
|
||||||
|
bool compare_types(const furvm::mod_type_h& lhs, const furvm::mod_type_h& rhs) const {
|
||||||
|
if (lhs->type != rhs->type) return false;
|
||||||
|
switch (lhs->type) {
|
||||||
|
case furvm::mod_type::S8:
|
||||||
|
case furvm::mod_type::S16:
|
||||||
|
case furvm::mod_type::S32:
|
||||||
|
case furvm::mod_type::S64:
|
||||||
|
case furvm::mod_type::U8:
|
||||||
|
case furvm::mod_type::U16:
|
||||||
|
case furvm::mod_type::U32:
|
||||||
|
case furvm::mod_type::U64: return true;
|
||||||
|
case furvm::mod_type::Ptr:
|
||||||
|
case furvm::mod_type::Ref:
|
||||||
|
return compare_types(mod.type_at(lhs->value.typeRef), mod.type_at(rhs->value.typeRef));
|
||||||
|
case furvm::mod_type::Array:
|
||||||
|
return lhs->value.array.size == rhs->value.array.size &&
|
||||||
|
compare_types(mod.type_at(lhs->value.array.typeId), mod.type_at(rhs->value.array.typeId));
|
||||||
|
case furvm::mod_type::Slice:
|
||||||
|
return compare_types(mod.type_at(lhs->value.slice.typeId), mod.type_at(rhs->value.slice.typeId));
|
||||||
|
case furvm::mod_type::Import:
|
||||||
|
return lhs->value.imprt.modId == rhs->value.imprt.modId &&
|
||||||
|
lhs->value.imprt.typeId == rhs->value.imprt.typeId;
|
||||||
|
case furvm::mod_type::Count: break;
|
||||||
|
}
|
||||||
|
throw std::runtime_error("unreachable");
|
||||||
|
}
|
||||||
|
|
||||||
|
furvm::function_h find_function(const std::string& name, const furvm::function_sig& signature) const {
|
||||||
|
if (auto it = functions.find(name); it != functions.end()) {
|
||||||
|
for (const auto& func : it->second) {
|
||||||
|
if (func->signature().params.size() != signature.params.size()) continue;
|
||||||
|
std::size_t idx = 0;
|
||||||
|
while (idx < signature.params.size()) {
|
||||||
|
if (!compare_types(func->signature().params[idx], signature.params[idx])) break;
|
||||||
|
++idx;
|
||||||
|
}
|
||||||
|
if (idx == signature.params.size()) return func;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
mod_context() {
|
||||||
|
types.emplace("s8", mod.emplace_type(furvm::mod_type::S8));
|
||||||
|
types.emplace("u8", mod.emplace_type(furvm::mod_type::U8));
|
||||||
|
types.emplace("s16", mod.emplace_type(furvm::mod_type::S16));
|
||||||
|
types.emplace("u16", mod.emplace_type(furvm::mod_type::U16));
|
||||||
|
types.emplace("s32", mod.emplace_type(furvm::mod_type::S32));
|
||||||
|
types.emplace("u32", mod.emplace_type(furvm::mod_type::U32));
|
||||||
|
types.emplace("s64", mod.emplace_type(furvm::mod_type::S64));
|
||||||
|
types.emplace("u64", mod.emplace_type(furvm::mod_type::U64));
|
||||||
|
}
|
||||||
|
|
||||||
|
static token_result next_token(lexer& lexer) {
|
||||||
|
auto token = lexer.next_token();
|
||||||
|
if (token.has_error()) {
|
||||||
|
switch (token.error().type) {
|
||||||
|
case lexer_error::EndOfFile:
|
||||||
|
return { { generator_error::UnexpectedEof, "Unexpected end of file" }, { token::Count } };
|
||||||
|
case lexer_error::UnknownCharacter:
|
||||||
|
return { { generator_error::UnknownCharacter, token.error().message }, { token::Count } };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { { generator_error::Success }, *token };
|
||||||
|
}
|
||||||
|
|
||||||
|
static token_result eat_token(lexer& lexer, enum token::type type) {
|
||||||
|
auto token = next_token(lexer);
|
||||||
|
if (!token) return token;
|
||||||
|
if (token.value.type != type) {
|
||||||
|
return { { generator_error::UnexpectedToken,
|
||||||
|
"Expected "s + token_type(type) + ", but got " + token_type(token->type) },
|
||||||
|
{ token::Count } };
|
||||||
|
}
|
||||||
|
return { { generator_error::Success }, token.value };
|
||||||
|
}
|
||||||
|
|
||||||
|
furvm::mod_type_h eat_type(lexer& lexer, const token& tok) {
|
||||||
|
switch (tok.type) {
|
||||||
|
case token::Dolar: {
|
||||||
|
auto result = eat_token(lexer, token::Identifier);
|
||||||
|
if (auto it = types.find(std::string(result->value.string)); it != types.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
throw std::runtime_error("unknown type");
|
||||||
|
}
|
||||||
|
case token::Import: {
|
||||||
|
throw std::runtime_error("unimplemented");
|
||||||
|
// auto typeNameRes = eat_token(lexer, token::Identifier);
|
||||||
|
// if (!typeNameRes) return typeNameRes.error;
|
||||||
|
// return { generator_error::Success };
|
||||||
|
}
|
||||||
|
case token::Ref: {
|
||||||
|
auto result = next_token(lexer);
|
||||||
|
if (!result) throw std::runtime_error("error");
|
||||||
|
|
||||||
|
auto inner = eat_type(lexer, result.value);
|
||||||
|
|
||||||
|
return mod.emplace_type(furvm::mod_type::Ref, inner.id());
|
||||||
|
}
|
||||||
|
case token::Array: {
|
||||||
|
auto result = next_token(lexer);
|
||||||
|
if (!result) throw std::runtime_error("error");
|
||||||
|
|
||||||
|
auto inner = eat_type(lexer, result.value);
|
||||||
|
|
||||||
|
auto size = eat_token(lexer, token::Unsigned);
|
||||||
|
if (!size) throw std::runtime_error("error");
|
||||||
|
|
||||||
|
return mod.emplace_type(inner.id(), size->value.uint);
|
||||||
|
}
|
||||||
|
case token::Slice: {
|
||||||
|
auto result = next_token(lexer);
|
||||||
|
|
||||||
|
if (!result) throw std::runtime_error("error");
|
||||||
|
|
||||||
|
auto inner = eat_type(lexer, result.value);
|
||||||
|
|
||||||
|
return mod.emplace_type(furvm::mod_type::Slice, inner.id());
|
||||||
|
}
|
||||||
|
default: throw std::runtime_error("error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
generator_error generate(lexer& lexer) {
|
||||||
|
auto result = next_token(lexer);
|
||||||
|
if (result.error.type == generator_error::UnexpectedEof) return { generator_error::Eof };
|
||||||
|
if (!result) return result.error;
|
||||||
|
switch (result->type) {
|
||||||
|
case token::Identifier: {
|
||||||
|
std::string labelName = std::string(result->value.string);
|
||||||
|
|
||||||
|
result = eat_token(lexer, token::Colon);
|
||||||
|
if (!result) return result.error;
|
||||||
|
|
||||||
|
auto& label = labels[labelName];
|
||||||
|
if (label.offset != label_context::INVALID)
|
||||||
|
return { generator_error::UnexpectedToken, "Label "s + labelName + " already exists" };
|
||||||
|
label.offset = mod.bytecode().size();
|
||||||
|
|
||||||
|
for (auto& func : label.functions) {
|
||||||
|
furvm::function_h handle =
|
||||||
|
(func.pub ? mod.emplace_function(func.name, std::move(func.signature), label.offset)
|
||||||
|
: mod.emplace_function(std::move(func.signature), label.offset));
|
||||||
|
|
||||||
|
functions[std::move(func.name)].push_back(handle);
|
||||||
|
handle.dispatch();
|
||||||
|
}
|
||||||
|
for (auto unknown : label.unknowns) {
|
||||||
|
const auto jmpOff = static_cast<std::ptrdiff_t>(label.offset) - static_cast<std::ptrdiff_t>(unknown);
|
||||||
|
if (jmpOff < std::numeric_limits<std::int8_t>::min() ||
|
||||||
|
jmpOff > std::numeric_limits<std::int8_t>::max()) {
|
||||||
|
assert(false); // TODO: Further jumps are not implemented
|
||||||
|
}
|
||||||
|
mod.bytecode()[unknown - 1] = static_cast<std::int8_t>(jmpOff);
|
||||||
|
}
|
||||||
|
label.functions = {};
|
||||||
|
label.unknowns = {};
|
||||||
|
return { generator_error::Success };
|
||||||
|
}
|
||||||
|
|
||||||
|
case token::Allocate: {
|
||||||
|
result = eat_token(lexer, token::Identifier);
|
||||||
|
if (!result) return result.error;
|
||||||
|
std::string name = std::string(result->value.string);
|
||||||
|
variables.emplace(name, variables.size());
|
||||||
|
mod.set_global_variable_count(variables.size());
|
||||||
|
return { generator_error::Success };
|
||||||
|
}
|
||||||
|
|
||||||
|
case token::Func:
|
||||||
|
case token::Type:
|
||||||
|
case token::Public:
|
||||||
|
case token::Private: {
|
||||||
|
bool pub = false;
|
||||||
|
if (result->type == token::Public) {
|
||||||
|
pub = true;
|
||||||
|
result = next_token(lexer);
|
||||||
|
if (!result) return { result.error };
|
||||||
|
} else if (result->type == token::Private) {
|
||||||
|
result = next_token(lexer);
|
||||||
|
if (!result) return { result.error };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result->type == token::Func) {
|
||||||
|
auto nameRes = next_token(lexer);
|
||||||
|
if (!nameRes) return nameRes.error;
|
||||||
|
|
||||||
|
furvm::function_sig signature;
|
||||||
|
|
||||||
|
result = next_token(lexer);
|
||||||
|
if (!result) return result.error;
|
||||||
|
while (result->type != token::EqSign) {
|
||||||
|
signature.params.push_back(eat_type(lexer, result.value));
|
||||||
|
|
||||||
|
result = next_token(lexer);
|
||||||
|
if (!result) return result.error;
|
||||||
|
}
|
||||||
|
if (result->type != token::EqSign) return result.error;
|
||||||
|
|
||||||
|
result = next_token(lexer);
|
||||||
|
if (!result) return result.error;
|
||||||
|
if (result->type == token::Dolar) {
|
||||||
|
result = eat_token(lexer, token::Identifier);
|
||||||
|
if (!result) return result.error;
|
||||||
|
if (auto it = types.find(std::string(result->value.string)); it != types.end()) {
|
||||||
|
signature.returnType = it->second;
|
||||||
|
} else {
|
||||||
|
return { generator_error::UnknownType, "Unknown type "s + std::string(result->value.string) };
|
||||||
|
}
|
||||||
|
|
||||||
|
result = next_token(lexer);
|
||||||
|
if (!result) return result.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string name = std::string(nameRes->value.string);
|
||||||
|
|
||||||
|
if (!find_function(name, signature).empty()) throw std::runtime_error("function already defined");
|
||||||
|
|
||||||
|
switch (result->type) {
|
||||||
|
case token::Sha256: {
|
||||||
|
result = eat_token(lexer, token::Identifier);
|
||||||
|
if (!result) return result.error;
|
||||||
|
|
||||||
|
std::string labelName = std::string(result->value.string);
|
||||||
|
std::size_t offset = 0;
|
||||||
|
if (auto it = labels.find(labelName); it != labels.end()) {
|
||||||
|
offset = it->second.offset;
|
||||||
|
} else {
|
||||||
|
labels[labelName].functions.emplace_back(
|
||||||
|
label_context::function_info{ std::move(name), std::move(signature), pub });
|
||||||
|
return { generator_error::Success };
|
||||||
|
}
|
||||||
|
|
||||||
|
furvm::function_h handle =
|
||||||
|
(pub ? mod.emplace_function(name, signature, offset) : mod.emplace_function(signature, offset));
|
||||||
|
functions[name].push_back(handle);
|
||||||
|
handle.dispatch();
|
||||||
|
|
||||||
|
return { generator_error::Success };
|
||||||
|
}
|
||||||
|
case token::Native: {
|
||||||
|
result = eat_token(lexer, token::Identifier);
|
||||||
|
if (!result) return result.error;
|
||||||
|
|
||||||
|
std::string nativeName = std::string(result->value.string);
|
||||||
|
|
||||||
|
std::string name = std::string(nameRes->value.string);
|
||||||
|
|
||||||
|
furvm::function_h handle = (pub ? mod.emplace_function(name, signature, std::move(nativeName))
|
||||||
|
: mod.emplace_function(signature, std::move(nativeName)));
|
||||||
|
functions[name].push_back(handle);
|
||||||
|
handle.dispatch();
|
||||||
|
|
||||||
|
return { generator_error::Success };
|
||||||
|
}
|
||||||
|
case token::Import: {
|
||||||
|
throw std::runtime_error("unimplemented");
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return { generator_error::UnexpectedToken,
|
||||||
|
"Unexpected token "s + token_type(result->type) +
|
||||||
|
", expected label name, `native`, or `import`" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result->type == token::Type) {
|
||||||
|
auto nameRes = eat_token(lexer, token::Identifier);
|
||||||
|
if (!nameRes) return nameRes.error;
|
||||||
|
|
||||||
|
result = eat_token(lexer, token::EqSign);
|
||||||
|
if (!result) return result.error;
|
||||||
|
|
||||||
|
result = next_token(lexer);
|
||||||
|
if (!result) return result.error;
|
||||||
|
|
||||||
|
auto type = eat_type(lexer, result.value);
|
||||||
|
types.emplace(std::string(nameRes->value.string), std::move(type));
|
||||||
|
return { generator_error::Success };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { generator_error::UnexpectedToken,
|
||||||
|
"Unexpected token "s + token_type(result->type) + ", expected either `func` or `type`" };
|
||||||
|
}
|
||||||
|
|
||||||
|
case token::Push: {
|
||||||
|
auto result = eat_token(lexer, token::Dolar);
|
||||||
|
if (!result) return result.error;
|
||||||
|
auto typeName = eat_token(lexer, token::Identifier);
|
||||||
|
if (!typeName) return result.error;
|
||||||
|
auto it = types.find(std::string(typeName->value.string));
|
||||||
|
if (it == types.end())
|
||||||
|
return { generator_error::UnexpectedToken, "Unknown type "s + std::string(typeName->value.string) };
|
||||||
|
// TODO: Add support for signed integers
|
||||||
|
auto value = eat_token(lexer, token::Unsigned);
|
||||||
|
if (!value) return result.error;
|
||||||
|
auto type = it->second;
|
||||||
|
switch (type->type) {
|
||||||
|
case furvm::mod_type::S8: {
|
||||||
|
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::PushS8));
|
||||||
|
mod.bytecode().push_back(value->value.uint & 0xFF);
|
||||||
|
} break;
|
||||||
|
case furvm::mod_type::U8: {
|
||||||
|
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::PushU8));
|
||||||
|
mod.bytecode().push_back(value->value.uint & 0xFF);
|
||||||
|
} break;
|
||||||
|
case furvm::mod_type::S16: {
|
||||||
|
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::PushS16));
|
||||||
|
mod.bytecode().push_back(value->value.uint & 0xFF);
|
||||||
|
mod.bytecode().push_back((value->value.uint >> 8) & 0xFF);
|
||||||
|
} break;
|
||||||
|
case furvm::mod_type::U16: {
|
||||||
|
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::PushU16));
|
||||||
|
mod.bytecode().push_back(value->value.uint & 0xFF);
|
||||||
|
mod.bytecode().push_back((value->value.uint >> 8) & 0xFF);
|
||||||
|
} break;
|
||||||
|
case furvm::mod_type::S32: {
|
||||||
|
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::PushS32));
|
||||||
|
mod.bytecode().push_back(value->value.uint & 0xFF);
|
||||||
|
} break;
|
||||||
|
case furvm::mod_type::U32: {
|
||||||
|
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::PushU32));
|
||||||
|
mod.bytecode().push_back(value->value.uint & 0xFF);
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
return { generator_error::UnexpectedToken, "Unexpected type "s + std::string(typeName->value.string) };
|
||||||
|
}
|
||||||
|
return { generator_error::Success };
|
||||||
|
}
|
||||||
|
|
||||||
|
case token::Array:
|
||||||
|
case token::Slice:
|
||||||
|
case token::Get:
|
||||||
|
case token::Set:
|
||||||
|
case token::Drop:
|
||||||
|
case token::Dup:
|
||||||
|
case token::Swap:
|
||||||
|
case token::Clone:
|
||||||
|
case token::Ref:
|
||||||
|
case token::Add:
|
||||||
|
case token::Sub:
|
||||||
|
case token::Mul:
|
||||||
|
case token::Div:
|
||||||
|
case token::Mod:
|
||||||
|
case token::Eq:
|
||||||
|
case token::Neq:
|
||||||
|
case token::Lt:
|
||||||
|
case token::Gt:
|
||||||
|
case token::Le:
|
||||||
|
case token::Ge:
|
||||||
|
case token::Ptrof:
|
||||||
|
case token::Sizeof:
|
||||||
|
case token::Lenof:
|
||||||
|
case token::Load:
|
||||||
|
case token::Store:
|
||||||
|
case token::LoadGlobal:
|
||||||
|
case token::StoreGlobal:
|
||||||
|
case token::Call:
|
||||||
|
case token::Jmp:
|
||||||
|
case token::Jnz:
|
||||||
|
case token::Ret: {
|
||||||
|
auto it = instructions.find(result->type);
|
||||||
|
assert(it != instructions.end());
|
||||||
|
furvm::instruction instr{ it->second };
|
||||||
|
instr.arg.type = furvm::instruction::s_arguments[instr.type];
|
||||||
|
switch (instr.arg.type) {
|
||||||
|
case furvm::instruction_argument::None: break;
|
||||||
|
case furvm::instruction_argument::Type: {
|
||||||
|
result = eat_token(lexer, token::Dolar);
|
||||||
|
if (!result) return result.error;
|
||||||
|
result = eat_token(lexer, token::Identifier);
|
||||||
|
if (!result) return result.error;
|
||||||
|
auto type = types.find(std::string(result->value.string));
|
||||||
|
if (type == types.end())
|
||||||
|
return { generator_error::UnknownType, "Unknown type "s + std::string(result->value.string) };
|
||||||
|
instr.arg.u32 = type->second.id();
|
||||||
|
} break;
|
||||||
|
case furvm::instruction_argument::Constant: {
|
||||||
|
assert(false); // TODO: Unimplemented
|
||||||
|
} break;
|
||||||
|
case furvm::instruction_argument::Variable: {
|
||||||
|
result = eat_token(lexer, token::Percent);
|
||||||
|
if (!result) return result.error;
|
||||||
|
result = eat_token(lexer, token::Unsigned);
|
||||||
|
if (!result) return result.error;
|
||||||
|
instr.arg.u16 = result->value.uint;
|
||||||
|
} break;
|
||||||
|
case furvm::instruction_argument::GlobalVariable: {
|
||||||
|
result = eat_token(lexer, token::Percent);
|
||||||
|
if (!result) return result.error;
|
||||||
|
result = eat_token(lexer, token::Identifier);
|
||||||
|
if (!result) return result.error;
|
||||||
|
if (auto it = variables.find(std::string(result->value.string)); it != variables.end())
|
||||||
|
instr.arg.u16 = it->second;
|
||||||
|
else
|
||||||
|
throw std::runtime_error("Unknown global variable");
|
||||||
|
} break;
|
||||||
|
case furvm::instruction_argument::Function: {
|
||||||
|
furvm::function_sig signature;
|
||||||
|
while ((result = next_token(lexer)).error.type == generator_error::Success &&
|
||||||
|
result->type != token::Identifier) {
|
||||||
|
signature.params.push_back(eat_type(lexer, result.value));
|
||||||
|
}
|
||||||
|
if (!result || result->type != token::Identifier) return result.error;
|
||||||
|
std::string name(result->value.string);
|
||||||
|
|
||||||
|
auto func = find_function(name, signature);
|
||||||
|
if (func.empty())
|
||||||
|
return { generator_error::UnknownType, "Unknown function "s + std::string(result->value.string) };
|
||||||
|
instr.arg.u16 = func.id();
|
||||||
|
} break;
|
||||||
|
case furvm::instruction_argument::Offset: {
|
||||||
|
result = eat_token(lexer, token::Sha256);
|
||||||
|
if (!result) return result.error;
|
||||||
|
result = eat_token(lexer, token::Identifier);
|
||||||
|
if (!result) return result.error;
|
||||||
|
auto& label = labels[std::string(result->value.string)];
|
||||||
|
auto offset = label.offset;
|
||||||
|
if (offset == label_context::INVALID) {
|
||||||
|
label.unknowns.push_back(mod.bytecode().size() + 2);
|
||||||
|
instr.arg.s8 = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const auto jmpOff =
|
||||||
|
static_cast<std::ptrdiff_t>(offset) - static_cast<std::ptrdiff_t>(mod.bytecode().size()) - 2;
|
||||||
|
if (jmpOff < std::numeric_limits<std::int8_t>::min() ||
|
||||||
|
jmpOff > std::numeric_limits<std::int8_t>::max()) {
|
||||||
|
assert(false); // TODO: Further jumps are not implemented
|
||||||
|
}
|
||||||
|
instr.arg.s8 = static_cast<std::int8_t>(jmpOff);
|
||||||
|
} break;
|
||||||
|
case furvm::instruction_argument::S8:
|
||||||
|
case furvm::instruction_argument::U8:
|
||||||
|
case furvm::instruction_argument::S16:
|
||||||
|
case furvm::instruction_argument::U16:
|
||||||
|
case furvm::instruction_argument::S32:
|
||||||
|
case furvm::instruction_argument::U32:
|
||||||
|
case furvm::instruction_argument::Count:
|
||||||
|
default: throw std::runtime_error("unreachable");
|
||||||
|
}
|
||||||
|
instr.write(mod.bytecode());
|
||||||
|
return { generator_error::Success };
|
||||||
|
}
|
||||||
|
|
||||||
|
case token::Signed:
|
||||||
|
case token::Unsigned:
|
||||||
|
case token::Colon:
|
||||||
|
case token::Monkey:
|
||||||
|
case token::Dolar:
|
||||||
|
case token::Sha256:
|
||||||
|
case token::Percent:
|
||||||
|
case token::EqSign:
|
||||||
|
case token::Dot:
|
||||||
|
case token::Native:
|
||||||
|
case token::Import:
|
||||||
|
case token::Count: break;
|
||||||
|
}
|
||||||
|
return { generator_error::UnexpectedToken, "Unexpected token "s + token_type(result->type) };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
generator::result generator::generate(lexer lexer) {
|
||||||
|
mod_context context;
|
||||||
|
generator_error error;
|
||||||
|
do {
|
||||||
|
error = context.generate(lexer);
|
||||||
|
} while (error.type == generator_error::Success);
|
||||||
|
if (error.type == generator_error::Eof) return { { generator_error::Success }, std::move(context.mod) };
|
||||||
|
return { error };
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace furas
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
#include "furas/lexer.hpp"
|
||||||
|
|
||||||
|
#include <cctype>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace furas {
|
||||||
|
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
token_r lexer::next_token() {
|
||||||
|
while (m_cursor < m_content.size() && std::isspace(m_content[m_cursor]) != 0) {
|
||||||
|
if (m_content[m_cursor] == '\n') {
|
||||||
|
m_lineStart = m_cursor + 1;
|
||||||
|
++m_column;
|
||||||
|
}
|
||||||
|
++m_cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Add support for single-line comments
|
||||||
|
// TODO: Add support for multi-line comments
|
||||||
|
|
||||||
|
if (m_cursor >= m_content.size()) return token_r::error(lexer_error{ lexer_error::EndOfFile, location() });
|
||||||
|
|
||||||
|
// TODO: Add support for negative integers (I am positive thanks to stasiu :v:)
|
||||||
|
// TODO: Add support for hexadecimal and binary numeric literals
|
||||||
|
if (std::isdigit(m_content[m_cursor]) != 0) {
|
||||||
|
std::uint64_t num = 0;
|
||||||
|
while (m_cursor < m_content.size() && std::isdigit(m_content[m_cursor]) != 0) {
|
||||||
|
num *= 10;
|
||||||
|
num += m_content[m_cursor++] - '0';
|
||||||
|
}
|
||||||
|
return { num };
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::unordered_map<std::string_view, enum token::type> s_tokens = {
|
||||||
|
{ "func", token::Func },
|
||||||
|
{ "type", token::Type },
|
||||||
|
{ "native", token::Native },
|
||||||
|
{ "import", token::Import },
|
||||||
|
{ "public", token::Public },
|
||||||
|
{ "private", token::Private },
|
||||||
|
{ "allocate", token::Allocate },
|
||||||
|
|
||||||
|
{ "push", token::Push },
|
||||||
|
{ "array", token::Array },
|
||||||
|
{ "slice", token::Slice },
|
||||||
|
{ "get", token::Get },
|
||||||
|
{ "set", token::Set },
|
||||||
|
{ "drop", token::Drop },
|
||||||
|
{ "dup", token::Dup },
|
||||||
|
{ "swap", token::Swap },
|
||||||
|
{ "clone", token::Clone },
|
||||||
|
{ "ref", token::Ref },
|
||||||
|
{ "add", token::Add },
|
||||||
|
{ "sub", token::Sub },
|
||||||
|
{ "mul", token::Mul },
|
||||||
|
{ "div", token::Div },
|
||||||
|
{ "mod", token::Mod },
|
||||||
|
{ "eq", token::Eq },
|
||||||
|
{ "neq", token::Neq },
|
||||||
|
{ "lt", token::Lt },
|
||||||
|
{ "gt", token::Gt },
|
||||||
|
{ "le", token::Le },
|
||||||
|
{ "ge", token::Ge },
|
||||||
|
{ "ptrof", token::Ptrof },
|
||||||
|
{ "sizeof", token::Sizeof },
|
||||||
|
{ "lenof", token::Lenof },
|
||||||
|
{ "load", token::Load },
|
||||||
|
{ "store", token::Store },
|
||||||
|
{ "loadg", token::LoadGlobal },
|
||||||
|
{ "storeg", token::StoreGlobal },
|
||||||
|
{ "call", token::Call },
|
||||||
|
{ "jmp", token::Jmp },
|
||||||
|
{ "jnz", token::Jnz },
|
||||||
|
{ "ret", token::Ret },
|
||||||
|
};
|
||||||
|
|
||||||
|
if (std::isalnum(m_content[m_cursor]) != 0) {
|
||||||
|
std::size_t begin = m_cursor++;
|
||||||
|
while (m_cursor < m_content.size() && (std::isalnum(m_content[m_cursor]) != 0 || m_content[m_cursor] == '_'))
|
||||||
|
++m_cursor;
|
||||||
|
std::string_view str = m_content.substr(begin, m_cursor - begin);
|
||||||
|
if (auto it = s_tokens.find(str); it != s_tokens.end()) return { it->second };
|
||||||
|
return { token::Identifier, str };
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (m_content[m_cursor]) {
|
||||||
|
case '@': ++m_cursor; return { token::Monkey };
|
||||||
|
case '$': ++m_cursor; return { token::Dolar };
|
||||||
|
case '#': ++m_cursor; return { token::Sha256 };
|
||||||
|
case '%': ++m_cursor; return { token::Percent };
|
||||||
|
case '=': ++m_cursor; return { token::EqSign };
|
||||||
|
case '.': ++m_cursor; return { token::Dot };
|
||||||
|
case ':': ++m_cursor; return { token::Colon };
|
||||||
|
default:
|
||||||
|
return token_r::error(lexer_error{ lexer_error::UnknownCharacter,
|
||||||
|
location(),
|
||||||
|
"Unknown character '"s + m_content[m_cursor] + "'" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace furas
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#include "furas/gen.hpp"
|
||||||
|
#include "furas/lexer.hpp"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <furvm/module.hpp>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int main(int argc, char** argv) { // NOLINT
|
||||||
|
if (argc < 2) {
|
||||||
|
std::cerr << "feed me more arguments daddy >_<\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (argc > 2) {
|
||||||
|
std::cerr << "too much O_O\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* filepath = argv[1];
|
||||||
|
|
||||||
|
std::ifstream file(filepath, std::ios::binary | std::ios::ate | std::ios::in);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
std::cerr << "file won't open >~<\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
std::string content;
|
||||||
|
content.resize(file.tellg());
|
||||||
|
file.seekg(0);
|
||||||
|
file.read(content.data(), static_cast<std::streamsize>(content.size()));
|
||||||
|
|
||||||
|
auto result = furas::generator::generate(furas::lexer(filepath, content));
|
||||||
|
if (result.error.type != furas::generator_error::Success) {
|
||||||
|
std::cerr << result.error.message << '\n';
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.mod.serialize(std::cout);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
file(GLOB_RECURSE FURC_SRCS "${CMAKE_SOURCE_DIR}/src/furc/**.cpp")
|
||||||
|
file(GLOB_RECURSE FURC_HDRS "${CMAKE_SOURCE_DIR}/include/furc/**.hpp")
|
||||||
|
add_library(libfurc ${FURC_SRCS} ${FURC_HDRS})
|
||||||
|
target_include_directories(libfurc PUBLIC ${CMAKE_SOURCE_DIR}/include/)
|
||||||
|
target_compile_definitions(libfurc PRIVATE LIBFURC)
|
||||||
|
set_target_properties(libfurc PROPERTIES PREFIX "")
|
||||||
|
target_link_libraries(libfurc PUBLIC furlang libfurvm)
|
||||||
|
add_executable(furc "${CMAKE_SOURCE_DIR}/src/furc/main.cpp")
|
||||||
|
target_link_libraries(furc PRIVATE libfurc)
|
||||||
|
|
||||||
|
include(GoogleTest)
|
||||||
|
|
||||||
|
add_executable(furc_tests "${CMAKE_SOURCE_DIR}/test/furc/ssa.cpp")
|
||||||
|
target_link_libraries(furc_tests PRIVATE libfurc GTest::gtest_main)
|
||||||
|
gtest_discover_tests(furc_tests)
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#include "furc/back/furvm/target.hpp"
|
||||||
|
|
||||||
|
#include "furvm/function.hpp"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace furc {
|
||||||
|
|
||||||
|
furvm::mod furvm_generator::generate(const ir_module& mod) {
|
||||||
|
if (!mod.variables.empty()) throw std::runtime_error("global variables are not supported for furvm yet");
|
||||||
|
|
||||||
|
furvm::mod furvmMod;
|
||||||
|
for (const auto& func : mod.functions) {
|
||||||
|
furvmMod.emplace_function(furvm::function_sig{ {}, {} }, furvmMod.bytecode().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
return furvmMod;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace furc
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
#include "furc/front/lexer.hpp"
|
||||||
|
|
||||||
|
#include <cctype>
|
||||||
|
#include <limits>
|
||||||
|
#include <string_view>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace furc {
|
||||||
|
|
||||||
|
token lexer::next_token() {
|
||||||
|
if (m_peekToken.empty()) return get_token();
|
||||||
|
auto tok = m_peekToken.front();
|
||||||
|
m_peekToken.pop_front();
|
||||||
|
return tok;
|
||||||
|
}
|
||||||
|
|
||||||
|
token lexer::peek_token(std::size_t offset) {
|
||||||
|
if (m_peekToken.size() <= offset) {
|
||||||
|
while (m_peekToken.size() <= offset) {
|
||||||
|
auto tok = get_token();
|
||||||
|
m_peekToken.push_back(tok);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m_peekToken[offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
token lexer::get_token() {
|
||||||
|
skip_spaces();
|
||||||
|
|
||||||
|
if (m_cursor >= m_content.size()) return { location(), token::EndOfFile };
|
||||||
|
|
||||||
|
auto loc = location();
|
||||||
|
|
||||||
|
if (std::isdigit(get()) != 0) {
|
||||||
|
std::uint64_t value = get() - '0';
|
||||||
|
next();
|
||||||
|
while (m_cursor < m_content.size() && std::isdigit(get()) != 0) {
|
||||||
|
static constexpr std::uint64_t MAX = std::numeric_limits<std::uint64_t>::max();
|
||||||
|
static constexpr std::uint64_t MAX_MULTS = MAX / 10;
|
||||||
|
static constexpr std::uint64_t LAST_DIGIT = MAX % 10;
|
||||||
|
|
||||||
|
if (value > MAX_MULTS) {
|
||||||
|
return { location(), token::InvalidInteger };
|
||||||
|
}
|
||||||
|
std::uint64_t digit = get() - '0';
|
||||||
|
if (value == MAX_MULTS && digit > LAST_DIGIT) {
|
||||||
|
return { location(), token::InvalidInteger };
|
||||||
|
}
|
||||||
|
|
||||||
|
value *= 10;
|
||||||
|
value += digit;
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
return { loc, value };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::isalnum(get()) != 0 || get() == '_') {
|
||||||
|
static std::unordered_map<std::string_view, token_t> s_keywords = {
|
||||||
|
{ "func", token::Func },
|
||||||
|
{ "return", token::Return },
|
||||||
|
{ "if", token::If },
|
||||||
|
{ "else", token::Else },
|
||||||
|
{ "while", token::While },
|
||||||
|
{ "public", token::Public },
|
||||||
|
{ "private", token::Private },
|
||||||
|
{ "pre", token::Pre },
|
||||||
|
{ "post", token::Post },
|
||||||
|
{ "pointerof", token::Pointerof },
|
||||||
|
{ "sizeof", token::Sizeof },
|
||||||
|
{ "lengthof", token::Lengthof },
|
||||||
|
{ "s8", token::S8 },
|
||||||
|
{ "u8", token::U8 },
|
||||||
|
{ "s16", token::S16 },
|
||||||
|
{ "u16", token::U16 },
|
||||||
|
{ "s32", token::S32 },
|
||||||
|
{ "u32", token::U32 },
|
||||||
|
{ "s64", token::S64 },
|
||||||
|
{ "u64", token::U64 },
|
||||||
|
};
|
||||||
|
|
||||||
|
std::size_t begin = m_cursor;
|
||||||
|
next();
|
||||||
|
while (m_cursor < m_content.size() && (std::isalnum(get()) != 0 || get() == '_'))
|
||||||
|
next();
|
||||||
|
std::string_view name = m_content.substr(begin, m_cursor - begin);
|
||||||
|
if (auto it = s_keywords.find(name); it != s_keywords.end()) {
|
||||||
|
return { loc, it->second };
|
||||||
|
}
|
||||||
|
return { loc, token::Identifier, name };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (get() == '"') {
|
||||||
|
next();
|
||||||
|
|
||||||
|
std::size_t begin = m_cursor;
|
||||||
|
while (m_cursor < m_content.size() && get() != '"')
|
||||||
|
next();
|
||||||
|
if (m_cursor >= m_content.size()) return { location(), token::UnexpectedEOF };
|
||||||
|
|
||||||
|
next();
|
||||||
|
return { loc, token::String, m_content.substr(begin, m_cursor - begin - 1) };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (get() == '\'') {
|
||||||
|
next();
|
||||||
|
bool slash = get() == '\\';
|
||||||
|
if (slash) next();
|
||||||
|
auto loc2 = location();
|
||||||
|
char character = get();
|
||||||
|
next();
|
||||||
|
if (get() != '\'') return { location(), token::UnexpectedCharacter, get() };
|
||||||
|
next();
|
||||||
|
if (slash) {
|
||||||
|
switch (character) {
|
||||||
|
case '\\': character = '\\'; break;
|
||||||
|
case 'n': character = '\n'; break;
|
||||||
|
case 'r': character = '\r'; break;
|
||||||
|
case 't': character = '\t'; break;
|
||||||
|
default: return { loc2, token::UnexpectedCharacter, character };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { loc, token::Char, character };
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::unordered_map<std::string_view, token_t> s_tokens = {
|
||||||
|
{ "(", token::LParen },
|
||||||
|
{ ")", token::RParen },
|
||||||
|
{ "{", token::LBrace },
|
||||||
|
{ "}", token::RBrace },
|
||||||
|
{ "[", token::LBracket },
|
||||||
|
{ "]", token::RBracket },
|
||||||
|
{ ";", token::Semicolon },
|
||||||
|
{ ":", token::Colon },
|
||||||
|
{ ",", token::Comma },
|
||||||
|
{ ".", token::Dot },
|
||||||
|
{ "+", token::Plus },
|
||||||
|
{ "-", token::Minus },
|
||||||
|
{ "*", token::Star },
|
||||||
|
{ "/", token::Slash },
|
||||||
|
{ "%", token::Percent },
|
||||||
|
{ "&", token::Ampersand },
|
||||||
|
{ "|", token::Pipe },
|
||||||
|
{ "^", token::Hat },
|
||||||
|
{ "&&", token::DblAmpersand },
|
||||||
|
{ "||", token::DblPipe },
|
||||||
|
{ "++", token::DblPlus },
|
||||||
|
{ "--", token::DblMinus },
|
||||||
|
{ "!", token::ExMark },
|
||||||
|
{ "^^", token::CatEars },
|
||||||
|
{ "=", token::Equals },
|
||||||
|
{ "+=", token::PlusEquals },
|
||||||
|
{ "-=", token::MinusEquals },
|
||||||
|
{ "*=", token::StarEquals },
|
||||||
|
{ "/=", token::SlashEquals },
|
||||||
|
{ "%=", token::PercentEquals },
|
||||||
|
{ "&=", token::AmpersandEquals },
|
||||||
|
{ "|=", token::PipeEquals },
|
||||||
|
{ "^=", token::HatEquals },
|
||||||
|
{ "==", token::DblEquals },
|
||||||
|
{ "!=", token::ExEquals },
|
||||||
|
{ "<", token::LessThan },
|
||||||
|
{ "<=", token::LessEquals },
|
||||||
|
{ ">", token::GreaterThan },
|
||||||
|
{ ">=", token::GreaterEquals },
|
||||||
|
{ "->", token::SlimArrow },
|
||||||
|
{ "=>", token::FatArrow },
|
||||||
|
{ "@", token::Monkey },
|
||||||
|
{ "#", token::Sha256 },
|
||||||
|
};
|
||||||
|
|
||||||
|
std::size_t begin = m_cursor;
|
||||||
|
std::size_t len = 1;
|
||||||
|
while (begin + len - 1 < m_content.size() && s_tokens.find(m_content.substr(begin, len)) != s_tokens.end())
|
||||||
|
++len;
|
||||||
|
|
||||||
|
if (len > 1) {
|
||||||
|
auto type = s_tokens[m_content.substr(begin, len - 1)];
|
||||||
|
m_cursor += len - 1;
|
||||||
|
return { loc, type };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { loc, token::UnexpectedCharacter, get() };
|
||||||
|
}
|
||||||
|
|
||||||
|
void lexer::next() {
|
||||||
|
if (m_cursor < m_content.size()) ++m_cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr char lexer::get(std::size_t offset) const {
|
||||||
|
return m_content[m_cursor + offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
void lexer::skip_spaces() {
|
||||||
|
while (m_cursor < m_content.size() && std::isspace(get()) != 0) {
|
||||||
|
if (m_content[m_cursor++] == '\n') {
|
||||||
|
++m_row;
|
||||||
|
m_lineStart = m_cursor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr token::location lexer::location() const {
|
||||||
|
return { m_filepath, m_row, m_cursor - m_lineStart };
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace furc
|
||||||
@@ -0,0 +1,385 @@
|
|||||||
|
#include "furc/front/parser.hpp"
|
||||||
|
|
||||||
|
#include "furc/front/ast.hpp"
|
||||||
|
#include "furc/front/token.hpp"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace furc {
|
||||||
|
|
||||||
|
ast parser::parse() {
|
||||||
|
ast tree;
|
||||||
|
while (m_lexer.peek_token().type != token::EndOfFile) {
|
||||||
|
auto* decl = parse_decl();
|
||||||
|
assert(decl);
|
||||||
|
tree.decls.push_back(decl);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::move(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt_node* parser::parse_stmt() {
|
||||||
|
switch (m_lexer.peek_token().type) {
|
||||||
|
case token::LBrace: return m_arena->allocate<comp_stmt_node>(parse_comp());
|
||||||
|
case token::Return: {
|
||||||
|
m_lexer.next_token();
|
||||||
|
return_stmt_node node;
|
||||||
|
if (m_lexer.peek_token().type != token::Semicolon) {
|
||||||
|
node.value = parse_expr();
|
||||||
|
}
|
||||||
|
eat_token(token::Semicolon);
|
||||||
|
return m_arena->allocate<return_stmt_node>(std::move(node));
|
||||||
|
}
|
||||||
|
case token::If: {
|
||||||
|
m_lexer.next_token();
|
||||||
|
eat_token(token::LParen);
|
||||||
|
if_stmt_node node;
|
||||||
|
node.cond = parse_expr();
|
||||||
|
eat_token(token::RParen);
|
||||||
|
node.thenBranch = parse_stmt();
|
||||||
|
if (m_lexer.peek_token().type == token::Else) {
|
||||||
|
m_lexer.next_token();
|
||||||
|
node.elseBranch = parse_stmt();
|
||||||
|
}
|
||||||
|
return m_arena->allocate<if_stmt_node>(std::move(node));
|
||||||
|
}
|
||||||
|
case token::While: {
|
||||||
|
m_lexer.next_token();
|
||||||
|
eat_token(token::LParen);
|
||||||
|
while_stmt_node node;
|
||||||
|
node.cond = parse_expr();
|
||||||
|
eat_token(token::RParen);
|
||||||
|
node.body = parse_stmt();
|
||||||
|
return m_arena->allocate<while_stmt_node>(std::move(node));
|
||||||
|
}
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_lexer.peek_token().type == token::Func) {
|
||||||
|
eat_token(token::Func);
|
||||||
|
func_decl_node func;
|
||||||
|
|
||||||
|
func.name = std::string(eat_token(token::Identifier).value.string);
|
||||||
|
eat_token(token::LParen);
|
||||||
|
if (m_lexer.peek_token().type != token::RParen) {
|
||||||
|
do {
|
||||||
|
var_decl_node param;
|
||||||
|
|
||||||
|
param.name = std::string(eat_token(token::Identifier).value.string);
|
||||||
|
eat_token(token::Colon);
|
||||||
|
param.type = parse_type();
|
||||||
|
if (m_lexer.peek_token().type == token::Equals) {
|
||||||
|
m_lexer.next_token();
|
||||||
|
param.init = parse_expr();
|
||||||
|
}
|
||||||
|
func.params.emplace_back(std::move(param));
|
||||||
|
} while (eat_token(token::Comma, token::RParen).type == token::Comma);
|
||||||
|
} else {
|
||||||
|
eat_token(token::RParen);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_lexer.peek_token().type == token::SlimArrow) {
|
||||||
|
m_lexer.next_token();
|
||||||
|
func.type = parse_type();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_lexer.peek_token().type == token::Semicolon) {
|
||||||
|
m_lexer.next_token();
|
||||||
|
return m_arena->allocate<func_decl_node>(std::move(func));
|
||||||
|
}
|
||||||
|
|
||||||
|
func_decl_node::def_s def;
|
||||||
|
|
||||||
|
while (m_lexer.peek_token().type == token::Pre || m_lexer.peek_token().type == token::Post) {
|
||||||
|
auto token = m_lexer.next_token();
|
||||||
|
eat_token(token::LParen);
|
||||||
|
auto* expr = parse_expr();
|
||||||
|
eat_token(token::RParen);
|
||||||
|
|
||||||
|
switch (token.type) {
|
||||||
|
case token::Pre: {
|
||||||
|
def.preConds.push_back(expr);
|
||||||
|
} break;
|
||||||
|
case token::Post: {
|
||||||
|
def.postConds.push_back(expr);
|
||||||
|
} break;
|
||||||
|
default: throw std::runtime_error("unreachable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def.body = parse_comp();
|
||||||
|
func.def = std::move(def);
|
||||||
|
|
||||||
|
return m_arena->allocate<func_decl_node>(std::move(func));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_lexer.peek_token().type == token::Identifier &&
|
||||||
|
(m_lexer.peek_token(1).type == token::Colon || m_lexer.peek_token(1).type == token::Equals)) {
|
||||||
|
var_decl_node var;
|
||||||
|
var.name = std::string(eat_token(token::Identifier).value.string);
|
||||||
|
eat_token(token::Colon); // TODO: Auto-deduce the type
|
||||||
|
var.type = parse_type();
|
||||||
|
if (eat_token(token::Equals, token::Semicolon).type == token::Equals) {
|
||||||
|
var.init = parse_expr();
|
||||||
|
eat_token(token::Semicolon);
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_arena->allocate<var_decl_node>(std::move(var));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* expr = parse_expr();
|
||||||
|
eat_token(token::Semicolon);
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
decl_node* parser::parse_decl() {
|
||||||
|
auto first = eat_token(token::Identifier, token::Func);
|
||||||
|
if (first.type == token::Func) {
|
||||||
|
func_decl_node func;
|
||||||
|
|
||||||
|
func.name = std::string(eat_token(token::Identifier).value.string);
|
||||||
|
eat_token(token::LParen);
|
||||||
|
if (m_lexer.peek_token().type != token::RParen) {
|
||||||
|
do {
|
||||||
|
var_decl_node param;
|
||||||
|
|
||||||
|
param.name = std::string(eat_token(token::Identifier).value.string);
|
||||||
|
eat_token(token::Colon);
|
||||||
|
param.type = parse_type();
|
||||||
|
if (m_lexer.peek_token().type == token::Equals) {
|
||||||
|
m_lexer.next_token();
|
||||||
|
param.init = parse_expr();
|
||||||
|
}
|
||||||
|
func.params.emplace_back(std::move(param));
|
||||||
|
} while (eat_token(token::Comma, token::RParen).type == token::Comma);
|
||||||
|
} else {
|
||||||
|
eat_token(token::RParen);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_lexer.peek_token().type == token::SlimArrow) {
|
||||||
|
m_lexer.next_token();
|
||||||
|
func.type = parse_type();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_lexer.peek_token().type == token::Semicolon) {
|
||||||
|
m_lexer.next_token();
|
||||||
|
return m_arena->allocate<func_decl_node>(std::move(func));
|
||||||
|
}
|
||||||
|
|
||||||
|
func_decl_node::def_s def;
|
||||||
|
|
||||||
|
while (m_lexer.peek_token().type == token::Pre || m_lexer.peek_token().type == token::Post) {
|
||||||
|
auto token = m_lexer.next_token();
|
||||||
|
eat_token(token::LParen);
|
||||||
|
auto* expr = parse_expr();
|
||||||
|
eat_token(token::RParen);
|
||||||
|
|
||||||
|
switch (token.type) {
|
||||||
|
case token::Pre: {
|
||||||
|
def.preConds.push_back(expr);
|
||||||
|
} break;
|
||||||
|
case token::Post: {
|
||||||
|
def.postConds.push_back(expr);
|
||||||
|
} break;
|
||||||
|
default: throw std::runtime_error("unreachable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def.body = parse_comp();
|
||||||
|
func.def = std::move(def);
|
||||||
|
|
||||||
|
return m_arena->allocate<func_decl_node>(std::move(func));
|
||||||
|
}
|
||||||
|
|
||||||
|
var_decl_node var;
|
||||||
|
var.name = std::string(first.value.string);
|
||||||
|
eat_token(token::Colon); // TODO: Auto-deduce the type
|
||||||
|
var.type = parse_type();
|
||||||
|
if (eat_token(token::Equals, token::Semicolon).type == token::Equals) {
|
||||||
|
var.init = parse_expr();
|
||||||
|
eat_token(token::Semicolon);
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_arena->allocate<var_decl_node>(std::move(var));
|
||||||
|
}
|
||||||
|
|
||||||
|
expr_node* parser::parse_expr() {
|
||||||
|
return parse_expr_right(parse_expr_unary());
|
||||||
|
}
|
||||||
|
|
||||||
|
ast_type parser::parse_type() {
|
||||||
|
auto token =
|
||||||
|
eat_token(token::S8, token::U8, token::S16, token::U16, token::S32, token::U32, token::S64, token::U64);
|
||||||
|
switch (token.type) {
|
||||||
|
case token::S8: return { ast_type::S8 };
|
||||||
|
case token::U8: return { ast_type::U8 };
|
||||||
|
case token::S16: return { ast_type::S16 };
|
||||||
|
case token::U16: return { ast_type::U16 };
|
||||||
|
case token::S32: return { ast_type::S32 };
|
||||||
|
case token::U32: return { ast_type::U32 };
|
||||||
|
case token::S64: return { ast_type::S64 };
|
||||||
|
case token::U64: return { ast_type::U64 };
|
||||||
|
default: throw std::runtime_error("unreachable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
comp_stmt_node parser::parse_comp() {
|
||||||
|
comp_stmt_node comp;
|
||||||
|
|
||||||
|
eat_token(token::LBrace);
|
||||||
|
while (m_lexer.peek_token().type != token::EndOfFile && m_lexer.peek_token().type != token::RBrace) {
|
||||||
|
comp.stmts.push_back(parse_stmt());
|
||||||
|
}
|
||||||
|
eat_token(token::RBrace);
|
||||||
|
|
||||||
|
return comp;
|
||||||
|
}
|
||||||
|
|
||||||
|
expr_node* parser::parse_expr_primary() {
|
||||||
|
auto token = eat_token(token::Identifier, token::LParen, token::If, token::Integer, token::Char);
|
||||||
|
switch (token.type) {
|
||||||
|
case token::Identifier: {
|
||||||
|
return m_arena->allocate<var_read_expr_node>(std::string(token.value.string));
|
||||||
|
}
|
||||||
|
case token::LParen: {
|
||||||
|
group_expr_node group;
|
||||||
|
group.inner = parse_expr();
|
||||||
|
eat_token(token::RParen);
|
||||||
|
return m_arena->allocate<group_expr_node>(std::move(group));
|
||||||
|
}
|
||||||
|
case token::If: {
|
||||||
|
if_expr_node ifExpr;
|
||||||
|
eat_token(token::LParen);
|
||||||
|
ifExpr.cond = parse_expr();
|
||||||
|
eat_token(token::RParen);
|
||||||
|
ifExpr.thenExpr = parse_expr();
|
||||||
|
eat_token(token::Else);
|
||||||
|
ifExpr.elseExpr = parse_expr();
|
||||||
|
return m_arena->allocate<if_expr_node>(std::move(ifExpr));
|
||||||
|
}
|
||||||
|
case token::Integer: {
|
||||||
|
return m_arena->allocate<int_lit_node>(int_lit_node(token.value.integer));
|
||||||
|
}
|
||||||
|
case token::Char: {
|
||||||
|
return m_arena->allocate<char_lit_node>(char_lit_node(token.value.character));
|
||||||
|
}
|
||||||
|
default: throw std::runtime_error("unreachable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expr_node* parser::parse_expr_unary() {
|
||||||
|
static std::unordered_map<token_t, unary_op_expr_node::unary_op_type> s_prefixOps = {
|
||||||
|
{ token::Plus, unary_op_expr_node::Positive },
|
||||||
|
{ token::Minus, unary_op_expr_node::Negative },
|
||||||
|
{ token::DblPlus, unary_op_expr_node::PreInc },
|
||||||
|
{ token::DblMinus, unary_op_expr_node::PreDec },
|
||||||
|
{ token::Tilde, unary_op_expr_node::BinNot },
|
||||||
|
{ token::ExMark, unary_op_expr_node::Not },
|
||||||
|
|
||||||
|
{ token::Sizeof, unary_op_expr_node::Sizeof },
|
||||||
|
{ token::Pointerof, unary_op_expr_node::Pointerof },
|
||||||
|
{ token::Lengthof, unary_op_expr_node::Lengthof },
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::unordered_map<token_t, unary_op_expr_node::unary_op_type> s_postfixOps = {
|
||||||
|
{ token::DblPlus, unary_op_expr_node::PostInc },
|
||||||
|
{ token::DblMinus, unary_op_expr_node::PostDec },
|
||||||
|
};
|
||||||
|
|
||||||
|
auto it = s_prefixOps.find(m_lexer.peek_token().type);
|
||||||
|
if (it == s_prefixOps.end()) {
|
||||||
|
auto* expr = parse_expr_primary();
|
||||||
|
while (true) {
|
||||||
|
auto postIt = s_postfixOps.find(m_lexer.peek_token().type);
|
||||||
|
if (postIt == s_postfixOps.end()) return expr;
|
||||||
|
m_lexer.next_token();
|
||||||
|
|
||||||
|
unary_op_expr_node unary;
|
||||||
|
unary.lhs = expr;
|
||||||
|
unary.type = postIt->second;
|
||||||
|
expr = m_arena->allocate<unary_op_expr_node>(std::move(unary));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto token = m_lexer.next_token();
|
||||||
|
|
||||||
|
unary_op_expr_node unary;
|
||||||
|
unary.lhs = parse_expr_unary();
|
||||||
|
unary.type = it->second;
|
||||||
|
return m_arena->allocate<unary_op_expr_node>(std::move(unary));
|
||||||
|
}
|
||||||
|
|
||||||
|
expr_node* parser::parse_expr_right(expr_node* lhs, std::uint32_t precedence) {
|
||||||
|
struct op_info {
|
||||||
|
enum type_e {
|
||||||
|
Binary = 0,
|
||||||
|
FunctionCall,
|
||||||
|
} type;
|
||||||
|
std::uint32_t precedence;
|
||||||
|
union {
|
||||||
|
binary_op_expr_node::binary_op_type binary;
|
||||||
|
};
|
||||||
|
bool right = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::unordered_map<token_t, op_info> s_ops = {
|
||||||
|
{ token::Plus, { op_info::Binary, 4, binary_op_expr_node::Add } },
|
||||||
|
{ token::Minus, { op_info::Binary, 4, binary_op_expr_node::Sub } },
|
||||||
|
{ token::Star, { op_info::Binary, 3, binary_op_expr_node::Mul } },
|
||||||
|
{ token::Slash, { op_info::Binary, 3, binary_op_expr_node::Div } },
|
||||||
|
{ token::Percent, { op_info::Binary, 3, binary_op_expr_node::Mod } },
|
||||||
|
{ token::DblLT, { op_info::Binary, 5, binary_op_expr_node::Shl } },
|
||||||
|
{ token::DblGT, { op_info::Binary, 5, binary_op_expr_node::Shr } },
|
||||||
|
{ token::Ampersand, { op_info::Binary, 8, binary_op_expr_node::BinAnd } },
|
||||||
|
{ token::Pipe, { op_info::Binary, 10, binary_op_expr_node::BinOr } },
|
||||||
|
{ token::Hat, { op_info::Binary, 9, binary_op_expr_node::BinXor } },
|
||||||
|
{ token::DblAmpersand, { op_info::Binary, 11, binary_op_expr_node::And } },
|
||||||
|
{ token::DblPipe, { op_info::Binary, 12, binary_op_expr_node::Or } },
|
||||||
|
{ token::DblEquals, { op_info::Binary, 7, binary_op_expr_node::Equals } },
|
||||||
|
{ token::ExEquals, { op_info::Binary, 7, binary_op_expr_node::NotEquals } },
|
||||||
|
{ token::LessThan, { op_info::Binary, 6, binary_op_expr_node::LessThan } },
|
||||||
|
{ token::LessEquals, { op_info::Binary, 6, binary_op_expr_node::LessEquals } },
|
||||||
|
{ token::GreaterThan, { op_info::Binary, 6, binary_op_expr_node::GreaterThan } },
|
||||||
|
{ token::GreaterEquals, { op_info::Binary, 6, binary_op_expr_node::GreaterEquals } },
|
||||||
|
{ token::LParen, { op_info::FunctionCall, 1 } },
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
auto it = s_ops.find(m_lexer.peek_token().type);
|
||||||
|
if (it == s_ops.end() || it->second.precedence >= precedence) return lhs;
|
||||||
|
auto op = it->second;
|
||||||
|
m_lexer.next_token();
|
||||||
|
|
||||||
|
if (op.type == op_info::FunctionCall) {
|
||||||
|
func_call_expr_node funcCall;
|
||||||
|
funcCall.lhs = lhs;
|
||||||
|
|
||||||
|
if (m_lexer.peek_token().type != token::RParen) {
|
||||||
|
do {
|
||||||
|
funcCall.args.push_back(parse_expr());
|
||||||
|
} while (eat_token(token::Comma, token::RParen).type == token::Comma);
|
||||||
|
} else {
|
||||||
|
m_lexer.next_token();
|
||||||
|
}
|
||||||
|
|
||||||
|
lhs = m_arena->allocate<func_call_expr_node>(std::move(funcCall));
|
||||||
|
} else {
|
||||||
|
binary_op_expr_node binary;
|
||||||
|
binary.lhs = lhs;
|
||||||
|
binary.rhs = parse_expr_unary();
|
||||||
|
binary.type = op.binary;
|
||||||
|
|
||||||
|
auto nextIt = s_ops.find(m_lexer.peek_token().type);
|
||||||
|
if (nextIt != s_ops.end()) {
|
||||||
|
binary.rhs = parse_expr_right(binary.rhs, op.precedence + (op.right ? 1 : 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
lhs = m_arena->allocate<binary_op_expr_node>(std::move(binary));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace furc
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#ifndef LIBFURC
|
||||||
|
|
||||||
|
#include "furc/front/lexer.hpp"
|
||||||
|
#include "furc/front/parser.hpp"
|
||||||
|
#include "furc/middle/ir.hpp"
|
||||||
|
#include "furc/middle/reg_gen.hpp"
|
||||||
|
#include "furc/middle/ssa.hpp"
|
||||||
|
#include "furlang/arena.hpp"
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
furlang::arena arena;
|
||||||
|
|
||||||
|
std::string_view content = R"(
|
||||||
|
func main(argc: u64) -> s32 {
|
||||||
|
x: s32 = 1 + 2 * 3;
|
||||||
|
return if (x == 9) 1 else 0;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
furc::lexer lexer = { "<AK>", content };
|
||||||
|
furc::parser parser = { std::move(lexer), arena };
|
||||||
|
furc::ir_module irModule = furc::ir_generator::generate(parser.parse());
|
||||||
|
for (auto& func : irModule.functions) {
|
||||||
|
furc::ssa ssa(*func);
|
||||||
|
furc::reg_gen gen(*func, ssa);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LIBFURC
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user