Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
549af109b9
|
|||
|
6dce7016ce
|
|||
|
a1e43576cc
|
|||
|
9f950e45e7
|
|||
|
dfaa3081cb
|
|||
|
46e92deed5
|
|||
|
42e71080a2
|
|||
|
03448d865e
|
|||
|
cb845ca625
|
|||
|
466d7f003b
|
+2
-1
@@ -14,6 +14,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
add_subdirectory(furlang)
|
||||
add_subdirectory(furvm)
|
||||
add_subdirectory(furc)
|
||||
add_subdirectory(furas)
|
||||
|
||||
if(DOXYGEN_FOUND)
|
||||
set(DOXYGEN_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/docs)
|
||||
@@ -24,4 +25,4 @@ if(DOXYGEN_FOUND)
|
||||
doxygen_add_docs(doxygen_doc ${CMAKE_SOURCE_DIR}/furlang/ ${CMAKE_SOURCE_DIR}/furvm/ ${CMAKE_SOURCE_DIR}/furc/
|
||||
ALL WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMENT "Generating API documentation with Doxygen")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
file(GLOB_RECURSE FURAS_SRCS "src/**.cpp")
|
||||
file(GLOB_RECURSE FURAS_HDRS "include/**.hpp")
|
||||
add_executable(furas ${FURAS_SRCS} ${FURAS_HDRS})
|
||||
target_include_directories(furas PUBLIC include/)
|
||||
target_link_libraries(furas PRIVATE furlang libfurvm)
|
||||
@@ -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,34 @@
|
||||
type arr = array $s8 10
|
||||
|
||||
func println $arr = 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
|
||||
call $arr println
|
||||
ret
|
||||
@@ -0,0 +1,23 @@
|
||||
func println $u32 = native println
|
||||
|
||||
public func main = #main
|
||||
main:
|
||||
push $u32 0
|
||||
store %0
|
||||
loop_header:
|
||||
load %0
|
||||
push $u32 10
|
||||
ge
|
||||
jnz #loop_end
|
||||
loop_body:
|
||||
load %0
|
||||
call $u32 println
|
||||
|
||||
load %0
|
||||
push $u32 1
|
||||
add
|
||||
store %0
|
||||
|
||||
jmp #loop_header
|
||||
loop_end:
|
||||
ret
|
||||
@@ -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<token, lexer_error>;
|
||||
|
||||
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,94 @@
|
||||
#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. */
|
||||
|
||||
// Instructions
|
||||
Push,
|
||||
Array,
|
||||
Get,
|
||||
Set,
|
||||
Drop,
|
||||
Dup,
|
||||
Swap,
|
||||
Clone,
|
||||
Ref,
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
Mod,
|
||||
Eq,
|
||||
Neq,
|
||||
Lt,
|
||||
Gt,
|
||||
Le,
|
||||
Ge,
|
||||
Ptrof,
|
||||
Sizeof,
|
||||
Lenof,
|
||||
Load,
|
||||
Store,
|
||||
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,558 @@
|
||||
#include "furas/gen.hpp"
|
||||
|
||||
#include "furas/token.hpp"
|
||||
#include "furvm/function.hpp"
|
||||
#include "furvm/fwd.hpp"
|
||||
#include "furvm/instruction.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 {
|
||||
|
||||
struct instruction {
|
||||
furvm::instruction_t fur{};
|
||||
enum arg_type {
|
||||
None = 0,
|
||||
Type,
|
||||
Constant,
|
||||
Variable,
|
||||
Function,
|
||||
Label,
|
||||
} arg = None;
|
||||
};
|
||||
|
||||
// NOLINTBEGIN
|
||||
std::unordered_map<enum token::type, instruction> instructions = {
|
||||
{ token::Array, { furvm::instruction_t::Array, instruction::Type } },
|
||||
{ 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, instruction::Variable } },
|
||||
{ token::Store, { furvm::instruction_t::Store, instruction::Variable } },
|
||||
{ token::Call, { furvm::instruction_t::Call, instruction::Function } },
|
||||
{ token::Jmp, { furvm::instruction_t::Jump, instruction::Label } },
|
||||
{ token::Jnz, { furvm::instruction_t::JumpNotZero, instruction::Label } },
|
||||
{ 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::Push: return "push";
|
||||
case token::Array: return "array";
|
||||
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::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::pair<std::string, furvm::function_sig>,
|
||||
furvm::function_h,
|
||||
furlang::utility::
|
||||
pair_hash<std::string, furvm::function_sig, std::hash<std::string>, furvm::detail::function_sig_hash>>
|
||||
functions;
|
||||
std::unordered_map<std::string, furvm::mod_type_h> types;
|
||||
|
||||
std::unordered_map<std::string, label_context> labels;
|
||||
|
||||
struct token_result {
|
||||
generator_error error;
|
||||
token token;
|
||||
|
||||
struct token* operator->() { return &token; }
|
||||
struct token& operator*() { return token; }
|
||||
|
||||
bool operator!() const { return error.type != generator_error::Success; }
|
||||
};
|
||||
|
||||
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.token.type != type) {
|
||||
return { { generator_error::UnexpectedToken,
|
||||
"Expected "s + token_type(type) + ", but got " + token_type(token->type) },
|
||||
{ token::Count } };
|
||||
}
|
||||
return { { generator_error::Success }, token.token };
|
||||
}
|
||||
|
||||
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.emplace(std::make_pair(std::move(func.name), std::move(func.signature)), handle);
|
||||
|
||||
handle.dispatch();
|
||||
}
|
||||
for (auto unknown : label.unknowns) {
|
||||
std::ptrdiff_t jmpOff = static_cast<std::ptrdiff_t>(label.offset - 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] = jmpOff;
|
||||
}
|
||||
label.functions = {};
|
||||
label.unknowns = {};
|
||||
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::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.params.push_back(it->second);
|
||||
} else {
|
||||
return { generator_error::UnknownType, "Unknown type "s + std::string(result->value.string) };
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
switch (result->type) {
|
||||
case token::Sha256: {
|
||||
result = eat_token(lexer, token::Identifier);
|
||||
if (!result) return result.error;
|
||||
|
||||
std::string name = std::string(nameRes->value.string);
|
||||
|
||||
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.emplace(std::make_pair(name, std::move(signature)), 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.emplace(std::make_pair(name, std::move(signature)), 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;
|
||||
switch (result->type) {
|
||||
case token::Import: {
|
||||
auto typeNameRes = eat_token(lexer, token::Identifier);
|
||||
if (!typeNameRes) return typeNameRes.error;
|
||||
return { generator_error::Success };
|
||||
}
|
||||
case token::Array: {
|
||||
result = eat_token(lexer, token::Dolar);
|
||||
if (!result) return result.error;
|
||||
|
||||
auto typeNameRes = eat_token(lexer, token::Identifier);
|
||||
if (!typeNameRes) return typeNameRes.error;
|
||||
|
||||
auto size = eat_token(lexer, token::Unsigned);
|
||||
if (!size) return size.error;
|
||||
|
||||
furvm::mod_type_id innerId = 0;
|
||||
if (auto it = types.find(std::string(typeNameRes->value.string)); it != types.end()) {
|
||||
innerId = it->second.id();
|
||||
} else {
|
||||
return { generator_error::UnknownType,
|
||||
"Unknown type "s + std::string(typeNameRes->value.string) };
|
||||
}
|
||||
|
||||
auto type = mod.emplace_type(innerId, size->value.uint);
|
||||
types.emplace(std::string(nameRes->value.string), type);
|
||||
type.dispatch();
|
||||
|
||||
return { generator_error::Success };
|
||||
}
|
||||
default:
|
||||
return { generator_error::UnexpectedToken,
|
||||
"Unexpected token "s + token_type(result->type) +
|
||||
", expected either type, `import`, or `array`" };
|
||||
}
|
||||
}
|
||||
|
||||
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::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::Call:
|
||||
case token::Jmp:
|
||||
case token::Jnz:
|
||||
case token::Ret: {
|
||||
auto it = instructions.find(result->type);
|
||||
assert(it != instructions.end());
|
||||
mod.bytecode().push_back(static_cast<furvm::byte>(it->second.fur));
|
||||
switch (it->second.arg) {
|
||||
case instruction::None: break;
|
||||
case instruction::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) };
|
||||
auto id = type->second.id();
|
||||
mod.bytecode().push_back((id >> 0) & 0xFF);
|
||||
mod.bytecode().push_back((id >> 8) & 0xFF);
|
||||
mod.bytecode().push_back((id >> 16) & 0xFF);
|
||||
mod.bytecode().push_back((id >> 24) & 0xFF);
|
||||
} break;
|
||||
case instruction::Constant: {
|
||||
assert(false); // TODO: Unimplemented
|
||||
} break;
|
||||
case instruction::Variable: {
|
||||
result = eat_token(lexer, token::Percent);
|
||||
if (!result) return result.error;
|
||||
result = eat_token(lexer, token::Unsigned);
|
||||
if (!result) return result.error;
|
||||
std::uint16_t var = result->value.uint;
|
||||
mod.bytecode().push_back((var >> 0) & 0xFF);
|
||||
mod.bytecode().push_back((var >> 8) & 0xFF);
|
||||
} break;
|
||||
case instruction::Function: {
|
||||
furvm::function_sig signature;
|
||||
while ((result = next_token(lexer)).error.type == generator_error::Success &&
|
||||
result->type == token::Dolar) {
|
||||
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) };
|
||||
signature.params.push_back(type->second);
|
||||
}
|
||||
if (!result || result->type != token::Identifier) return result.error;
|
||||
std::string name(result->value.string);
|
||||
|
||||
auto func = functions.find(std::make_pair(name, signature));
|
||||
if (func == functions.end())
|
||||
return { generator_error::UnknownType, "Unknown type "s + std::string(result->value.string) };
|
||||
auto id = func->second.id();
|
||||
mod.bytecode().push_back((id >> 0) & 0xFF);
|
||||
mod.bytecode().push_back((id >> 8) & 0xFF);
|
||||
} break;
|
||||
case instruction::Label: {
|
||||
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() + 1);
|
||||
mod.bytecode().push_back(0);
|
||||
return { generator_error::Success };
|
||||
}
|
||||
std::ptrdiff_t jmpOff = static_cast<std::ptrdiff_t>(offset - mod.bytecode().size() - 1);
|
||||
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().push_back(jmpOff);
|
||||
} break;
|
||||
}
|
||||
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,98 @@
|
||||
#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{ 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 },
|
||||
|
||||
{ "push", token::Push },
|
||||
{ "array", token::Array },
|
||||
{ "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 },
|
||||
{ "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{
|
||||
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;
|
||||
}
|
||||
@@ -170,7 +170,7 @@ void furvm_generator::generate_operand(furvm::mod& mod, function_context& ctx, c
|
||||
static_assert(sizeof(var) == 2, "sizeof(furvm::variable_t) has changed");
|
||||
} break;
|
||||
case furlang::ir::operand_t::Integer: {
|
||||
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::PushB2I));
|
||||
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::PushS32));
|
||||
mod.bytecode().push_back(operand.integer());
|
||||
} break;
|
||||
case furlang::ir::operand_t::Variable:
|
||||
|
||||
@@ -12,11 +12,34 @@ enum class instruction_t : byte {
|
||||
NoOperation = 0,
|
||||
|
||||
/**
|
||||
* @brief Pushes an integer from a byte onto the stack.
|
||||
*
|
||||
* Pushes an integer constructed from a next byte onto the stack.
|
||||
* @brief Pushes an s8 integer from a byte onto the stack.
|
||||
*/
|
||||
PushB2I,
|
||||
PushS8,
|
||||
|
||||
/**
|
||||
* @brief Pushes an u8 integer from a byte onto the stack.
|
||||
*/
|
||||
PushU8,
|
||||
|
||||
/**
|
||||
* @brief Pushes an s16 integer from two byte onto the stack.
|
||||
*/
|
||||
PushS16,
|
||||
|
||||
/**
|
||||
* @brief Pushes an u16 integer from two byte onto the stack.
|
||||
*/
|
||||
PushU16,
|
||||
|
||||
/**
|
||||
* @brief Pushes an s32 integer from a byte onto the stack.
|
||||
*/
|
||||
PushS32,
|
||||
|
||||
/**
|
||||
* @brief Pushes an u32 integer from a byte onto the stack.
|
||||
*/
|
||||
PushU32,
|
||||
|
||||
/**
|
||||
* @brief Pushes a constant onto the stack.
|
||||
@@ -38,6 +61,11 @@ enum class instruction_t : byte {
|
||||
*/
|
||||
Get,
|
||||
|
||||
/**
|
||||
* @brief Sets an array element.
|
||||
*/
|
||||
Set,
|
||||
|
||||
/**
|
||||
* @brief Pops top element from the stack.
|
||||
*/
|
||||
@@ -48,6 +76,11 @@ enum class instruction_t : byte {
|
||||
*/
|
||||
Duplicate,
|
||||
|
||||
/**
|
||||
* @brief Swaps two top elements of the stack.
|
||||
*/
|
||||
Swap,
|
||||
|
||||
/**
|
||||
* @brief Clones top element on the stack.
|
||||
*/
|
||||
|
||||
@@ -433,7 +433,7 @@ public:
|
||||
* @return The integer value.
|
||||
*/
|
||||
thing_type::s64 integer() const {
|
||||
switch (m_type.type) {
|
||||
switch (true_type().type) {
|
||||
case thing_type::S8: return get<thing_type::s8>();
|
||||
case thing_type::S16: return get<thing_type::s16>();
|
||||
case thing_type::S32: return get<thing_type::s32>();
|
||||
@@ -448,11 +448,11 @@ public:
|
||||
|
||||
void resize(thing_type::u64 newSize) {
|
||||
if (!is(thing_type::Array)) throw bad_thing_access();
|
||||
if (m_type.value.array.size > 0) throw std::runtime_error("cannot resize a static array");
|
||||
if (true_type().value.array.size > 0) throw std::runtime_error("cannot resize a static array");
|
||||
|
||||
auto& array = get<union array>();
|
||||
if (newSize < 0 || newSize == array.dynamic.size) return;
|
||||
std::size_t innerSize = compute_size_na(*m_type.value.array.type);
|
||||
std::size_t innerSize = compute_size_na(*true_type().value.array.type);
|
||||
std::byte* newData = new std::byte[innerSize * newSize];
|
||||
std::memcpy(newData,
|
||||
array.dynamic.data,
|
||||
@@ -502,6 +502,32 @@ public:
|
||||
if (m_type.type != thing_type::Ref || *m_type.value.typeRef != thing.type()) throw bad_thing_access();
|
||||
m_data = thing.m_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Self-explainatory.
|
||||
*
|
||||
* TODO: Document
|
||||
*/
|
||||
void assign(thing&& thing) {
|
||||
class thing rhs = std::move(thing);
|
||||
if (true_type() != rhs.true_type()) throw std::runtime_error("thing type mismatch");
|
||||
// TODO: Move this to another function
|
||||
switch (true_type().type) {
|
||||
case thing_type::S8:
|
||||
case thing_type::S16:
|
||||
case thing_type::S32:
|
||||
case thing_type::S64:
|
||||
case thing_type::U8:
|
||||
case thing_type::U16:
|
||||
case thing_type::U32:
|
||||
case thing_type::U64: std::memcpy(m_data, rhs.m_data, m_size); return;
|
||||
case thing_type::Ptr:
|
||||
case thing_type::Ref:
|
||||
case thing_type::Array: throw std::runtime_error("unimplemented");
|
||||
case thing_type::Count: break;
|
||||
}
|
||||
throw std::runtime_error("unreachable");
|
||||
}
|
||||
private:
|
||||
static void copy_list(const thing_type& arrayType, array& dst, const array& src) {
|
||||
if (arrayType.type != thing_type::Array || arrayType.value.array.type == nullptr)
|
||||
@@ -577,7 +603,7 @@ private:
|
||||
private:
|
||||
template <typename Func>
|
||||
decltype(auto) visit_primitive(Func&& func) const {
|
||||
switch (m_type.type) {
|
||||
switch (true_type().type) {
|
||||
case thing_type::S8: return std::forward<Func>(func)(get<thing_type::s8>());
|
||||
case thing_type::S16: return std::forward<Func>(func)(get<thing_type::s16>());
|
||||
case thing_type::S32: return std::forward<Func>(func)(get<thing_type::s32>());
|
||||
@@ -592,7 +618,7 @@ private:
|
||||
|
||||
template <typename Op>
|
||||
thing binary_op(const thing& rhs, const Op& op) const {
|
||||
if (thing_type::is_primitive(m_type.type) && thing_type::is_primitive(rhs.m_type.type)) {
|
||||
if (thing_type::is_primitive(true_type().type) && thing_type::is_primitive(true_type().type)) {
|
||||
static constexpr enum thing_type::type promotions[8 * 8] = {
|
||||
// S8
|
||||
thing_type::S8,
|
||||
|
||||
@@ -69,8 +69,9 @@ public:
|
||||
[[nodiscard]] T* allocate(std::size_t count = 1) {
|
||||
for (auto it = m_deadThings->begin(); it != m_deadThings->end(); ++it) {
|
||||
if (it->second != count) continue;
|
||||
T* data = it->first;
|
||||
m_deadThings->erase(it);
|
||||
return it->first;
|
||||
return data;
|
||||
}
|
||||
return m_arena->allocate<T>(count);
|
||||
}
|
||||
|
||||
+44
-6
@@ -10,6 +10,7 @@
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace furvm {
|
||||
@@ -100,7 +101,7 @@ thing_h executor::pop_thing() {
|
||||
if (m_frames.top().stackBase >= m_stack.size()) throw stack_underflow();
|
||||
thing_h top = std::move(m_stack.top());
|
||||
m_stack.pop();
|
||||
return top;
|
||||
return std::move(top);
|
||||
}
|
||||
|
||||
thing_h executor::thing() const {
|
||||
@@ -122,7 +123,7 @@ void executor::store_thing(variable_t variable, thing_h&& thing) {
|
||||
|
||||
thing_h executor::load_thing(variable_t variable) const {
|
||||
const auto& frame = m_frames.top();
|
||||
return frame.variables[variable];
|
||||
return { frame.variables[variable] };
|
||||
}
|
||||
|
||||
void executor::step() {
|
||||
@@ -133,9 +134,33 @@ void executor::step() {
|
||||
instruction_t instr = static_cast<instruction_t>((*frame.mod).byte(frame.position++));
|
||||
switch (instr) {
|
||||
case instruction_t::NoOperation: break;
|
||||
case instruction_t::PushB2I: {
|
||||
push_thing({ (struct thing_type){ thing_type::S32 }, m_context->thing_alloc() })->get<int>() =
|
||||
frame.mod->byte(frame.position++);
|
||||
case instruction_t::PushS8: {
|
||||
push_thing({ (struct thing_type){ thing_type::S8 }, m_context->thing_alloc() })->get<thing_type::s8>() =
|
||||
static_cast<thing_type::s8>(frame.mod->byte(frame.position++));
|
||||
} break;
|
||||
case instruction_t::PushU8: {
|
||||
push_thing({ (struct thing_type){ thing_type::U8 }, m_context->thing_alloc() })->get<thing_type::u8>() =
|
||||
static_cast<thing_type::u8>(frame.mod->byte(frame.position++));
|
||||
} break;
|
||||
case instruction_t::PushS16: {
|
||||
thing_type::u16 value = frame.mod->byte(frame.position++);
|
||||
value |= static_cast<thing_type::u16>(frame.mod->byte(frame.position++) << 8);
|
||||
push_thing({ (struct thing_type){ thing_type::S16 }, m_context->thing_alloc() })->get<thing_type::s16>() =
|
||||
static_cast<thing_type::s16>(value);
|
||||
} break;
|
||||
case instruction_t::PushU16: {
|
||||
thing_type::u16 value = frame.mod->byte(frame.position++);
|
||||
value |= static_cast<thing_type::u16>(frame.mod->byte(frame.position++) << 8);
|
||||
push_thing({ (struct thing_type){ thing_type::U16 }, m_context->thing_alloc() })->get<thing_type::u16>() =
|
||||
value;
|
||||
} break;
|
||||
case instruction_t::PushS32: {
|
||||
push_thing({ (struct thing_type){ thing_type::S32 }, m_context->thing_alloc() })->get<thing_type::s32>() =
|
||||
static_cast<thing_type::s32>(frame.mod->byte(frame.position++));
|
||||
} break;
|
||||
case instruction_t::PushU32: {
|
||||
push_thing({ (struct thing_type){ thing_type::U32 }, m_context->thing_alloc() })->get<thing_type::u32>() =
|
||||
static_cast<thing_type::u32>(frame.mod->byte(frame.position++));
|
||||
} break;
|
||||
case instruction_t::Array: {
|
||||
mod_type_id typeId = static_cast<mod_type_id>(frame.mod->byte(frame.position)) |
|
||||
@@ -161,12 +186,24 @@ void executor::step() {
|
||||
auto array = pop_thing();
|
||||
push_thing(array->at(index->integer()));
|
||||
} break;
|
||||
case instruction_t::Set: {
|
||||
auto element = pop_thing();
|
||||
auto index = pop_thing();
|
||||
auto array = pop_thing();
|
||||
array->at(index->integer()).assign(std::move(element->clone()));
|
||||
} break;
|
||||
case instruction_t::Drop: {
|
||||
pop_thing();
|
||||
} break;
|
||||
case instruction_t::Duplicate: {
|
||||
push_thing(thing());
|
||||
} break;
|
||||
case instruction_t::Swap: {
|
||||
auto thing1 = pop_thing();
|
||||
auto thing2 = pop_thing();
|
||||
push_thing(std::move(thing1));
|
||||
push_thing(std::move(thing2));
|
||||
} break;
|
||||
case instruction_t::Clone: {
|
||||
push_thing(std::move(thing()->clone()));
|
||||
} break;
|
||||
@@ -286,7 +323,8 @@ void executor::step() {
|
||||
push_frame(frame.mod, *frame.mod->function_at(funcId));
|
||||
} break;
|
||||
case instruction_t::Jump: {
|
||||
frame.position += ((std::int8_t)frame.mod->byte(frame.position)) + 1;
|
||||
std::int8_t offset = static_cast<std::int8_t>(frame.mod->byte(frame.position++));
|
||||
frame.position += offset;
|
||||
} break;
|
||||
case instruction_t::JumpNotZero: {
|
||||
byte offset = frame.mod->byte(frame.position++);
|
||||
|
||||
+8
-9
@@ -13,7 +13,7 @@
|
||||
static void print_thing(const furvm::thing<furvm::thing_allocator>& thing) {
|
||||
using namespace furvm;
|
||||
|
||||
switch (thing.type().type) {
|
||||
switch (thing.true_type().type) {
|
||||
case thing_type::S8: std::cout << thing.cast_to<thing_type::s16>(); break;
|
||||
case thing_type::S16: std::cout << thing.get<thing_type::s16>(); break;
|
||||
case thing_type::S32: std::cout << thing.get<thing_type::s32>(); break;
|
||||
@@ -22,7 +22,7 @@ static void print_thing(const furvm::thing<furvm::thing_allocator>& thing) {
|
||||
case thing_type::U16: std::cout << thing.get<thing_type::u16>(); break;
|
||||
case thing_type::U32: std::cout << thing.get<thing_type::u32>(); break;
|
||||
case thing_type::U64: std::cout << thing.get<thing_type::u64>(); break;
|
||||
case furvm::thing_type::Array:
|
||||
case thing_type::Array:
|
||||
std::cout << "{ ";
|
||||
for (thing_type::u64 i = 0; i < thing.length(); ++i) {
|
||||
if (i > 0) std::cout << ", ";
|
||||
@@ -37,20 +37,20 @@ static void print_thing(const furvm::thing<furvm::thing_allocator>& thing) {
|
||||
int main(int argc, char** argv) {
|
||||
auto context = std::make_shared<furvm::context>();
|
||||
|
||||
#if 0 // NOLINT
|
||||
#if 1 // NOLINT
|
||||
if (argc != 2) {
|
||||
std::cerr << "Usage: " << argv[0] << " <mod.fmod>\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
std::ifstream file(argv[1]);
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "Failed to open " << argv[1] << '\n';
|
||||
return 1;
|
||||
}
|
||||
|
||||
furvm::mod_h mod = context->emplace("main", std::move(furvm::mod::load(file)));
|
||||
furvm::mod_h mod = context->emplace("main", std::move(furvm::mod::load(file)));
|
||||
auto mainFunc = mod->function_at("main", furvm::function_sig{});
|
||||
#else
|
||||
static const furvm::byte s_bytecode[] = {
|
||||
static_cast<furvm::byte>(furvm::instruction_t::Array),
|
||||
@@ -74,11 +74,10 @@ int main(int argc, char** argv) {
|
||||
auto mainFunc = mod->emplace_function("main", furvm::function_sig{}, 0);
|
||||
mod->emplace_function(furvm::function_sig{ { u64Type }, u64Type }, "print").dispatch();
|
||||
#endif
|
||||
mod->set_native_function("print", [](furvm::executor& executor) {
|
||||
auto arg = std::move(*executor.load_thing(0));
|
||||
print_thing(arg);
|
||||
mod->set_native_function("println", [](furvm::executor& executor) {
|
||||
auto arg = executor.load_thing(0);
|
||||
print_thing(*arg);
|
||||
std::cout << '\n';
|
||||
executor.push_thing(std::move(arg));
|
||||
});
|
||||
|
||||
furvm::executor_h executor = context->emplace_executor(context);
|
||||
|
||||
+10
-6
@@ -25,6 +25,7 @@ std::ostream& mod::serialize(std::ostream& os) const {
|
||||
}
|
||||
|
||||
auto type = *m_types.at(id);
|
||||
detail::serialize(os, static_cast<std::uint8_t>(type.type));
|
||||
switch (type.type) {
|
||||
case mod_type::S8:
|
||||
case mod_type::S16:
|
||||
@@ -67,7 +68,7 @@ std::ostream& mod::serialize(std::ostream& os) const {
|
||||
}
|
||||
|
||||
// Function signature
|
||||
detail::serialize(os, func->signature().params.size());
|
||||
detail::serialize(os, static_cast<std::uint32_t>(func->signature().params.size()));
|
||||
for (std::uint32_t i = 0; i < func->signature().params.size(); ++i)
|
||||
detail::serialize(os, func->signature().params[i].id());
|
||||
|
||||
@@ -116,7 +117,10 @@ mod mod::load(std::istream& is) {
|
||||
case mod_type::U8:
|
||||
case mod_type::U16:
|
||||
case mod_type::U32:
|
||||
case mod_type::U64: break;
|
||||
case mod_type::U64: {
|
||||
mod_type theType = { (enum mod_type::type)type };
|
||||
mod.emplace_type(id, theType).dispatch();
|
||||
} break;
|
||||
case mod_type::Ptr: {
|
||||
mod_type_id typeId = 0;
|
||||
detail::load(is, typeId);
|
||||
@@ -165,17 +169,17 @@ mod mod::load(std::istream& is) {
|
||||
decltype(std::declval<function>().position()) offset = 0;
|
||||
detail::load(is, offset);
|
||||
if (name.empty())
|
||||
mod.emplace_function(std::move(name), id, std::move(signature), offset).dispatch();
|
||||
else
|
||||
mod.emplace_function(id, std::move(signature), offset).dispatch();
|
||||
else
|
||||
mod.emplace_function(std::move(name), id, std::move(signature), offset).dispatch();
|
||||
} break;
|
||||
case function_t::Native: {
|
||||
std::string native;
|
||||
detail::load(is, native);
|
||||
if (name.empty())
|
||||
mod.emplace_function(std::move(name), id, std::move(signature), std::move(native)).dispatch();
|
||||
else
|
||||
mod.emplace_function(id, std::move(signature), std::move(native)).dispatch();
|
||||
else
|
||||
mod.emplace_function(std::move(name), id, std::move(signature), std::move(native)).dispatch();
|
||||
} break;
|
||||
case function_t::Import: {
|
||||
std::string modName;
|
||||
|
||||
+26
-1
@@ -1,3 +1,28 @@
|
||||
#include "furlang/arena.hpp"
|
||||
#include "furvm/furvm.hpp"
|
||||
#include "furvm/thing.hpp"
|
||||
#include "furvm/thing_allocator.hpp"
|
||||
|
||||
#include "gtest/gtest.h" // IWYU pragma: keep
|
||||
|
||||
namespace {}
|
||||
namespace {
|
||||
|
||||
// TODO: Basic program tests (e.g. for loops)
|
||||
|
||||
TEST(Things, Ops) {
|
||||
furlang::arena arena;
|
||||
furvm::thing_allocator<std::byte> alloc{ arena };
|
||||
|
||||
furvm::thing lhs{ furvm::thing_type{ furvm::thing_type::U32 }, alloc };
|
||||
lhs.get<furvm::thing_type::u32>() = 6;
|
||||
furvm::thing rhs{ furvm::thing_type{ furvm::thing_type::U32 }, alloc };
|
||||
rhs.get<furvm::thing_type::u32>() = 7;
|
||||
|
||||
auto res = lhs.add(rhs);
|
||||
ASSERT_EQ(res.type().type, furvm::thing_type::U32);
|
||||
EXPECT_EQ(lhs.get<furvm::thing_type::u32>(), 6);
|
||||
EXPECT_EQ(rhs.get<furvm::thing_type::u32>(), 7);
|
||||
EXPECT_EQ(res.get<furvm::thing_type::u32>(), 6 + 7);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Reference in New Issue
Block a user