Initial commit: wip spec, ast definition, parser & interpreter
This commit is contained in:
commit
9d7f81d7fc
43 changed files with 2577 additions and 0 deletions
9
CMakeLists.txt
Normal file
9
CMakeLists.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
cmake_minimum_required(VERSION 3.30)
|
||||||
|
project(pslang CXX)
|
||||||
|
|
||||||
|
add_subdirectory(libs/type)
|
||||||
|
add_subdirectory(libs/ast)
|
||||||
|
add_subdirectory(libs/parser)
|
||||||
|
add_subdirectory(libs/interpreter)
|
||||||
|
|
||||||
|
add_subdirectory(apps/interpreter)
|
||||||
6
apps/interpreter/CMakeLists.txt
Normal file
6
apps/interpreter/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
file(GLOB_RECURSE PSLI_HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp")
|
||||||
|
file(GLOB_RECURSE PSLI_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp")
|
||||||
|
|
||||||
|
add_executable(psli ${PSLI_HEADERS} ${PSLI_SOURCES})
|
||||||
|
target_include_directories(psli PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||||
|
target_link_libraries(psli PUBLIC pslang-interpreter)
|
||||||
45
apps/interpreter/source/main.cpp
Normal file
45
apps/interpreter/source/main.cpp
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
#include <pslang/parser/parser.hpp>
|
||||||
|
#include <pslang/interpreter/interpreter.hpp>
|
||||||
|
#include <pslang/ast/statement.hpp>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
int main(int argc, char ** argv)
|
||||||
|
{
|
||||||
|
if (argc == 1)
|
||||||
|
{
|
||||||
|
std::cout << "Usage: psli [ options ] <file1> [ <file2> ... ]\n";
|
||||||
|
std::cout << "Available options:\n";
|
||||||
|
std::cout << " -t, --trace Trace each line of execution\n";
|
||||||
|
std::cout << " -d, --dump Dump all variables after processing each file\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
using namespace pslang;
|
||||||
|
|
||||||
|
auto context = interpreter::empty_context();
|
||||||
|
|
||||||
|
bool dump = false;
|
||||||
|
|
||||||
|
for (int arg = 1; arg < argc; ++arg)
|
||||||
|
{
|
||||||
|
if (std::strcmp(argv[arg], "-d") == 0 || std::strcmp(argv[arg], "--dump") == 0)
|
||||||
|
{
|
||||||
|
dump = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::strcmp(argv[arg], "-t") == 0 || std::strcmp(argv[arg], "--trace") == 0)
|
||||||
|
{
|
||||||
|
context.trace = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ast = parser::parse(argv[arg]);
|
||||||
|
interpreter::execute(context, ast);
|
||||||
|
|
||||||
|
if (dump)
|
||||||
|
interpreter::dump(std::cout, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
84
examples/example.psl
Normal file
84
examples/example.psl
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
import math
|
||||||
|
import events
|
||||||
|
import components
|
||||||
|
import ecs
|
||||||
|
|
||||||
|
const x = 10s // deduced type i16
|
||||||
|
var y = 14u // deduced type u32
|
||||||
|
var z: f64 = 3.14l
|
||||||
|
|
||||||
|
func fma(x: f32, y: f32, z: f32) -> f32:
|
||||||
|
return x * y + z
|
||||||
|
|
||||||
|
struct vec2:
|
||||||
|
x: f32
|
||||||
|
y: f32
|
||||||
|
|
||||||
|
// pass by value
|
||||||
|
func length(v: vec2) -> f32:
|
||||||
|
return math.sqrt(v.x * v.x + v.y * v.y)
|
||||||
|
|
||||||
|
// return type deduced as u64
|
||||||
|
func merge(x: u32, y: u32):
|
||||||
|
return (x as u64) or ((y as u64) << 32)
|
||||||
|
|
||||||
|
var v = vec2(10, 20)
|
||||||
|
length(v)
|
||||||
|
|
||||||
|
// can be called using method syntax
|
||||||
|
v.length()
|
||||||
|
|
||||||
|
// function pointers
|
||||||
|
var my_func = fma // deduced type (f32, f32, f32) -> f32
|
||||||
|
|
||||||
|
// pass by reference/pointer with *
|
||||||
|
// TODO: const pointer?
|
||||||
|
func my_system(event: events.update, position: *components.position, velocity: *components.velocity):
|
||||||
|
position += event.dt * velocity
|
||||||
|
|
||||||
|
func attach(dispatcher: *ecs.dispatcher):
|
||||||
|
// TODO: how does it work? C++-style variadic templates? Oh no...
|
||||||
|
dispatcher.system(my_system)
|
||||||
|
|
||||||
|
// objects with methods
|
||||||
|
struct rectangle:
|
||||||
|
width: i32
|
||||||
|
height: i32
|
||||||
|
|
||||||
|
func extend(r: &rectangle, size: i32):
|
||||||
|
r.width += size
|
||||||
|
r.height += size
|
||||||
|
|
||||||
|
var r = rectangle(10, 12)
|
||||||
|
r.extend(5)
|
||||||
|
|
||||||
|
// named initializers
|
||||||
|
var r2 = rectangle(width = 20, height = 30)
|
||||||
|
|
||||||
|
// regular pointers
|
||||||
|
var ptr: *i32 = null
|
||||||
|
var x = 15
|
||||||
|
ptr = &x
|
||||||
|
|
||||||
|
// field/method access using pointers is the same as with values
|
||||||
|
var sptr: *rectangle = &r
|
||||||
|
r.width *= 2
|
||||||
|
|
||||||
|
// simple generics
|
||||||
|
struct array(T):
|
||||||
|
data: *T
|
||||||
|
size: u64
|
||||||
|
|
||||||
|
// TODO: constructors? destructors?
|
||||||
|
func new(self: array(T), size: u64):
|
||||||
|
return array(T)(data = mem.alloc(size * sizeof(T)), size = size)
|
||||||
|
|
||||||
|
// TODO: static arrays?
|
||||||
|
// TODO: move-only types? alloc returns smth like unique ptr?
|
||||||
|
|
||||||
|
struct kvpair(K, V):
|
||||||
|
key: K
|
||||||
|
value: V
|
||||||
|
|
||||||
|
struct arraymap(K, V):
|
||||||
|
values: array(kvpair(K, V))
|
||||||
3
examples/test.psl
Normal file
3
examples/test.psl
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
let n = 10 + (1u as i32)
|
||||||
|
let x = (n as f32) / 3.0
|
||||||
|
x = 15.2
|
||||||
6
libs/ast/CMakeLists.txt
Normal file
6
libs/ast/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
file(GLOB_RECURSE PSLANG_AST_HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp")
|
||||||
|
file(GLOB_RECURSE PSLANG_AST_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp")
|
||||||
|
|
||||||
|
add_library(pslang-ast STATIC ${PSLANG_AST_HEADERS} ${PSLANG_AST_SOURCES})
|
||||||
|
target_include_directories(pslang-ast PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||||
|
target_link_libraries(pslang-ast PUBLIC pslang-type)
|
||||||
15
libs/ast/include/pslang/ast/cast.hpp
Normal file
15
libs/ast/include/pslang/ast/cast.hpp
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <pslang/ast/expression_fwd.hpp>
|
||||||
|
#include <pslang/type/type.hpp>
|
||||||
|
|
||||||
|
namespace pslang::ast
|
||||||
|
{
|
||||||
|
|
||||||
|
struct cast_operation
|
||||||
|
{
|
||||||
|
expression_ptr expression;
|
||||||
|
type::type_ptr type;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
49
libs/ast/include/pslang/ast/control.hpp
Normal file
49
libs/ast/include/pslang/ast/control.hpp
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <pslang/ast/expression_fwd.hpp>
|
||||||
|
#include <pslang/ast/statement_fwd.hpp>
|
||||||
|
|
||||||
|
namespace pslang::ast
|
||||||
|
{
|
||||||
|
|
||||||
|
// N.B.: if_block, else_block, and else_if_block are temporary parsing elements
|
||||||
|
// and are not present in the final AST
|
||||||
|
|
||||||
|
struct if_block
|
||||||
|
{
|
||||||
|
expression_ptr condition;
|
||||||
|
statement_list_ptr statements;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct else_block
|
||||||
|
{
|
||||||
|
statement_list_ptr statements;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct else_if_block
|
||||||
|
{
|
||||||
|
expression_ptr condition;
|
||||||
|
statement_list_ptr statements;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Interpreted as a consecutive "if -> else if -> else if -> else" chain
|
||||||
|
// Empty condition means no condition (last "else" in chain)
|
||||||
|
// All blocks but the last must have a non-empty condition
|
||||||
|
struct if_chain
|
||||||
|
{
|
||||||
|
struct block
|
||||||
|
{
|
||||||
|
expression_ptr condition;
|
||||||
|
statement_list_ptr statements;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<block> blocks;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct while_block
|
||||||
|
{
|
||||||
|
expression_ptr condition;
|
||||||
|
statement_list_ptr statements;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
39
libs/ast/include/pslang/ast/expression.hpp
Normal file
39
libs/ast/include/pslang/ast/expression.hpp
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <pslang/ast/literal.hpp>
|
||||||
|
#include <pslang/ast/identifier.hpp>
|
||||||
|
#include <pslang/ast/operation.hpp>
|
||||||
|
#include <pslang/ast/cast.hpp>
|
||||||
|
#include <pslang/ast/expression_fwd.hpp>
|
||||||
|
|
||||||
|
namespace pslang::ast
|
||||||
|
{
|
||||||
|
|
||||||
|
struct unary_operation
|
||||||
|
{
|
||||||
|
unary_operation_type type;
|
||||||
|
expression_ptr arg1;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct binary_operation
|
||||||
|
{
|
||||||
|
binary_operation_type type;
|
||||||
|
expression_ptr arg1;
|
||||||
|
expression_ptr arg2;
|
||||||
|
};
|
||||||
|
|
||||||
|
using expression_impl = std::variant<
|
||||||
|
literal,
|
||||||
|
identifier,
|
||||||
|
unary_operation,
|
||||||
|
binary_operation,
|
||||||
|
cast_operation
|
||||||
|
>;
|
||||||
|
|
||||||
|
struct expression
|
||||||
|
: expression_impl
|
||||||
|
{
|
||||||
|
using expression_impl::expression_impl;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
12
libs/ast/include/pslang/ast/expression_fwd.hpp
Normal file
12
libs/ast/include/pslang/ast/expression_fwd.hpp
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace pslang::ast
|
||||||
|
{
|
||||||
|
|
||||||
|
struct expression;
|
||||||
|
|
||||||
|
using expression_ptr = std::unique_ptr<expression>;
|
||||||
|
|
||||||
|
}
|
||||||
13
libs/ast/include/pslang/ast/identifier.hpp
Normal file
13
libs/ast/include/pslang/ast/identifier.hpp
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace pslang::ast
|
||||||
|
{
|
||||||
|
|
||||||
|
struct identifier
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
43
libs/ast/include/pslang/ast/literal.hpp
Normal file
43
libs/ast/include/pslang/ast/literal.hpp
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <variant>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace pslang::ast
|
||||||
|
{
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct numeric_literal_base
|
||||||
|
{
|
||||||
|
T value;
|
||||||
|
};
|
||||||
|
|
||||||
|
using bool_literal = numeric_literal_base<bool>;
|
||||||
|
|
||||||
|
using i8_literal = numeric_literal_base<std::int8_t>;
|
||||||
|
using u8_literal = numeric_literal_base<std::uint8_t>;
|
||||||
|
using i16_literal = numeric_literal_base<std::int16_t>;
|
||||||
|
using u16_literal = numeric_literal_base<std::uint16_t>;
|
||||||
|
using i32_literal = numeric_literal_base<std::int32_t>;
|
||||||
|
using u32_literal = numeric_literal_base<std::uint32_t>;
|
||||||
|
using i64_literal = numeric_literal_base<std::int64_t>;
|
||||||
|
using u64_literal = numeric_literal_base<std::uint64_t>;
|
||||||
|
|
||||||
|
using f32_literal = numeric_literal_base<float>;
|
||||||
|
using f64_literal = numeric_literal_base<double>;
|
||||||
|
|
||||||
|
using literal = std::variant<
|
||||||
|
bool_literal,
|
||||||
|
i8_literal,
|
||||||
|
u8_literal,
|
||||||
|
i16_literal,
|
||||||
|
u16_literal,
|
||||||
|
i32_literal,
|
||||||
|
u32_literal,
|
||||||
|
i64_literal,
|
||||||
|
u64_literal,
|
||||||
|
f32_literal,
|
||||||
|
f64_literal
|
||||||
|
>;
|
||||||
|
|
||||||
|
}
|
||||||
96
libs/ast/include/pslang/ast/operation.hpp
Normal file
96
libs/ast/include/pslang/ast/operation.hpp
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace pslang::ast
|
||||||
|
{
|
||||||
|
|
||||||
|
enum class unary_operation_type
|
||||||
|
{
|
||||||
|
negation,
|
||||||
|
logical_not,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class binary_operation_type
|
||||||
|
{
|
||||||
|
addition,
|
||||||
|
subtraction,
|
||||||
|
multiplication,
|
||||||
|
division,
|
||||||
|
remainder,
|
||||||
|
logical_and,
|
||||||
|
logical_or,
|
||||||
|
logical_xor,
|
||||||
|
equals,
|
||||||
|
not_equals,
|
||||||
|
less,
|
||||||
|
greater,
|
||||||
|
less_equals,
|
||||||
|
greater_equals,
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Ostream>
|
||||||
|
Ostream & operator << (Ostream & out, unary_operation_type type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case unary_operation_type::negation:
|
||||||
|
out << "negation";
|
||||||
|
break;
|
||||||
|
case unary_operation_type::logical_not:
|
||||||
|
out << "not";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Ostream>
|
||||||
|
Ostream & operator << (Ostream & out, binary_operation_type type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case binary_operation_type::addition:
|
||||||
|
out << "addition";
|
||||||
|
break;
|
||||||
|
case binary_operation_type::subtraction:
|
||||||
|
out << "subtraction";
|
||||||
|
break;
|
||||||
|
case binary_operation_type::multiplication:
|
||||||
|
out << "multiplication";
|
||||||
|
break;
|
||||||
|
case binary_operation_type::division:
|
||||||
|
out << "division";
|
||||||
|
break;
|
||||||
|
case binary_operation_type::remainder:
|
||||||
|
out << "remainder";
|
||||||
|
break;
|
||||||
|
case binary_operation_type::logical_and:
|
||||||
|
out << "and";
|
||||||
|
break;
|
||||||
|
case binary_operation_type::logical_or:
|
||||||
|
out << "or";
|
||||||
|
break;
|
||||||
|
case binary_operation_type::logical_xor:
|
||||||
|
out << "xor";
|
||||||
|
break;
|
||||||
|
case binary_operation_type::equals:
|
||||||
|
out << "equals";
|
||||||
|
break;
|
||||||
|
case binary_operation_type::not_equals:
|
||||||
|
out << "not equals";
|
||||||
|
break;
|
||||||
|
case binary_operation_type::less:
|
||||||
|
out << "less";
|
||||||
|
break;
|
||||||
|
case binary_operation_type::greater:
|
||||||
|
out << "greater";
|
||||||
|
break;
|
||||||
|
case binary_operation_type::less_equals:
|
||||||
|
out << "less or equals";
|
||||||
|
break;
|
||||||
|
case binary_operation_type::greater_equals:
|
||||||
|
out << "greater or equals";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
43
libs/ast/include/pslang/ast/print.hpp
Normal file
43
libs/ast/include/pslang/ast/print.hpp
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <pslang/ast/statement.hpp>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace pslang::ast
|
||||||
|
{
|
||||||
|
|
||||||
|
struct print_options
|
||||||
|
{
|
||||||
|
std::string_view indent_string = "| ";
|
||||||
|
std::size_t indent_level = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
void print(std::ostream & out, bool_literal const & node, print_options const & options = {});
|
||||||
|
void print(std::ostream & out, i8_literal const & node, print_options const & options = {});
|
||||||
|
void print(std::ostream & out, u8_literal const & node, print_options const & options = {});
|
||||||
|
void print(std::ostream & out, i16_literal const & node, print_options const & options = {});
|
||||||
|
void print(std::ostream & out, u16_literal const & node, print_options const & options = {});
|
||||||
|
void print(std::ostream & out, i32_literal const & node, print_options const & options = {});
|
||||||
|
void print(std::ostream & out, u32_literal const & node, print_options const & options = {});
|
||||||
|
void print(std::ostream & out, i64_literal const & node, print_options const & options = {});
|
||||||
|
void print(std::ostream & out, u64_literal const & node, print_options const & options = {});
|
||||||
|
void print(std::ostream & out, f32_literal const & node, print_options const & options = {});
|
||||||
|
void print(std::ostream & out, f64_literal const & node, print_options const & options = {});
|
||||||
|
void print(std::ostream & out, literal const & node, print_options const & options = {});
|
||||||
|
void print(std::ostream & out, identifier const & node, print_options const & options = {});
|
||||||
|
void print(std::ostream & out, unary_operation const & node, print_options const & options = {});
|
||||||
|
void print(std::ostream & out, binary_operation const & node, print_options const & options = {});
|
||||||
|
void print(std::ostream & out, cast_operation const & node, print_options const & options = {});
|
||||||
|
void print(std::ostream & out, expression_ptr const & node, print_options const & options = {});
|
||||||
|
void print(std::ostream & out, assignment const & node, print_options const & options = {});
|
||||||
|
void print(std::ostream & out, variable_declaration const & node, print_options const & options = {});
|
||||||
|
void print(std::ostream & out, if_block const & node, print_options const & options = {});
|
||||||
|
void print(std::ostream & out, else_block const & node, print_options const & options = {});
|
||||||
|
void print(std::ostream & out, else_if_block const & node, print_options const & options = {});
|
||||||
|
void print(std::ostream & out, if_chain const & node, print_options const & options = {});
|
||||||
|
void print(std::ostream & out, while_block const & node, print_options const & options = {});
|
||||||
|
void print(std::ostream & out, statement_ptr const & node, print_options const & options = {});
|
||||||
|
void print(std::ostream & out, statement_list_ptr const & node, print_options const & options = {});
|
||||||
|
|
||||||
|
}
|
||||||
45
libs/ast/include/pslang/ast/statement.hpp
Normal file
45
libs/ast/include/pslang/ast/statement.hpp
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <pslang/ast/expression.hpp>
|
||||||
|
#include <pslang/ast/value_category.hpp>
|
||||||
|
#include <pslang/ast/control.hpp>
|
||||||
|
#include <pslang/ast/statement_fwd.hpp>
|
||||||
|
#include <pslang/type/type.hpp>
|
||||||
|
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
namespace pslang::ast
|
||||||
|
{
|
||||||
|
|
||||||
|
struct variable_declaration
|
||||||
|
{
|
||||||
|
value_category category;
|
||||||
|
std::string name;
|
||||||
|
type::type_ptr type;
|
||||||
|
expression_ptr initializer;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct assignment
|
||||||
|
{
|
||||||
|
expression_ptr lhs;
|
||||||
|
expression_ptr rhs;
|
||||||
|
};
|
||||||
|
|
||||||
|
using statement_impl = std::variant<
|
||||||
|
expression_ptr,
|
||||||
|
assignment,
|
||||||
|
variable_declaration,
|
||||||
|
if_block,
|
||||||
|
else_block,
|
||||||
|
else_if_block,
|
||||||
|
if_chain,
|
||||||
|
while_block
|
||||||
|
>;
|
||||||
|
|
||||||
|
struct statement
|
||||||
|
: statement_impl
|
||||||
|
{
|
||||||
|
using statement_impl::statement_impl;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
20
libs/ast/include/pslang/ast/statement_fwd.hpp
Normal file
20
libs/ast/include/pslang/ast/statement_fwd.hpp
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace pslang::ast
|
||||||
|
{
|
||||||
|
|
||||||
|
struct statement;
|
||||||
|
|
||||||
|
using statement_ptr = std::unique_ptr<statement>;
|
||||||
|
|
||||||
|
struct statement_list
|
||||||
|
{
|
||||||
|
std::vector<statement_ptr> statements;
|
||||||
|
};
|
||||||
|
|
||||||
|
using statement_list_ptr = std::unique_ptr<statement_list>;
|
||||||
|
|
||||||
|
}
|
||||||
31
libs/ast/include/pslang/ast/value_category.hpp
Normal file
31
libs/ast/include/pslang/ast/value_category.hpp
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace pslang::ast
|
||||||
|
{
|
||||||
|
|
||||||
|
enum class value_category
|
||||||
|
{
|
||||||
|
compile_time,
|
||||||
|
constant,
|
||||||
|
_mutable,
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Ostream>
|
||||||
|
Ostream & operator << (Ostream & out, value_category category)
|
||||||
|
{
|
||||||
|
switch (category)
|
||||||
|
{
|
||||||
|
case value_category::compile_time:
|
||||||
|
out << "compile-time";
|
||||||
|
break;
|
||||||
|
case value_category::constant:
|
||||||
|
out << "constant";
|
||||||
|
break;
|
||||||
|
case value_category::_mutable:
|
||||||
|
out << "mutable";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
246
libs/ast/source/print.cpp
Normal file
246
libs/ast/source/print.cpp
Normal file
|
|
@ -0,0 +1,246 @@
|
||||||
|
#include <pslang/ast/print.hpp>
|
||||||
|
#include <pslang/type/print.hpp>
|
||||||
|
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
namespace pslang::ast
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
print_options child(print_options options)
|
||||||
|
{
|
||||||
|
options.indent_level += 1;
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
void put_indent(std::ostream & out, print_options const & options)
|
||||||
|
{
|
||||||
|
for (std::size_t i = 0; i < options.indent_level; ++i)
|
||||||
|
out << options.indent_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
void newline(std::ostream & out)
|
||||||
|
{
|
||||||
|
out << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, bool_literal const & node, print_options const & options)
|
||||||
|
{
|
||||||
|
put_indent(out, options);
|
||||||
|
out << "bool literal { value = " << (node.value ? "true" : "false") << " }";
|
||||||
|
newline(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, i8_literal const & node, print_options const & options)
|
||||||
|
{
|
||||||
|
put_indent(out, options);
|
||||||
|
out << "i8 literal { value = " << (std::int32_t)node.value << " }";
|
||||||
|
newline(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, u8_literal const & node, print_options const & options)
|
||||||
|
{
|
||||||
|
put_indent(out, options);
|
||||||
|
out << "u8 literal { value = " << (std::uint32_t)node.value << " }";
|
||||||
|
newline(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, i16_literal const & node, print_options const & options)
|
||||||
|
{
|
||||||
|
put_indent(out, options);
|
||||||
|
out << "i16 literal { value = " << node.value << " }";
|
||||||
|
newline(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, u16_literal const & node, print_options const & options)
|
||||||
|
{
|
||||||
|
put_indent(out, options);
|
||||||
|
out << "u16 literal { value = " << node.value << " }";
|
||||||
|
newline(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, i32_literal const & node, print_options const & options)
|
||||||
|
{
|
||||||
|
put_indent(out, options);
|
||||||
|
out << "i32 literal { value = " << node.value << " }";
|
||||||
|
newline(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, u32_literal const & node, print_options const & options)
|
||||||
|
{
|
||||||
|
put_indent(out, options);
|
||||||
|
out << "u32 literal { value = " << node.value << " }";
|
||||||
|
newline(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, i64_literal const & node, print_options const & options)
|
||||||
|
{
|
||||||
|
put_indent(out, options);
|
||||||
|
out << "i64 literal { value = " << node.value << " }";
|
||||||
|
newline(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, u64_literal const & node, print_options const & options)
|
||||||
|
{
|
||||||
|
put_indent(out, options);
|
||||||
|
out << "u64 literal { value = " << node.value << " }";
|
||||||
|
newline(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, f32_literal const & node, print_options const & options)
|
||||||
|
{
|
||||||
|
put_indent(out, options);
|
||||||
|
out << "f32 literal { value = " << std::setprecision(7) << node.value << " }";
|
||||||
|
newline(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, f64_literal const & node, print_options const & options)
|
||||||
|
{
|
||||||
|
put_indent(out, options);
|
||||||
|
out << "f64 literal { value = " << std::setprecision(15) << node.value << " }";
|
||||||
|
newline(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, literal const & node, print_options const & options)
|
||||||
|
{
|
||||||
|
std::visit([&](auto const & value){ print(out, value, options); }, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, identifier const & node, print_options const & options)
|
||||||
|
{
|
||||||
|
put_indent(out, options);
|
||||||
|
out << "identifier { name = \"" << node.name << "\" }";
|
||||||
|
newline(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, unary_operation const & node, print_options const & options)
|
||||||
|
{
|
||||||
|
put_indent(out, options);
|
||||||
|
out << node.type;
|
||||||
|
newline(out);
|
||||||
|
print(out, node.arg1, child(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, binary_operation const & node, print_options const & options)
|
||||||
|
{
|
||||||
|
put_indent(out, options);
|
||||||
|
out << node.type;
|
||||||
|
newline(out);
|
||||||
|
print(out, node.arg1, child(options));
|
||||||
|
print(out, node.arg2, child(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, cast_operation const & node, print_options const & options)
|
||||||
|
{
|
||||||
|
put_indent(out, options);
|
||||||
|
out << "cast as ";
|
||||||
|
type::print(out, *node.type);
|
||||||
|
newline(out);
|
||||||
|
print(out, node.expression, child(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, expression_ptr const & node, print_options const & options)
|
||||||
|
{
|
||||||
|
std::visit([&](auto const & value){ print(out, value, options); }, *node);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, assignment const & node, print_options const & options)
|
||||||
|
{
|
||||||
|
put_indent(out, options);
|
||||||
|
out << "assignment";
|
||||||
|
newline(out);
|
||||||
|
print(out, node.lhs, child(options));
|
||||||
|
print(out, node.rhs, child(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, variable_declaration const & node, print_options const & options)
|
||||||
|
{
|
||||||
|
put_indent(out, options);
|
||||||
|
out << "variable declaration { category = " << node.category << ", name = \"" << node.name << "\"";
|
||||||
|
if (node.type)
|
||||||
|
{
|
||||||
|
out << ", type = ";
|
||||||
|
type::print(out, *node.type);
|
||||||
|
}
|
||||||
|
out << " }";
|
||||||
|
newline(out);
|
||||||
|
print(out, node.initializer, child(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, if_block const & node, print_options const & options)
|
||||||
|
{
|
||||||
|
put_indent(out, options);
|
||||||
|
out << "if";
|
||||||
|
newline(out);
|
||||||
|
print(out, node.condition, child(options));
|
||||||
|
print(out, node.statements, child(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, else_block const & node, print_options const & options)
|
||||||
|
{
|
||||||
|
put_indent(out, options);
|
||||||
|
out << "else";
|
||||||
|
newline(out);
|
||||||
|
print(out, node.statements, child(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, else_if_block const & node, print_options const & options)
|
||||||
|
{
|
||||||
|
put_indent(out, options);
|
||||||
|
out << "else if";
|
||||||
|
newline(out);
|
||||||
|
print(out, node.condition, child(options));
|
||||||
|
print(out, node.statements, child(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, if_chain const & node, print_options const & options)
|
||||||
|
{
|
||||||
|
put_indent(out, options);
|
||||||
|
out << "if chain";
|
||||||
|
newline(out);
|
||||||
|
for (auto const & block : node.blocks)
|
||||||
|
{
|
||||||
|
put_indent(out, child(options));
|
||||||
|
out << "condition";
|
||||||
|
newline(out);
|
||||||
|
if (block.condition)
|
||||||
|
print(out, block.condition, child(child(options)));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
put_indent(out, child(child(options)));
|
||||||
|
out << "(none)";
|
||||||
|
newline(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
put_indent(out, child(options));
|
||||||
|
out << "body";
|
||||||
|
newline(out);
|
||||||
|
print(out, block.statements, child(child(options)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, while_block const & node, print_options const & options)
|
||||||
|
{
|
||||||
|
put_indent(out, options);
|
||||||
|
out << "while";
|
||||||
|
newline(out);
|
||||||
|
print(out, node.condition, child(options));
|
||||||
|
print(out, node.statements, child(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, statement_ptr const & node, print_options const & options)
|
||||||
|
{
|
||||||
|
std::visit([&](auto const & value){ print(out, value, options); }, *node);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, statement_list_ptr const & node, print_options const & options)
|
||||||
|
{
|
||||||
|
for (auto const & statement : node->statements)
|
||||||
|
print(out, statement, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
6
libs/interpreter/CMakeLists.txt
Normal file
6
libs/interpreter/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
file(GLOB_RECURSE PSLANG_INTERPRETER_HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp")
|
||||||
|
file(GLOB_RECURSE PSLANG_INTERPRETER_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp")
|
||||||
|
|
||||||
|
add_library(pslang-interpreter STATIC ${PSLANG_INTERPRETER_HEADERS} ${PSLANG_INTERPRETER_SOURCES})
|
||||||
|
target_include_directories(pslang-interpreter PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||||
|
target_link_libraries(pslang-interpreter PUBLIC pslang-parser)
|
||||||
35
libs/interpreter/include/pslang/interpreter/context.hpp
Normal file
35
libs/interpreter/include/pslang/interpreter/context.hpp
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <pslang/interpreter/value.hpp>
|
||||||
|
#include <pslang/ast/value_category.hpp>
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace pslang::interpreter
|
||||||
|
{
|
||||||
|
|
||||||
|
struct variable_data
|
||||||
|
{
|
||||||
|
ast::value_category category;
|
||||||
|
interpreter::value value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct scope
|
||||||
|
{
|
||||||
|
std::unordered_map<std::string, variable_data> variables;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct context
|
||||||
|
{
|
||||||
|
bool trace = false;
|
||||||
|
std::vector<scope> scope_stack;
|
||||||
|
};
|
||||||
|
|
||||||
|
context empty_context();
|
||||||
|
|
||||||
|
void dump(std::ostream & out, context const & context);
|
||||||
|
|
||||||
|
}
|
||||||
12
libs/interpreter/include/pslang/interpreter/eval.hpp
Normal file
12
libs/interpreter/include/pslang/interpreter/eval.hpp
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <pslang/interpreter/context.hpp>
|
||||||
|
#include <pslang/interpreter/value.hpp>
|
||||||
|
#include <pslang/ast/expression_fwd.hpp>
|
||||||
|
|
||||||
|
namespace pslang::interpreter
|
||||||
|
{
|
||||||
|
|
||||||
|
value eval(context & context, ast::expression_ptr const & expression);
|
||||||
|
|
||||||
|
}
|
||||||
11
libs/interpreter/include/pslang/interpreter/interpreter.hpp
Normal file
11
libs/interpreter/include/pslang/interpreter/interpreter.hpp
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <pslang/ast/statement_fwd.hpp>
|
||||||
|
#include <pslang/interpreter/context.hpp>
|
||||||
|
|
||||||
|
namespace pslang::interpreter
|
||||||
|
{
|
||||||
|
|
||||||
|
void execute(context & context, ast::statement_list_ptr const & statements);
|
||||||
|
|
||||||
|
}
|
||||||
67
libs/interpreter/include/pslang/interpreter/value.hpp
Normal file
67
libs/interpreter/include/pslang/interpreter/value.hpp
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <pslang/type/type.hpp>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <variant>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace pslang::interpreter
|
||||||
|
{
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct primitive_value_base
|
||||||
|
{
|
||||||
|
using native_type = T;
|
||||||
|
T value;
|
||||||
|
};
|
||||||
|
|
||||||
|
using bool_value = primitive_value_base<bool>;
|
||||||
|
|
||||||
|
using i8_value = primitive_value_base<std::int8_t>;
|
||||||
|
using u8_value = primitive_value_base<std::uint8_t>;
|
||||||
|
using i16_value = primitive_value_base<std::int16_t>;
|
||||||
|
using u16_value = primitive_value_base<std::uint16_t>;
|
||||||
|
using i32_value = primitive_value_base<std::int32_t>;
|
||||||
|
using u32_value = primitive_value_base<std::uint32_t>;
|
||||||
|
using i64_value = primitive_value_base<std::int64_t>;
|
||||||
|
using u64_value = primitive_value_base<std::uint64_t>;
|
||||||
|
|
||||||
|
using f32_value = primitive_value_base<float>;
|
||||||
|
using f64_value = primitive_value_base<double>;
|
||||||
|
|
||||||
|
using primitive_value_impl = std::variant<
|
||||||
|
bool_value,
|
||||||
|
i8_value,
|
||||||
|
u8_value,
|
||||||
|
i16_value,
|
||||||
|
u16_value,
|
||||||
|
i32_value,
|
||||||
|
u32_value,
|
||||||
|
i64_value,
|
||||||
|
u64_value,
|
||||||
|
f32_value,
|
||||||
|
f64_value
|
||||||
|
>;
|
||||||
|
|
||||||
|
struct primitive_value
|
||||||
|
: primitive_value_impl
|
||||||
|
{
|
||||||
|
using primitive_value_impl::primitive_value_impl;
|
||||||
|
};
|
||||||
|
|
||||||
|
using value_impl = std::variant<
|
||||||
|
primitive_value
|
||||||
|
>;
|
||||||
|
|
||||||
|
struct value
|
||||||
|
: value_impl
|
||||||
|
{
|
||||||
|
using value_impl::value_impl;
|
||||||
|
};
|
||||||
|
|
||||||
|
type::type type_of(value const & value);
|
||||||
|
|
||||||
|
void print(std::ostream & out, value const & value);
|
||||||
|
|
||||||
|
}
|
||||||
30
libs/interpreter/source/context.cpp
Normal file
30
libs/interpreter/source/context.cpp
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
#include <pslang/interpreter/context.hpp>
|
||||||
|
#include <pslang/type/print.hpp>
|
||||||
|
|
||||||
|
namespace pslang::interpreter
|
||||||
|
{
|
||||||
|
|
||||||
|
context empty_context()
|
||||||
|
{
|
||||||
|
context result;
|
||||||
|
result.scope_stack.emplace_back();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dump(std::ostream & out, context const & context)
|
||||||
|
{
|
||||||
|
for (auto const & scope : context.scope_stack)
|
||||||
|
{
|
||||||
|
for (auto const & variable : scope.variables)
|
||||||
|
{
|
||||||
|
out << variable.first << " = ";
|
||||||
|
print(out, variable.second.value);
|
||||||
|
out << " (";
|
||||||
|
type::print(out, type_of(variable.second.value));
|
||||||
|
out << ")\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::cout << std::flush;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
332
libs/interpreter/source/eval.cpp
Normal file
332
libs/interpreter/source/eval.cpp
Normal file
|
|
@ -0,0 +1,332 @@
|
||||||
|
#include <pslang/interpreter/eval.hpp>
|
||||||
|
#include <pslang/interpreter/interpreter.hpp>
|
||||||
|
#include <pslang/interpreter/value.hpp>
|
||||||
|
#include <pslang/ast/expression.hpp>
|
||||||
|
#include <pslang/type/print.hpp>
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace pslang::interpreter
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
void print(std::ostream & out, ast::unary_operation_type type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case ast::unary_operation_type::negation:
|
||||||
|
out << "-";
|
||||||
|
return;
|
||||||
|
case ast::unary_operation_type::logical_not:
|
||||||
|
out << "!";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
out << "(unknown)";
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, ast::binary_operation_type type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case ast::binary_operation_type::addition:
|
||||||
|
out << "+";
|
||||||
|
return;
|
||||||
|
case ast::binary_operation_type::subtraction:
|
||||||
|
out << "-";
|
||||||
|
return;
|
||||||
|
case ast::binary_operation_type::multiplication:
|
||||||
|
out << "*";
|
||||||
|
return;
|
||||||
|
case ast::binary_operation_type::division:
|
||||||
|
out << "/";
|
||||||
|
return;
|
||||||
|
case ast::binary_operation_type::remainder:
|
||||||
|
out << "%";
|
||||||
|
return;
|
||||||
|
case ast::binary_operation_type::logical_and:
|
||||||
|
out << "&";
|
||||||
|
return;
|
||||||
|
case ast::binary_operation_type::logical_or:
|
||||||
|
out << "|";
|
||||||
|
return;
|
||||||
|
case ast::binary_operation_type::logical_xor:
|
||||||
|
out << "^";
|
||||||
|
return;
|
||||||
|
case ast::binary_operation_type::equals:
|
||||||
|
out << "==";
|
||||||
|
return;
|
||||||
|
case ast::binary_operation_type::not_equals:
|
||||||
|
out << "!=";
|
||||||
|
return;
|
||||||
|
case ast::binary_operation_type::less:
|
||||||
|
out << "<";
|
||||||
|
return;
|
||||||
|
case ast::binary_operation_type::greater:
|
||||||
|
out << ">";
|
||||||
|
return;
|
||||||
|
case ast::binary_operation_type::less_equals:
|
||||||
|
out << "<=";
|
||||||
|
return;
|
||||||
|
case ast::binary_operation_type::greater_equals:
|
||||||
|
out << ">=";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
out << "(unknown)";
|
||||||
|
}
|
||||||
|
|
||||||
|
value eval_impl(context & context, ast::expression_ptr const & expression);
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
value eval_impl(context & context, ast::numeric_literal_base<T> const & literal)
|
||||||
|
{
|
||||||
|
return primitive_value(primitive_value_base<T>{literal.value});;
|
||||||
|
}
|
||||||
|
|
||||||
|
value eval_impl(context & context, ast::literal const & literal)
|
||||||
|
{
|
||||||
|
return std::visit([&](auto const & expression){ return eval_impl(context, expression); }, literal);
|
||||||
|
}
|
||||||
|
|
||||||
|
value eval_impl(context & context, ast::identifier const & identifier)
|
||||||
|
{
|
||||||
|
for (auto it = context.scope_stack.rbegin(); it != context.scope_stack.rend(); ++it)
|
||||||
|
{
|
||||||
|
if (auto jt = it->variables.find(identifier.name); jt != it->variables.end())
|
||||||
|
return jt->second.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw std::runtime_error("Identifier \"" + identifier.name + "\" is not defined");
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
value unary_operation_impl(ast::unary_operation_type type, primitive_value_base<T> const & arg1)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case ast::unary_operation_type::negation:
|
||||||
|
if constexpr ((std::is_integral_v<T> || std::is_floating_point_v<T>) && !std::is_same_v<T, bool>)
|
||||||
|
{
|
||||||
|
return primitive_value(primitive_value_base<T>{static_cast<T>(-arg1.value)});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ast::unary_operation_type::logical_not:
|
||||||
|
if constexpr (std::is_same_v<T, bool>)
|
||||||
|
{
|
||||||
|
return primitive_value(primitive_value_base<T>{static_cast<T>(!arg1.value)});
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_integral_v<T>)
|
||||||
|
{
|
||||||
|
return primitive_value(primitive_value_base<T>{static_cast<T>(~arg1.value)});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostringstream os;
|
||||||
|
os << "Cannot apply unary operator \"";
|
||||||
|
print(os, type);
|
||||||
|
os << "\" to a value of type ";
|
||||||
|
type::print(os, type_of(primitive_value(arg1)));
|
||||||
|
throw std::runtime_error(os.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
value unary_operation_impl(ast::unary_operation_type type, primitive_value const & arg1)
|
||||||
|
{
|
||||||
|
return std::visit([&](auto const & value){ return unary_operation_impl(type, value); }, arg1);
|
||||||
|
}
|
||||||
|
|
||||||
|
value eval_impl(context & context, ast::unary_operation const & unary_operation)
|
||||||
|
{
|
||||||
|
auto arg1 = eval_impl(context, unary_operation.arg1);
|
||||||
|
return std::visit([&](auto const & value){ return unary_operation_impl(unary_operation.type, value); }, arg1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool requires_same_argument_type(ast::binary_operation_type)
|
||||||
|
{
|
||||||
|
// TODO: shift operators should return false
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
value binary_operation_impl_same_type(ast::binary_operation_type type, primitive_value_base<T> const & arg1, value const & arg2_generic)
|
||||||
|
{
|
||||||
|
primitive_value_base<T> const & arg2 = std::get<primitive_value_base<T>>(std::get<primitive_value>(arg2_generic));
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case ast::binary_operation_type::addition:
|
||||||
|
if constexpr (!std::is_same_v<T, bool>)
|
||||||
|
{
|
||||||
|
return primitive_value(primitive_value_base<T>{static_cast<T>(arg1.value + arg2.value)});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ast::binary_operation_type::subtraction:
|
||||||
|
if constexpr (!std::is_same_v<T, bool>)
|
||||||
|
{
|
||||||
|
return primitive_value(primitive_value_base<T>{static_cast<T>(arg1.value - arg2.value)});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ast::binary_operation_type::multiplication:
|
||||||
|
if constexpr (!std::is_same_v<T, bool>)
|
||||||
|
{
|
||||||
|
return primitive_value(primitive_value_base<T>{static_cast<T>(arg1.value * arg2.value)});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ast::binary_operation_type::division:
|
||||||
|
if constexpr (!std::is_same_v<T, bool>)
|
||||||
|
{
|
||||||
|
return primitive_value(primitive_value_base<T>{static_cast<T>(arg1.value / arg2.value)});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ast::binary_operation_type::remainder:
|
||||||
|
if constexpr (!std::is_same_v<T, bool> && std::is_integral_v<T>)
|
||||||
|
{
|
||||||
|
return primitive_value(primitive_value_base<T>{static_cast<T>(arg1.value % arg2.value)});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ast::binary_operation_type::logical_and:
|
||||||
|
if constexpr (std::is_same_v<T, bool>)
|
||||||
|
{
|
||||||
|
return primitive_value(primitive_value_base<T>{static_cast<T>(arg1.value && arg2.value)});
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_integral_v<T>)
|
||||||
|
{
|
||||||
|
return primitive_value(primitive_value_base<T>{static_cast<T>(arg1.value & arg2.value)});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ast::binary_operation_type::logical_or:
|
||||||
|
if constexpr (std::is_same_v<T, bool>)
|
||||||
|
{
|
||||||
|
return primitive_value(primitive_value_base<T>{static_cast<T>(arg1.value || arg2.value)});
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_integral_v<T>)
|
||||||
|
{
|
||||||
|
return primitive_value(primitive_value_base<T>{static_cast<T>(arg1.value | arg2.value)});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ast::binary_operation_type::logical_xor:
|
||||||
|
if constexpr (std::is_same_v<T, bool>)
|
||||||
|
{
|
||||||
|
return primitive_value(primitive_value_base<T>{static_cast<T>(arg1.value ^ arg2.value)});
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_integral_v<T>)
|
||||||
|
{
|
||||||
|
return primitive_value(primitive_value_base<T>{static_cast<T>(arg1.value ^ arg2.value)});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ast::binary_operation_type::equals:
|
||||||
|
return primitive_value(primitive_value_base<bool>{arg1.value == arg2.value});
|
||||||
|
case ast::binary_operation_type::not_equals:
|
||||||
|
return primitive_value(primitive_value_base<bool>{arg1.value != arg2.value});
|
||||||
|
case ast::binary_operation_type::less:
|
||||||
|
return primitive_value(primitive_value_base<bool>{arg1.value < arg2.value});
|
||||||
|
case ast::binary_operation_type::greater:
|
||||||
|
return primitive_value(primitive_value_base<bool>{arg1.value > arg2.value});
|
||||||
|
case ast::binary_operation_type::less_equals:
|
||||||
|
return primitive_value(primitive_value_base<bool>{arg1.value <= arg2.value});
|
||||||
|
case ast::binary_operation_type::greater_equals:
|
||||||
|
return primitive_value(primitive_value_base<bool>{arg1.value >= arg2.value});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostringstream os;
|
||||||
|
os << "Cannot apply binary operator \"";
|
||||||
|
print(os, type);
|
||||||
|
os << "\" to values of type ";
|
||||||
|
type::print(os, type_of(primitive_value(arg1)));
|
||||||
|
os << " and ";
|
||||||
|
type::print(os, type_of(primitive_value(arg2)));
|
||||||
|
throw std::runtime_error(os.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
value binary_operation_impl_same_type(ast::binary_operation_type type, primitive_value const & arg1, value const & arg2)
|
||||||
|
{
|
||||||
|
return std::visit([&](auto const & value){ return binary_operation_impl_same_type(type, value, arg2); }, arg1);
|
||||||
|
}
|
||||||
|
|
||||||
|
value eval_impl(context & context, ast::binary_operation const & binary_operation)
|
||||||
|
{
|
||||||
|
auto arg1 = eval_impl(context, binary_operation.arg1);
|
||||||
|
auto arg2 = eval_impl(context, binary_operation.arg2);
|
||||||
|
|
||||||
|
if (requires_same_argument_type(binary_operation.type))
|
||||||
|
{
|
||||||
|
auto type1 = type_of(arg1);
|
||||||
|
auto type2 = type_of(arg2);
|
||||||
|
|
||||||
|
if (!type::equal(type1, type2))
|
||||||
|
{
|
||||||
|
std::ostringstream os;
|
||||||
|
os << "Cannot apply binary operator \"";
|
||||||
|
print(os, binary_operation.type);
|
||||||
|
os << "\" to values of type ";
|
||||||
|
type::print(os, type1);
|
||||||
|
os << " and ";
|
||||||
|
type::print(os, type2);
|
||||||
|
throw std::runtime_error(os.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::visit([&](auto const & value){ return binary_operation_impl_same_type(binary_operation.type, value, arg2); }, arg1);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw std::runtime_error("eval(binary_operation) for different argument types not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename H>
|
||||||
|
value cast_impl(primitive_value_base<T> const & value, type::primitive_type_base<H> const & type)
|
||||||
|
{
|
||||||
|
if constexpr (std::is_same_v<T, H>)
|
||||||
|
{
|
||||||
|
return primitive_value(value);
|
||||||
|
}
|
||||||
|
else if constexpr (!std::is_same_v<T, bool> && !std::is_same_v<bool, H>)
|
||||||
|
{
|
||||||
|
return primitive_value(primitive_value_base<H>{static_cast<H>(value.value)});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostringstream os;
|
||||||
|
os << "Cannot cast value of type ";
|
||||||
|
type::print(os, type_of(primitive_value(value)));
|
||||||
|
os << " to type ";
|
||||||
|
type::print(os, type::primitive_type(type));
|
||||||
|
throw std::runtime_error(os.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
value cast_impl(primitive_value_base<T> const & value, type::primitive_type const & type)
|
||||||
|
{
|
||||||
|
return std::visit([&](auto const & type){ return cast_impl(value, type); }, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
value cast_impl(primitive_value_base<T> const & value, type::type const & type)
|
||||||
|
{
|
||||||
|
return std::visit([&](auto const & type){ return cast_impl(value, type); }, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
value cast_impl(primitive_value const & value, type::type const & type)
|
||||||
|
{
|
||||||
|
return std::visit([&](auto const & value){ return cast_impl(value, type); }, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
value eval_impl(context & context, ast::cast_operation const & cast_operation)
|
||||||
|
{
|
||||||
|
auto arg = eval(context, cast_operation.expression);
|
||||||
|
return std::visit([&](auto const & value){ return cast_impl(value, *cast_operation.type); }, arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
value eval_impl(context & context, ast::expression_ptr const & expression)
|
||||||
|
{
|
||||||
|
return std::visit([&](auto const & expression){ return eval_impl(context, expression); }, *expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
value eval(context & context, ast::expression_ptr const & expression)
|
||||||
|
{
|
||||||
|
return eval_impl(context, expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
158
libs/interpreter/source/interpreter.cpp
Normal file
158
libs/interpreter/source/interpreter.cpp
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
#include <pslang/interpreter/interpreter.hpp>
|
||||||
|
#include <pslang/interpreter/eval.hpp>
|
||||||
|
#include <pslang/ast/statement.hpp>
|
||||||
|
#include <pslang/type/print.hpp>
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace pslang::interpreter
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
void execute_impl(context & context, ast::expression_ptr const & expression)
|
||||||
|
{
|
||||||
|
eval(context, expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
void execute_impl(context & context, ast::assignment const & assignment)
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
if (auto identifier = std::get_if<ast::identifier>(assignment.lhs.get()))
|
||||||
|
name = identifier->name;
|
||||||
|
else
|
||||||
|
throw std::runtime_error("Cannot assign a value to a non-identifier");
|
||||||
|
|
||||||
|
for (auto it = context.scope_stack.rbegin(); it != context.scope_stack.rend(); ++it)
|
||||||
|
{
|
||||||
|
if (auto jt = it->variables.find(name); jt != it->variables.end())
|
||||||
|
{
|
||||||
|
if (jt->second.category != ast::value_category::_mutable)
|
||||||
|
throw std::runtime_error("Cannot assign a value to a non-mutable variable");
|
||||||
|
|
||||||
|
auto new_value = eval(context, assignment.rhs);
|
||||||
|
auto new_type = type_of(new_value);
|
||||||
|
auto existing_type = type_of(jt->second.value);
|
||||||
|
if (!type::equal(existing_type, new_type))
|
||||||
|
{
|
||||||
|
std::ostringstream os;
|
||||||
|
os << "Cannot assign a value of type ";
|
||||||
|
type::print(os, new_type);
|
||||||
|
os << " to a variable of type ";
|
||||||
|
type::print(os, existing_type);
|
||||||
|
throw std::runtime_error(os.str());
|
||||||
|
}
|
||||||
|
jt->second.value = std::move(new_value);;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw std::runtime_error("Identifier \"" + name + "\" is not defined");
|
||||||
|
}
|
||||||
|
|
||||||
|
void execute_impl(context & context, ast::variable_declaration const & variable_declaration)
|
||||||
|
{
|
||||||
|
auto & scope = context.scope_stack.back();
|
||||||
|
if (scope.variables.count(variable_declaration.name) > 0)
|
||||||
|
throw std::runtime_error("Error: variable \"" + variable_declaration.name + "\" is already declared");
|
||||||
|
|
||||||
|
auto value = eval(context, variable_declaration.initializer);
|
||||||
|
if (variable_declaration.type)
|
||||||
|
{
|
||||||
|
auto actual_type = type_of(value);
|
||||||
|
if (!type::equal(*variable_declaration.type, actual_type))
|
||||||
|
{
|
||||||
|
std::ostringstream os;
|
||||||
|
os << "Cannot initialize a variable of type ";
|
||||||
|
type::print(os, *variable_declaration.type);
|
||||||
|
os << " with an expression of type ";
|
||||||
|
type::print(os, actual_type);
|
||||||
|
throw std::runtime_error(os.str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scope.variables[variable_declaration.name] = {.category = variable_declaration.category, .value = value};
|
||||||
|
}
|
||||||
|
|
||||||
|
void execute_impl(context & context, ast::if_block const &)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Internal interpreter error: if blocks cannot be present in the final AST");
|
||||||
|
}
|
||||||
|
|
||||||
|
void execute_impl(context & context, ast::else_block const &)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Internal interpreter error: else blocks cannot be present in the final AST");
|
||||||
|
}
|
||||||
|
|
||||||
|
void execute_impl(context & context, ast::else_if_block const &)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Internal interpreter error: else if blocks cannot be present in the final AST");
|
||||||
|
}
|
||||||
|
|
||||||
|
void execute_impl(context & context, ast::if_chain const & if_chain)
|
||||||
|
{
|
||||||
|
for (auto const & block : if_chain.blocks)
|
||||||
|
{
|
||||||
|
if (block.condition)
|
||||||
|
{
|
||||||
|
auto value = eval(context, block.condition);
|
||||||
|
auto actual_type = type_of(value);
|
||||||
|
if (!type::equal(actual_type, type::primitive_type{type::bool_type{}}))
|
||||||
|
{
|
||||||
|
std::ostringstream os;
|
||||||
|
os << "Expected type bool, got type ";
|
||||||
|
type::print(os, actual_type);
|
||||||
|
os << " in if block condition";
|
||||||
|
throw std::runtime_error(os.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::get<bool_value>(std::get<primitive_value>(value)).value)
|
||||||
|
{
|
||||||
|
execute(context, block.statements);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void execute_impl(context & context, ast::while_block const & while_block)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
auto value = eval(context, while_block.condition);
|
||||||
|
auto actual_type = type_of(value);
|
||||||
|
if (!type::equal(actual_type, type::primitive_type{type::bool_type{}}))
|
||||||
|
{
|
||||||
|
std::ostringstream os;
|
||||||
|
os << "Expected type bool, got type ";
|
||||||
|
type::print(os, actual_type);
|
||||||
|
os << " in while block condition";
|
||||||
|
throw std::runtime_error(os.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::get<bool_value>(std::get<primitive_value>(value)).value)
|
||||||
|
{
|
||||||
|
execute(context, while_block.statements);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void execute_impl(context & context, ast::statement_list_ptr const & statements)
|
||||||
|
{
|
||||||
|
for (auto const & statement : statements->statements)
|
||||||
|
{
|
||||||
|
std::visit([&](auto const & statement){ execute_impl(context, statement); }, *statement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void execute(context & context, ast::statement_list_ptr const & statements)
|
||||||
|
{
|
||||||
|
execute_impl(context, statements);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
78
libs/interpreter/source/value.cpp
Normal file
78
libs/interpreter/source/value.cpp
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
#include <pslang/interpreter/value.hpp>
|
||||||
|
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
namespace pslang::interpreter
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
type::type type_of_impl(primitive_value_base<T> const &)
|
||||||
|
{
|
||||||
|
return type::primitive_type(type::primitive_type_base<T>{});
|
||||||
|
}
|
||||||
|
|
||||||
|
type::type type_of_impl(primitive_value const & value)
|
||||||
|
{
|
||||||
|
return std::visit([](auto const & value){ return type_of_impl(value); }, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
type::type type_of_impl(value const & value)
|
||||||
|
{
|
||||||
|
return std::visit([](auto const & value){ return type_of_impl(value); }, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void print_impl(std::ostream & out, primitive_value_base<T> const & value)
|
||||||
|
{
|
||||||
|
if constexpr (std::is_same_v<T, bool>)
|
||||||
|
{
|
||||||
|
out << (value.value ? "true" : "false");
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_integral_v<T> && std::is_signed_v<T>)
|
||||||
|
{
|
||||||
|
out << (std::int64_t)value.value;
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_integral_v<T> && std::is_unsigned_v<T>)
|
||||||
|
{
|
||||||
|
out << (std::uint64_t)value.value;
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, float>)
|
||||||
|
{
|
||||||
|
out << std::setprecision(7) << value.value;
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, double>)
|
||||||
|
{
|
||||||
|
out << std::setprecision(15) << value.value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
out << "(unknown)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_impl(std::ostream & out, primitive_value const & value)
|
||||||
|
{
|
||||||
|
std::visit([&](auto const & value){ return print_impl(out, value); }, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_impl(std::ostream & out, value const & value)
|
||||||
|
{
|
||||||
|
std::visit([&](auto const & value){ return print_impl(out, value); }, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type::type type_of(value const & value)
|
||||||
|
{
|
||||||
|
return type_of_impl(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, value const & value)
|
||||||
|
{
|
||||||
|
print_impl(out, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
47
libs/parser/CMakeLists.txt
Normal file
47
libs/parser/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
find_package(FLEX REQUIRED)
|
||||||
|
find_package(BISON REQUIRED)
|
||||||
|
|
||||||
|
set(PSLANG_LEXER_RULES_FILE "${CMAKE_CURRENT_SOURCE_DIR}/rules/pslang.l")
|
||||||
|
set(PSLANG_PARSER_RULES_FILE "${CMAKE_CURRENT_SOURCE_DIR}/rules/pslang.y")
|
||||||
|
|
||||||
|
set(PSLANG_LEXER_HEADER_FILE "${CMAKE_CURRENT_BINARY_DIR}/generated/gen_lexer.hpp")
|
||||||
|
set(PSLANG_LEXER_SOURCE_FILE "${CMAKE_CURRENT_BINARY_DIR}/generated/gen_lexer.cpp")
|
||||||
|
|
||||||
|
set(PSLANG_PARSER_HEADER_FILE "${CMAKE_CURRENT_BINARY_DIR}/generated/gen_parser.hpp")
|
||||||
|
set(PSLANG_PARSER_SOURCE_FILE "${CMAKE_CURRENT_BINARY_DIR}/generated/gen_parser.cpp")
|
||||||
|
|
||||||
|
flex_target(
|
||||||
|
generate-pslang-lexer
|
||||||
|
${PSLANG_LEXER_RULES_FILE}
|
||||||
|
${PSLANG_LEXER_SOURCE_FILE}
|
||||||
|
DEFINES_FILE ${PSLANG_LEXER_HEADER_FILE}
|
||||||
|
)
|
||||||
|
|
||||||
|
bison_target(
|
||||||
|
generate-pslang-parser
|
||||||
|
${PSLANG_PARSER_RULES_FILE}
|
||||||
|
${PSLANG_PARSER_SOURCE_FILE}
|
||||||
|
DEFINES_FILE ${PSLANG_PARSER_HEADER_FILE}
|
||||||
|
COMPILE_FLAGS -Wcounterexamples
|
||||||
|
)
|
||||||
|
|
||||||
|
add_flex_bison_dependency(generate-pslang-lexer generate-pslang-parser)
|
||||||
|
|
||||||
|
set(PSLANG_PARSER_RULE_FILES
|
||||||
|
${PSLANG_LEXER_RULES_FILE}
|
||||||
|
${PSLANG_PARSER_RULES_FILE}
|
||||||
|
)
|
||||||
|
|
||||||
|
set(PSLANG_PARSER_GENERATED_FILES
|
||||||
|
${PSLANG_LEXER_HEADER_FILE}
|
||||||
|
${PSLANG_LEXER_SOURCE_FILE}
|
||||||
|
${PSLANG_PARSER_HEADER_FILE}
|
||||||
|
${PSLANG_PARSER_SOURCE_FILE}
|
||||||
|
)
|
||||||
|
|
||||||
|
file(GLOB_RECURSE PSLANG_PARSER_HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp")
|
||||||
|
file(GLOB_RECURSE PSLANG_PARSER_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp")
|
||||||
|
|
||||||
|
add_library(pslang-parser STATIC ${PSLANG_PARSER_HEADERS} ${PSLANG_PARSER_SOURCES} ${PSLANG_PARSER_RULE_FILES} ${PSLANG_PARSER_GENERATED_FILES})
|
||||||
|
target_include_directories(pslang-parser PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_BINARY_DIR}/generated")
|
||||||
|
target_link_libraries(pslang-parser pslang-ast)
|
||||||
21
libs/parser/include/pslang/parser/context.hpp
Normal file
21
libs/parser/include/pslang/parser/context.hpp
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <pslang/parser/indented_statement.hpp>
|
||||||
|
|
||||||
|
namespace pslang::parser
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace bison
|
||||||
|
{
|
||||||
|
|
||||||
|
class location;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
struct context
|
||||||
|
{
|
||||||
|
bison::location & location;
|
||||||
|
indented_statement_list & result;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
23
libs/parser/include/pslang/parser/indented_statement.hpp
Normal file
23
libs/parser/include/pslang/parser/indented_statement.hpp
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <pslang/ast/statement_fwd.hpp>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace pslang::parser
|
||||||
|
{
|
||||||
|
|
||||||
|
struct indented_statement
|
||||||
|
{
|
||||||
|
std::size_t indentation;
|
||||||
|
ast::statement_ptr statement;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct indented_statement_list
|
||||||
|
{
|
||||||
|
std::vector<indented_statement> statements;
|
||||||
|
};
|
||||||
|
|
||||||
|
ast::statement_list_ptr finilize(indented_statement_list statements);
|
||||||
|
|
||||||
|
}
|
||||||
12
libs/parser/include/pslang/parser/parser.hpp
Normal file
12
libs/parser/include/pslang/parser/parser.hpp
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <pslang/ast/statement_fwd.hpp>
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
namespace pslang::parser
|
||||||
|
{
|
||||||
|
|
||||||
|
ast::statement_list_ptr parse(std::filesystem::path const & file);
|
||||||
|
|
||||||
|
}
|
||||||
83
libs/parser/rules/pslang.l
Normal file
83
libs/parser/rules/pslang.l
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
%option noyywrap nounput noinput
|
||||||
|
|
||||||
|
%{
|
||||||
|
|
||||||
|
#include "gen_parser.hpp"
|
||||||
|
#include <pslang/parser/context.hpp>
|
||||||
|
|
||||||
|
using bp = ::pslang::parser::bison::parser;
|
||||||
|
|
||||||
|
#define YY_DECL bp::symbol_type yylex(::pslang::parser::context& ctx)
|
||||||
|
#define YY_USER_ACTION ctx.location.columns(yyleng);
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
%{
|
||||||
|
ctx.location.step();
|
||||||
|
%}
|
||||||
|
|
||||||
|
[ ]+ { ctx.location.step(); }
|
||||||
|
|
||||||
|
const { return bp::make_const(ctx.location); }
|
||||||
|
let { return bp::make_let(ctx.location); }
|
||||||
|
mut { return bp::make_mut(ctx.location); }
|
||||||
|
if { return bp::make_if(ctx.location); }
|
||||||
|
else { return bp::make_else(ctx.location); }
|
||||||
|
while { return bp::make_while(ctx.location); }
|
||||||
|
as { return bp::make_as(ctx.location); }
|
||||||
|
true { return bp::make_true(ctx.location); }
|
||||||
|
false { return bp::make_false(ctx.location); }
|
||||||
|
|
||||||
|
bool { return bp::make_bool(ctx.location); }
|
||||||
|
i8 { return bp::make_i8(ctx.location); }
|
||||||
|
u8 { return bp::make_u8(ctx.location); }
|
||||||
|
i16 { return bp::make_i16(ctx.location); }
|
||||||
|
u16 { return bp::make_u16(ctx.location); }
|
||||||
|
i32 { return bp::make_i32(ctx.location); }
|
||||||
|
u32 { return bp::make_u32(ctx.location); }
|
||||||
|
i64 { return bp::make_i64(ctx.location); }
|
||||||
|
u64 { return bp::make_u64(ctx.location); }
|
||||||
|
f32 { return bp::make_f32(ctx.location); }
|
||||||
|
f64 { return bp::make_f64(ctx.location); }
|
||||||
|
|
||||||
|
[a-z]+ { return bp::make_name(yytext, ctx.location); }
|
||||||
|
|
||||||
|
"\n" { ctx.location.lines(1); return bp::make_newline(ctx.location); }
|
||||||
|
"\t" { return bp::make_indent(ctx.location); }
|
||||||
|
"=" { return bp::make_assignment(ctx.location); }
|
||||||
|
":" { return bp::make_colon(ctx.location); }
|
||||||
|
"(" { return bp::make_lparen(ctx.location); }
|
||||||
|
")" { return bp::make_rparen(ctx.location); }
|
||||||
|
"+" { return bp::make_plus(ctx.location); }
|
||||||
|
"-" { return bp::make_minus(ctx.location); }
|
||||||
|
"*" { return bp::make_asterisk(ctx.location); }
|
||||||
|
"/" { return bp::make_slash(ctx.location); }
|
||||||
|
"%" { return bp::make_percent(ctx.location); }
|
||||||
|
"&" { return bp::make_ampersand(ctx.location); }
|
||||||
|
"|" { return bp::make_vertical_bar(ctx.location); }
|
||||||
|
"^" { return bp::make_circumflex(ctx.location); }
|
||||||
|
"!" { return bp::make_exclamation(ctx.location); }
|
||||||
|
"==" { return bp::make_equals(ctx.location); }
|
||||||
|
"!=" { return bp::make_not_equals(ctx.location); }
|
||||||
|
"<" { return bp::make_less(ctx.location); }
|
||||||
|
">" { return bp::make_greater(ctx.location); }
|
||||||
|
"<=" { return bp::make_less_equals(ctx.location); }
|
||||||
|
">=" { return bp::make_greater_equals(ctx.location); }
|
||||||
|
|
||||||
|
[0-9]+b { return bp::make_lit_i8(yytext, ctx.location); }
|
||||||
|
[0-9]+ub { return bp::make_lit_u8(yytext, ctx.location); }
|
||||||
|
[0-9]+s { return bp::make_lit_i16(yytext, ctx.location); }
|
||||||
|
[0-9]+us { return bp::make_lit_u16(yytext, ctx.location); }
|
||||||
|
[0-9]+ { return bp::make_lit_i32(yytext, ctx.location); }
|
||||||
|
[0-9]+u { return bp::make_lit_u32(yytext, ctx.location); }
|
||||||
|
[0-9]+l { return bp::make_lit_i64(yytext, ctx.location); }
|
||||||
|
[0-9]+ul { return bp::make_lit_u64(yytext, ctx.location); }
|
||||||
|
|
||||||
|
[0-9]+\.[0-9]+ { return bp::make_lit_f32(yytext, ctx.location); }
|
||||||
|
[0-9]+\.[0-9]+l { return bp::make_lit_f64(yytext, ctx.location); }
|
||||||
|
|
||||||
|
<<EOF>> { return bp::make_end(ctx.location); }
|
||||||
|
|
||||||
|
. { throw std::runtime_error(std::string("Unexpected character: ") + yytext); }
|
||||||
285
libs/parser/rules/pslang.y
Normal file
285
libs/parser/rules/pslang.y
Normal file
|
|
@ -0,0 +1,285 @@
|
||||||
|
%skeleton "lalr1.cc"
|
||||||
|
%require "3.8.1"
|
||||||
|
%header
|
||||||
|
|
||||||
|
%language "C++"
|
||||||
|
%define api.namespace {pslang::parser::bison}
|
||||||
|
%define api.location.file none
|
||||||
|
|
||||||
|
%define api.token.raw
|
||||||
|
%define api.token.constructor
|
||||||
|
%define api.value.type variant
|
||||||
|
%define api.value.automove
|
||||||
|
|
||||||
|
%define parse.assert
|
||||||
|
%define parse.trace
|
||||||
|
%define parse.error detailed
|
||||||
|
%define parse.lac full
|
||||||
|
|
||||||
|
%locations
|
||||||
|
|
||||||
|
%{
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
void yyerror(char const * s)
|
||||||
|
{
|
||||||
|
printf("error: %s\n", s);
|
||||||
|
}
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
|
%code requires {
|
||||||
|
|
||||||
|
#include <pslang/ast/statement.hpp>
|
||||||
|
#include <pslang/parser/indented_statement.hpp>
|
||||||
|
|
||||||
|
namespace pslang::parser {
|
||||||
|
|
||||||
|
struct context;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
%code {
|
||||||
|
|
||||||
|
#include <pslang/parser/context.hpp>
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <sstream>
|
||||||
|
#include <charconv>
|
||||||
|
|
||||||
|
#define YY_DECL ::pslang::parser::bison::parser::symbol_type yylex(::pslang::parser::context& ctx)
|
||||||
|
YY_DECL;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
::pslang::ast::literal parse_numeric_literal(std::string const & str)
|
||||||
|
{
|
||||||
|
T value;
|
||||||
|
auto result = std::from_chars(str.data(), str.data() + str.size(), value);
|
||||||
|
if (result.ec != std::errc())
|
||||||
|
throw std::system_error(std::make_error_code(result.ec));
|
||||||
|
return ::pslang::ast::numeric_literal_base<T>{value};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
%param { ::pslang::parser::context& ctx }
|
||||||
|
|
||||||
|
%define api.token.prefix {tok_}
|
||||||
|
|
||||||
|
%token newline "newline"
|
||||||
|
%token indent "indentation"
|
||||||
|
%token assignment "="
|
||||||
|
%token colon ":"
|
||||||
|
%token lparen "("
|
||||||
|
%token rparen ")"
|
||||||
|
%token plus "+"
|
||||||
|
%token minus "-"
|
||||||
|
%token asterisk "*"
|
||||||
|
%token slash "/"
|
||||||
|
%token percent "%"
|
||||||
|
%token ampersand "&"
|
||||||
|
%token vertical_bar "|"
|
||||||
|
%token circumflex "^"
|
||||||
|
%token exclamation "!"
|
||||||
|
%token equals "=="
|
||||||
|
%token not_equals "!="
|
||||||
|
%token less "<"
|
||||||
|
%token greater ">"
|
||||||
|
%token less_equals "<="
|
||||||
|
%token greater_equals ">="
|
||||||
|
|
||||||
|
%token <std::string> lit_i8
|
||||||
|
%token <std::string> lit_u8
|
||||||
|
%token <std::string> lit_i16
|
||||||
|
%token <std::string> lit_u16
|
||||||
|
%token <std::string> lit_i32
|
||||||
|
%token <std::string> lit_u32
|
||||||
|
%token <std::string> lit_i64
|
||||||
|
%token <std::string> lit_u64
|
||||||
|
|
||||||
|
%token <std::string> lit_f8
|
||||||
|
%token <std::string> lit_f16
|
||||||
|
%token <std::string> lit_f32
|
||||||
|
%token <std::string> lit_f64
|
||||||
|
|
||||||
|
%token <std::string> name
|
||||||
|
|
||||||
|
%token const
|
||||||
|
%token let
|
||||||
|
%token mut
|
||||||
|
%token if
|
||||||
|
%token else
|
||||||
|
%token while
|
||||||
|
%token as
|
||||||
|
%token true
|
||||||
|
%token false
|
||||||
|
|
||||||
|
%token bool
|
||||||
|
%token i8
|
||||||
|
%token u8
|
||||||
|
%token i16
|
||||||
|
%token u16
|
||||||
|
%token i32
|
||||||
|
%token u32
|
||||||
|
%token i64
|
||||||
|
%token u64
|
||||||
|
%token f32
|
||||||
|
%token f64
|
||||||
|
|
||||||
|
%token end 0
|
||||||
|
|
||||||
|
%type <indented_statement_list> indented_statement_list
|
||||||
|
%type <std::size_t> indentation
|
||||||
|
%type <ast::statement> statement
|
||||||
|
%type <ast::variable_declaration> variable_declaration
|
||||||
|
%type <ast::value_category> variable_keyword
|
||||||
|
%type <type::type> type_expression
|
||||||
|
%type <type::primitive_type> primitive_type
|
||||||
|
%type <ast::expression> expression
|
||||||
|
%type <ast::expression> bool_expression
|
||||||
|
%type <ast::expression> compare_expression
|
||||||
|
%type <ast::expression> as_expression
|
||||||
|
%type <ast::expression> negate_expression
|
||||||
|
%type <ast::expression> sum_expression
|
||||||
|
%type <ast::expression> mult_expression
|
||||||
|
%type <ast::expression> not_expression
|
||||||
|
%type <ast::expression> base_expression
|
||||||
|
%type <ast::expression> literal
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
module
|
||||||
|
: indented_statement_list end { ctx.result = $1; }
|
||||||
|
;
|
||||||
|
|
||||||
|
indented_statement_list
|
||||||
|
: indented_statement_list indentation statement newline { auto tmp = $1; tmp.statements.push_back({$2, std::make_unique<ast::statement>($3)}); $$ = std::move(tmp); }
|
||||||
|
| indented_statement_list indentation newline { $$ = $1; }
|
||||||
|
| %empty { $$ = {}; }
|
||||||
|
;
|
||||||
|
|
||||||
|
indentation
|
||||||
|
: indent indentation { $$ = $2 + 1ull; }
|
||||||
|
| %empty { $$ = 0ull; }
|
||||||
|
;
|
||||||
|
|
||||||
|
statement
|
||||||
|
: expression { $$ = std::make_unique<ast::expression>($1); }
|
||||||
|
| expression assignment expression { $$ = ast::assignment{ std::make_unique<ast::expression>($1), std::make_unique<ast::expression>($3) }; }
|
||||||
|
| variable_declaration { $$ = $1; }
|
||||||
|
| if expression colon { $$ = ast::if_block{std::make_unique<ast::expression>($2), {}}; }
|
||||||
|
| else colon { $$ = ast::else_block{{}}; }
|
||||||
|
| else if expression colon { $$ = ast::else_if_block{std::make_unique<ast::expression>($3), {}}; }
|
||||||
|
| while expression colon { $$ = ast::while_block{std::make_unique<ast::expression>($2), {}}; }
|
||||||
|
;
|
||||||
|
|
||||||
|
variable_declaration
|
||||||
|
: variable_keyword name assignment expression { $$ = ast::variable_declaration{$1, $2, nullptr, std::make_unique<ast::expression>($4)}; }
|
||||||
|
| variable_keyword name colon type_expression assignment expression { $$ = ast::variable_declaration{$1, $2, std::make_unique<type::type>($4), std::make_unique<ast::expression>($6)}; }
|
||||||
|
;
|
||||||
|
|
||||||
|
variable_keyword
|
||||||
|
: const { $$ = ast::value_category::compile_time; }
|
||||||
|
| let { $$ = ast::value_category::constant; }
|
||||||
|
| mut { $$ = ast::value_category::_mutable; }
|
||||||
|
;
|
||||||
|
|
||||||
|
type_expression
|
||||||
|
: primitive_type { $$ = type::type($1); }
|
||||||
|
;
|
||||||
|
|
||||||
|
primitive_type
|
||||||
|
: bool { $$ = type::bool_type{}; }
|
||||||
|
| i8 { $$ = type::i8_type{}; }
|
||||||
|
| u8 { $$ = type::u8_type{}; }
|
||||||
|
| i16 { $$ = type::i16_type{}; }
|
||||||
|
| u16 { $$ = type::u16_type{}; }
|
||||||
|
| i32 { $$ = type::i32_type{}; }
|
||||||
|
| u32 { $$ = type::u32_type{}; }
|
||||||
|
| i64 { $$ = type::i64_type{}; }
|
||||||
|
| u64 { $$ = type::u64_type{}; }
|
||||||
|
| f32 { $$ = type::f32_type{}; }
|
||||||
|
| f64 { $$ = type::f64_type{}; }
|
||||||
|
;
|
||||||
|
|
||||||
|
expression
|
||||||
|
: bool_expression { $$ = $1; }
|
||||||
|
;
|
||||||
|
|
||||||
|
bool_expression
|
||||||
|
: compare_expression { $$ = $1; }
|
||||||
|
| bool_expression ampersand compare_expression { $$ = ast::binary_operation{ast::binary_operation_type::logical_and, std::make_unique<ast::expression>($1), std::make_unique<ast::expression>($3) }; }
|
||||||
|
| bool_expression vertical_bar compare_expression { $$ = ast::binary_operation{ast::binary_operation_type::logical_or, std::make_unique<ast::expression>($1), std::make_unique<ast::expression>($3) }; }
|
||||||
|
| bool_expression circumflex compare_expression { $$ = ast::binary_operation{ast::binary_operation_type::logical_xor, std::make_unique<ast::expression>($1), std::make_unique<ast::expression>($3) }; }
|
||||||
|
;
|
||||||
|
|
||||||
|
compare_expression
|
||||||
|
: as_expression { $$ = $1; }
|
||||||
|
| compare_expression equals as_expression { $$ = ast::binary_operation{ast::binary_operation_type::equals, std::make_unique<ast::expression>($1), std::make_unique<ast::expression>($3) }; }
|
||||||
|
| compare_expression not_equals as_expression { $$ = ast::binary_operation{ast::binary_operation_type::not_equals, std::make_unique<ast::expression>($1), std::make_unique<ast::expression>($3) }; }
|
||||||
|
| compare_expression less as_expression { $$ = ast::binary_operation{ast::binary_operation_type::less, std::make_unique<ast::expression>($1), std::make_unique<ast::expression>($3) }; }
|
||||||
|
| compare_expression greater as_expression { $$ = ast::binary_operation{ast::binary_operation_type::greater, std::make_unique<ast::expression>($1), std::make_unique<ast::expression>($3) }; }
|
||||||
|
| compare_expression less_equals as_expression { $$ = ast::binary_operation{ast::binary_operation_type::less_equals, std::make_unique<ast::expression>($1), std::make_unique<ast::expression>($3) }; }
|
||||||
|
| compare_expression greater_equals as_expression { $$ = ast::binary_operation{ast::binary_operation_type::greater_equals, std::make_unique<ast::expression>($1), std::make_unique<ast::expression>($3) }; }
|
||||||
|
;
|
||||||
|
|
||||||
|
as_expression
|
||||||
|
: negate_expression { $$ = $1; }
|
||||||
|
| negate_expression as type_expression { $$ = ast::cast_operation{ std::make_unique<ast::expression>($1), std::make_unique<type::type>($3) }; }
|
||||||
|
;
|
||||||
|
|
||||||
|
negate_expression
|
||||||
|
: sum_expression { $$ = $1; }
|
||||||
|
| minus sum_expression { $$ = ast::unary_operation{ast::unary_operation_type::negation, std::make_unique<ast::expression>($2) }; }
|
||||||
|
;
|
||||||
|
|
||||||
|
sum_expression
|
||||||
|
: mult_expression { $$ = $1; }
|
||||||
|
| sum_expression plus mult_expression { $$ = ast::binary_operation{ast::binary_operation_type::addition, std::make_unique<ast::expression>($1), std::make_unique<ast::expression>($3) }; }
|
||||||
|
| sum_expression minus mult_expression { $$ = ast::binary_operation{ast::binary_operation_type::subtraction, std::make_unique<ast::expression>($1), std::make_unique<ast::expression>($3) }; }
|
||||||
|
;
|
||||||
|
|
||||||
|
mult_expression
|
||||||
|
: not_expression { $$ = $1; }
|
||||||
|
| mult_expression asterisk not_expression { $$ = ast::binary_operation{ast::binary_operation_type::multiplication, std::make_unique<ast::expression>($1), std::make_unique<ast::expression>($3) }; }
|
||||||
|
| mult_expression slash not_expression { $$ = ast::binary_operation{ast::binary_operation_type::division, std::make_unique<ast::expression>($1), std::make_unique<ast::expression>($3) }; }
|
||||||
|
| mult_expression percent not_expression { $$ = ast::binary_operation{ast::binary_operation_type::remainder, std::make_unique<ast::expression>($1), std::make_unique<ast::expression>($3) }; }
|
||||||
|
;
|
||||||
|
|
||||||
|
not_expression
|
||||||
|
: base_expression
|
||||||
|
| exclamation base_expression { $$ = ast::unary_operation{ast::unary_operation_type::logical_not, std::make_unique<ast::expression>($2) }; }
|
||||||
|
;
|
||||||
|
|
||||||
|
base_expression
|
||||||
|
: literal
|
||||||
|
| name { $$ = ast::identifier{$1}; }
|
||||||
|
| lparen expression rparen { $$ = $2; }
|
||||||
|
;
|
||||||
|
|
||||||
|
literal
|
||||||
|
: true { $$ = ast::literal(ast::bool_literal{true}); }
|
||||||
|
| false { $$ = ast::literal(ast::bool_literal{false}); }
|
||||||
|
| lit_i8 { $$ = parse_numeric_literal<std::int8_t>($1); }
|
||||||
|
| lit_u8 { $$ = parse_numeric_literal<std::uint8_t>($1); }
|
||||||
|
| lit_i16 { $$ = parse_numeric_literal<std::int16_t>($1); }
|
||||||
|
| lit_u16 { $$ = parse_numeric_literal<std::uint16_t>($1); }
|
||||||
|
| lit_i32 { $$ = parse_numeric_literal<std::int32_t>($1); }
|
||||||
|
| lit_u32 { $$ = parse_numeric_literal<std::uint32_t>($1); }
|
||||||
|
| lit_i64 { $$ = parse_numeric_literal<std::int64_t>($1); }
|
||||||
|
| lit_u64 { $$ = parse_numeric_literal<std::uint64_t>($1); }
|
||||||
|
| lit_f32 { $$ = parse_numeric_literal<float>($1); }
|
||||||
|
| lit_f64 { $$ = parse_numeric_literal<double>($1); }
|
||||||
|
;
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
void pslang::parser::bison::parser::error(location_type const& location, std::string const& message)
|
||||||
|
{
|
||||||
|
std::ostringstream os;
|
||||||
|
os << "Error parsing at " << location << ": " << message << "\n";
|
||||||
|
throw std::runtime_error(os.str());
|
||||||
|
}
|
||||||
131
libs/parser/source/finilize.cpp
Normal file
131
libs/parser/source/finilize.cpp
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
#include <pslang/parser/indented_statement.hpp>
|
||||||
|
#include <pslang/ast/statement.hpp>
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace pslang::parser
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
ast::statement_list * get_statement_list(ast::expression_ptr &)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ast::statement_list * get_statement_list(ast::assignment &)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ast::statement_list * get_statement_list(ast::variable_declaration &)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ast::statement_list * get_statement_list(ast::if_block & node)
|
||||||
|
{
|
||||||
|
node.statements = std::make_unique<ast::statement_list>();
|
||||||
|
return node.statements.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
ast::statement_list * get_statement_list(ast::else_block & node)
|
||||||
|
{
|
||||||
|
node.statements = std::make_unique<ast::statement_list>();
|
||||||
|
return node.statements.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
ast::statement_list * get_statement_list(ast::else_if_block & node)
|
||||||
|
{
|
||||||
|
node.statements = std::make_unique<ast::statement_list>();
|
||||||
|
return node.statements.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// NB: if chain merging happens after retrieving statement list
|
||||||
|
ast::statement_list * get_statement_list(ast::if_chain & node)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ast::statement_list * get_statement_list(ast::while_block & node)
|
||||||
|
{
|
||||||
|
node.statements = std::make_unique<ast::statement_list>();
|
||||||
|
return node.statements.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
ast::statement_list * get_statement_list(ast::statement & statement)
|
||||||
|
{
|
||||||
|
return std::visit([](auto & value){ return get_statement_list(value); }, statement);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ast::statement_list_ptr finilize(indented_statement_list statements)
|
||||||
|
{
|
||||||
|
ast::statement_list_ptr result = std::make_unique<ast::statement_list>();
|
||||||
|
|
||||||
|
std::vector<ast::statement_list *> stack;
|
||||||
|
stack.push_back(result.get());
|
||||||
|
std::size_t current_indent = 0;
|
||||||
|
|
||||||
|
for (auto & statement : statements.statements)
|
||||||
|
{
|
||||||
|
if (statement.indentation > current_indent)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Unexpected indent");
|
||||||
|
}
|
||||||
|
|
||||||
|
while (statement.indentation < current_indent)
|
||||||
|
{
|
||||||
|
stack.pop_back();
|
||||||
|
--current_indent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now statement.indentation == current_indent
|
||||||
|
|
||||||
|
auto list = get_statement_list(*statement.statement);
|
||||||
|
|
||||||
|
if (auto if_block = std::get_if<ast::if_block>(statement.statement.get()))
|
||||||
|
{
|
||||||
|
ast::if_chain chain;
|
||||||
|
chain.blocks.push_back({.condition = std::move(if_block->condition), .statements = std::move(if_block->statements)});
|
||||||
|
stack.back()->statements.push_back(std::make_unique<ast::statement>(std::move(chain)));
|
||||||
|
}
|
||||||
|
else if (auto else_block = std::get_if<ast::else_block>(statement.statement.get()))
|
||||||
|
{
|
||||||
|
if (stack.back()->statements.empty())
|
||||||
|
throw std::runtime_error("Unexpected else block");
|
||||||
|
auto chain = std::get_if<ast::if_chain>(stack.back()->statements.back().get());
|
||||||
|
if (!chain || chain->blocks.empty() || !chain->blocks.back().condition)
|
||||||
|
throw std::runtime_error("Unexpected else block");
|
||||||
|
|
||||||
|
chain->blocks.push_back({.condition = nullptr, .statements = std::move(else_block->statements)});
|
||||||
|
}
|
||||||
|
else if (auto else_if_block = std::get_if<ast::else_if_block>(statement.statement.get()))
|
||||||
|
{
|
||||||
|
if (stack.back()->statements.empty())
|
||||||
|
throw std::runtime_error("Unexpected else if block");
|
||||||
|
auto chain = std::get_if<ast::if_chain>(stack.back()->statements.back().get());
|
||||||
|
if (!chain || chain->blocks.empty() || !chain->blocks.back().condition)
|
||||||
|
throw std::runtime_error("Unexpected else if block");
|
||||||
|
|
||||||
|
chain->blocks.push_back({.condition = std::move(else_if_block->condition), .statements = std::move(else_if_block->statements)});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stack.back()->statements.push_back(std::move(statement.statement));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (list)
|
||||||
|
{
|
||||||
|
stack.push_back(list);
|
||||||
|
++current_indent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
31
libs/parser/source/parser.cpp
Normal file
31
libs/parser/source/parser.cpp
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#include <pslang/parser/parser.hpp>
|
||||||
|
#include <pslang/parser/context.hpp>
|
||||||
|
#include <pslang/parser/indented_statement.hpp>
|
||||||
|
#include "gen_parser.hpp"
|
||||||
|
#include "gen_lexer.hpp"
|
||||||
|
|
||||||
|
namespace pslang::parser
|
||||||
|
{
|
||||||
|
|
||||||
|
ast::statement_list_ptr parse(std::filesystem::path const & file)
|
||||||
|
{
|
||||||
|
auto filename = file.string();
|
||||||
|
|
||||||
|
yyin = fopen(filename.c_str(), "r");
|
||||||
|
if (!yyin)
|
||||||
|
throw std::system_error(std::make_error_code(static_cast<std::errc>(errno)));
|
||||||
|
|
||||||
|
bison::location location(&filename);
|
||||||
|
indented_statement_list result;
|
||||||
|
context ctx{location, result};
|
||||||
|
|
||||||
|
bison::parser parser(ctx);
|
||||||
|
|
||||||
|
parser.parse();
|
||||||
|
|
||||||
|
fclose(yyin);
|
||||||
|
|
||||||
|
return finilize(std::move(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
5
libs/type/CMakeLists.txt
Normal file
5
libs/type/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
file(GLOB_RECURSE PSLANG_TYPE_HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp")
|
||||||
|
file(GLOB_RECURSE PSLANG_TYPE_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp")
|
||||||
|
|
||||||
|
add_library(pslang-type STATIC ${PSLANG_TYPE_HEADERS} ${PSLANG_TYPE_SOURCES})
|
||||||
|
target_include_directories(pslang-type PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||||
55
libs/type/include/pslang/type/primitive.hpp
Normal file
55
libs/type/include/pslang/type/primitive.hpp
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <variant>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace pslang::type
|
||||||
|
{
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct primitive_type_base
|
||||||
|
{
|
||||||
|
using native_type = T;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool operator == (primitive_type_base<T> const &, primitive_type_base<T> const &)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
using bool_type = primitive_type_base<bool>;
|
||||||
|
|
||||||
|
using i8_type = primitive_type_base<std::int8_t>;
|
||||||
|
using u8_type = primitive_type_base<std::uint8_t>;
|
||||||
|
using i16_type = primitive_type_base<std::int16_t>;
|
||||||
|
using u16_type = primitive_type_base<std::uint16_t>;
|
||||||
|
using i32_type = primitive_type_base<std::int32_t>;
|
||||||
|
using u32_type = primitive_type_base<std::uint32_t>;
|
||||||
|
using i64_type = primitive_type_base<std::int64_t>;
|
||||||
|
using u64_type = primitive_type_base<std::uint64_t>;
|
||||||
|
|
||||||
|
using f32_type = primitive_type_base<float>;
|
||||||
|
using f64_type = primitive_type_base<double>;
|
||||||
|
|
||||||
|
using primitive_type_impl = std::variant<
|
||||||
|
bool_type,
|
||||||
|
i8_type,
|
||||||
|
u8_type,
|
||||||
|
i16_type,
|
||||||
|
u16_type,
|
||||||
|
i32_type,
|
||||||
|
u32_type,
|
||||||
|
i64_type,
|
||||||
|
u64_type,
|
||||||
|
f32_type,
|
||||||
|
f64_type
|
||||||
|
>;
|
||||||
|
|
||||||
|
struct primitive_type
|
||||||
|
: primitive_type_impl
|
||||||
|
{
|
||||||
|
using primitive_type_impl::primitive_type_impl;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
12
libs/type/include/pslang/type/print.hpp
Normal file
12
libs/type/include/pslang/type/print.hpp
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <pslang/type/type_fwd.hpp>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace pslang::type
|
||||||
|
{
|
||||||
|
|
||||||
|
void print(std::ostream & out, type const & type);
|
||||||
|
|
||||||
|
}
|
||||||
23
libs/type/include/pslang/type/type.hpp
Normal file
23
libs/type/include/pslang/type/type.hpp
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <pslang/type/primitive.hpp>
|
||||||
|
#include <pslang/type/type_fwd.hpp>
|
||||||
|
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
namespace pslang::type
|
||||||
|
{
|
||||||
|
|
||||||
|
using type_impl = std::variant<
|
||||||
|
primitive_type
|
||||||
|
>;
|
||||||
|
|
||||||
|
struct type
|
||||||
|
: type_impl
|
||||||
|
{
|
||||||
|
using type_impl::type_impl;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool equal(type const & t1, type const & t2);
|
||||||
|
|
||||||
|
}
|
||||||
12
libs/type/include/pslang/type/type_fwd.hpp
Normal file
12
libs/type/include/pslang/type/type_fwd.hpp
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace pslang::type
|
||||||
|
{
|
||||||
|
|
||||||
|
struct type;
|
||||||
|
|
||||||
|
using type_ptr = std::unique_ptr<type>;
|
||||||
|
|
||||||
|
}
|
||||||
82
libs/type/source/print.cpp
Normal file
82
libs/type/source/print.cpp
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
#include <pslang/type/print.hpp>
|
||||||
|
#include <pslang/type/type.hpp>
|
||||||
|
|
||||||
|
namespace pslang::type
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
void print_impl(std::ostream & out, bool_type const &)
|
||||||
|
{
|
||||||
|
out << "bool";
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_impl(std::ostream & out, i8_type const &)
|
||||||
|
{
|
||||||
|
out << "i8";
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_impl(std::ostream & out, u8_type const &)
|
||||||
|
{
|
||||||
|
out << "u8";
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_impl(std::ostream & out, i16_type const &)
|
||||||
|
{
|
||||||
|
out << "i16";
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_impl(std::ostream & out, u16_type const &)
|
||||||
|
{
|
||||||
|
out << "u16";
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_impl(std::ostream & out, i32_type const &)
|
||||||
|
{
|
||||||
|
out << "i32";
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_impl(std::ostream & out, u32_type const &)
|
||||||
|
{
|
||||||
|
out << "u32";
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_impl(std::ostream & out, i64_type const &)
|
||||||
|
{
|
||||||
|
out << "i64";
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_impl(std::ostream & out, u64_type const &)
|
||||||
|
{
|
||||||
|
out << "u64";
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_impl(std::ostream & out, f32_type const &)
|
||||||
|
{
|
||||||
|
out << "f32";
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_impl(std::ostream & out, f64_type const &)
|
||||||
|
{
|
||||||
|
out << "f64";
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_impl(std::ostream & out, primitive_type const & type)
|
||||||
|
{
|
||||||
|
std::visit([&](auto const & value){ print_impl(out, value); }, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_impl(std::ostream & out, type const & type)
|
||||||
|
{
|
||||||
|
std::visit([&](auto const & value){ print_impl(out, value); }, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream & out, type const & type)
|
||||||
|
{
|
||||||
|
print_impl(out, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
11
libs/type/source/type.cpp
Normal file
11
libs/type/source/type.cpp
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
#include <pslang/type/type.hpp>
|
||||||
|
|
||||||
|
namespace pslang::type
|
||||||
|
{
|
||||||
|
|
||||||
|
bool equal(type const & t1, type const & t2)
|
||||||
|
{
|
||||||
|
return static_cast<type_impl const &>(t1) == static_cast<type_impl const &>(t2);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
210
spec.txt
Normal file
210
spec.txt
Normal file
|
|
@ -0,0 +1,210 @@
|
||||||
|
======== TYPES ========
|
||||||
|
|
||||||
|
Built-in types:
|
||||||
|
unit
|
||||||
|
bool
|
||||||
|
u8
|
||||||
|
u16
|
||||||
|
u32
|
||||||
|
u64
|
||||||
|
i8
|
||||||
|
i16
|
||||||
|
i32
|
||||||
|
i64
|
||||||
|
f8 (maybe? cpu-emulated at best)
|
||||||
|
f16 (maybe? cpu-emulated at best)
|
||||||
|
f32
|
||||||
|
f64
|
||||||
|
|
||||||
|
N.B.: there is no dedicated 'char' type, strings operate on u8 (utf-8) or u32 (utf-32)
|
||||||
|
|
||||||
|
Pointer types:
|
||||||
|
*T (pointer to const T)
|
||||||
|
*mut T (pointer to mutable T)
|
||||||
|
**T (pointer to const pointer to const T)
|
||||||
|
*mut *T (pointer to mutable pointer to const T)
|
||||||
|
**mut T (pointer to const pointer to mutable T)
|
||||||
|
*mut *mut T (pointer to mutable pointer to mutable T)
|
||||||
|
|
||||||
|
Array types:
|
||||||
|
T[N] array of N elements of type T (N must be a compile-time value)
|
||||||
|
|
||||||
|
======== LITERALS ========
|
||||||
|
|
||||||
|
Literals:
|
||||||
|
56b -> i8
|
||||||
|
42ub -> u8
|
||||||
|
456s -> i16
|
||||||
|
456us -> u16
|
||||||
|
98765 -> i32
|
||||||
|
98765u -> u32
|
||||||
|
123l -> i64
|
||||||
|
123ul -> u64
|
||||||
|
3.14h -> f16
|
||||||
|
3.14f -> f32
|
||||||
|
3.14 -> f64
|
||||||
|
'a' -> u8
|
||||||
|
'猫'u -> u32
|
||||||
|
|
||||||
|
TODO: string literals? fixed-size arrays? built-in spans?
|
||||||
|
"hello, world" -> utf-8 string
|
||||||
|
"здарова, братки"u -> utf-32 string
|
||||||
|
|
||||||
|
|
||||||
|
======== VARIABLES ========
|
||||||
|
|
||||||
|
Variable declaration:
|
||||||
|
const x = ... compile-time value, type inferred
|
||||||
|
const x: T = ... compile-time value of type T
|
||||||
|
let x = ... immutable value, type inferred
|
||||||
|
let x: T = ... immutable value of type T
|
||||||
|
mut x = ... mutable, ...
|
||||||
|
mut x: T = ...
|
||||||
|
|
||||||
|
Array declaration:
|
||||||
|
let arr: i32[4] = [12, 15, 65, 42]
|
||||||
|
let arr: i32[] = [56, 23] // size inferred as 2
|
||||||
|
let arr = [2, 5, 6] // size and type inferred as i32[3]
|
||||||
|
|
||||||
|
Variables must always be initialized. (TODO: really? What about arrays?)
|
||||||
|
Const variables must be initialized with a const expression (any expression that doesn't include non-const values).
|
||||||
|
|
||||||
|
======== OPERATORS ========
|
||||||
|
|
||||||
|
Logical (only bool type):
|
||||||
|
!x
|
||||||
|
x & y
|
||||||
|
x | y
|
||||||
|
x ^ y
|
||||||
|
|
||||||
|
Equality (all built-in types, only same type):
|
||||||
|
x == y
|
||||||
|
x != y
|
||||||
|
|
||||||
|
Comparison (all built-in types, only same type):
|
||||||
|
x < y
|
||||||
|
x > y
|
||||||
|
x <= y
|
||||||
|
x >= y
|
||||||
|
|
||||||
|
Bitwise (integer types, only same type):
|
||||||
|
!x
|
||||||
|
x & y
|
||||||
|
x | y
|
||||||
|
x ^ y
|
||||||
|
|
||||||
|
Bitwise shift (any pair of integer types):
|
||||||
|
x >> y
|
||||||
|
x << y
|
||||||
|
|
||||||
|
Arithmetic (only same integer/floating-point type):
|
||||||
|
-x
|
||||||
|
x + y
|
||||||
|
x * y
|
||||||
|
x / y // in case of integers, rounds down when y>0 when x<0, consistently with %
|
||||||
|
x % y // integer only; mathematical, i.e. always in [0, y-1] when y>0 even when x<0; TODO: what if y<0?
|
||||||
|
|
||||||
|
Pointer arithmetic (any pointer type + any integer type):
|
||||||
|
p + x
|
||||||
|
p - x
|
||||||
|
p - q // returns i64
|
||||||
|
|
||||||
|
Pointer arithmetic works element-wise (like C or C++), i.e. p + n advances by n * sizeof(T) when typeof(p) is *T
|
||||||
|
|
||||||
|
Casting:
|
||||||
|
x as u32 // always explicit, no implicit casts allowed
|
||||||
|
|
||||||
|
Any integer/floating-point types can be cast to each other.
|
||||||
|
Any pointer types can be cast to each other (TODO: alignment? UB or safe fallback?).
|
||||||
|
|
||||||
|
Address:
|
||||||
|
&x // returns *T
|
||||||
|
&mut x // returns *mut T, fails if x is non-mut variable
|
||||||
|
|
||||||
|
Assignment:
|
||||||
|
x = 15 // requires x to be a mut variable
|
||||||
|
*p = 15 // p must be a pointer to mut
|
||||||
|
|
||||||
|
======== STRUCTS ========
|
||||||
|
|
||||||
|
Struct types:
|
||||||
|
struct rect:
|
||||||
|
width: u32
|
||||||
|
height: u32
|
||||||
|
|
||||||
|
Creating a struct value:
|
||||||
|
let x = rect(10u, 20u)
|
||||||
|
let y = rect(width = 10u, height = 20u)
|
||||||
|
|
||||||
|
Struct field access:
|
||||||
|
let r = rect(1u, 2u)
|
||||||
|
let x = r.width
|
||||||
|
let p = &r
|
||||||
|
let y = p.height // field access through pointer is the same
|
||||||
|
|
||||||
|
Function types:
|
||||||
|
(T1, T2, T3) -> i32
|
||||||
|
(T1, T2) -> unit // no return value
|
||||||
|
|
||||||
|
Function declaration (required for e.g. loops in call graph):
|
||||||
|
func foo(x: i32, y: i32) -> i32
|
||||||
|
func bar(x: f32) // same as -> unit
|
||||||
|
|
||||||
|
Function definition:
|
||||||
|
func foo(x: i32, y: i32) -> i32:
|
||||||
|
return x * y
|
||||||
|
|
||||||
|
func bar(x: f32): // deduced return type unit
|
||||||
|
print(x)
|
||||||
|
|
||||||
|
Flow control:
|
||||||
|
if condition:
|
||||||
|
statements
|
||||||
|
else if condition:
|
||||||
|
statements
|
||||||
|
else:
|
||||||
|
statements
|
||||||
|
|
||||||
|
while condition:
|
||||||
|
statements
|
||||||
|
|
||||||
|
TODO: for loops? iterator/range interface?
|
||||||
|
|
||||||
|
======== TYPE OF TYPES ========
|
||||||
|
|
||||||
|
Types are also considered to be values. The keyword `type` denotes the type of all types.
|
||||||
|
I.e. `typeof(16) == i32` and `typeof(i32) == type`. Incidentally, `typeof(type) == type` as well; there are no type kinds or etc.
|
||||||
|
`type` can be used in any place where a type is required (variable types, function arguments, function return value, etc).
|
||||||
|
E.g.
|
||||||
|
func foo(x: type) -> type:
|
||||||
|
return x[4] // type of arrays of 4 elements of type x
|
||||||
|
|
||||||
|
let y: type = u32
|
||||||
|
if foo(y) == u32[4]:
|
||||||
|
do_smth()
|
||||||
|
|
||||||
|
======== CONST EXPRESSIONS ========
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// Auto-upgrading values to compile-time when a function is executed from const-only values?
|
||||||
|
|
||||||
|
======== METAPROGRAMMING ========
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// Functions returning functions/structs
|
||||||
|
// Syntactic sugar for common cases
|
||||||
|
// Figure out: max(a,b) - how to deduce type parameters?
|
||||||
|
// func max(t: type):
|
||||||
|
// return func(x : t, y : t):
|
||||||
|
// if x > y:
|
||||||
|
// return x
|
||||||
|
// else:
|
||||||
|
// return y
|
||||||
|
|
||||||
|
======== MODULES AND IMPORTS ========
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
======== STANDARD LIBRARY ========
|
||||||
|
|
||||||
|
// TODO: containers, memory management, strings?
|
||||||
Loading…
Add table
Reference in a new issue