[General] Syncing code
This commit is contained in:
parent
f653a2a83e
commit
e0a8808c31
12 changed files with 764 additions and 0 deletions
3
.clangd
Normal file
3
.clangd
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
CompileFlags:
|
||||
Remove: [-fdeps-format=*,-fmodules-ts,-fmodule-mapper=* ]
|
||||
CompilationDatabase: "build-debug"
|
||||
0
.gitginore → .gitignore
vendored
0
.gitginore → .gitignore
vendored
16
CMakeLists.txt
Normal file
16
CMakeLists.txt
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
cmake_minimum_required(VERSION 4.0)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
|
||||
|
||||
project(jlx LANGUAGES CXX)
|
||||
|
||||
include(CheckIPOSupported)
|
||||
check_ipo_supported(RESULT JLX_IPO_OK OUTPUT JLX_IPO_MSG)
|
||||
|
||||
if (NOT JLX_IPO_OK)
|
||||
message("No IPO: ${JLX_IPO_MSG}")
|
||||
endif()
|
||||
|
||||
add_subdirectory(libjlx)
|
||||
add_subdirectory(jlx)
|
||||
28
CMakePresets.json
Normal file
28
CMakePresets.json
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"version": 10,
|
||||
"cmakeMinimumRequired": {
|
||||
"major": 4,
|
||||
"minor": 0,
|
||||
"patch": 0
|
||||
},
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "debug",
|
||||
"displayName": "Debug",
|
||||
"binaryDir": "${sourceDir}/build-debug",
|
||||
"generator": "Ninja",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "release",
|
||||
"displayName": "Release",
|
||||
"binaryDir": "${sourceDir}/build-release",
|
||||
"generator": "Ninja",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
9
jlx/CMakeLists.txt
Normal file
9
jlx/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
add_executable(jlx ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp)
|
||||
|
||||
target_compile_options(jlx PRIVATE $<IF:$<CXX_COMPILER_ID:Msvc>,/W4 /WX,-Wall -Wextra -Werror>)
|
||||
target_include_directories(jlx PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
target_link_libraries(jlx PRIVATE libjlx)
|
||||
|
||||
if (JLX_IPO_OK)
|
||||
set_property(TARGET jlx PROPERTY INTERPROCEDURAL_OPTIMIZATION $<BOOL:$<NOT:$<CONFIG:Debug>>>)
|
||||
endif()
|
||||
32
jlx/src/main.cpp
Normal file
32
jlx/src/main.cpp
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
import jlx;
|
||||
|
||||
int main(int, char**) {
|
||||
std::istreambuf_iterator<char> start(std::cin), end;
|
||||
|
||||
std::string data{start, end};
|
||||
|
||||
auto tokenizer = jlx::tokenizer(data);
|
||||
|
||||
std::optional<jlx::token> res = tokenizer.read_token();
|
||||
|
||||
std::vector<jlx::token> tokens;
|
||||
|
||||
auto last = std::string();
|
||||
while(res.has_value()) {
|
||||
const auto& t = res.value();
|
||||
|
||||
tokens.emplace_back(t);
|
||||
|
||||
res = tokenizer.read_token();
|
||||
}
|
||||
|
||||
auto parser = jlx::parser<std::vector<jlx::token>::iterator, std::vector<jlx::token>::iterator>(tokens.begin(), tokens.end());
|
||||
|
||||
auto root = parser.parse();
|
||||
|
||||
return 0;
|
||||
}
|
||||
14
libjlx/CMakeLists.txt
Normal file
14
libjlx/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
add_library(libjlx STATIC)
|
||||
set_target_properties(libjlx PROPERTIES PREFIX "")
|
||||
target_sources(libjlx PUBLIC FILE_SET libjlx_modules TYPE CXX_MODULES FILES
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/modules/main.cppm"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/modules/sourceStream.cppm"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/modules/tokenizer.cppm"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/modules/ast.cppm"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/modules/utils.cppm"
|
||||
)
|
||||
target_compile_options(libjlx PRIVATE $<IF:$<CXX_COMPILER_ID:Msvc>,/W4 /WX,-Wall -Wextra -Werror>)
|
||||
target_include_directories(libjlx PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||
if (JLX_IPO_OK)
|
||||
set_property(TARGET libjlx PROPERTY INTERPROCEDURAL_OPTIMIZATION $<BOOL:$<NOT:$<CONFIG:Debug>>>)
|
||||
endif()
|
||||
223
libjlx/modules/ast.cppm
Normal file
223
libjlx/modules/ast.cppm
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
module;
|
||||
|
||||
#include <stdexcept>
|
||||
#include <format>
|
||||
#include <iterator>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
export module jlx:ast;
|
||||
|
||||
import :tokenizer;
|
||||
|
||||
namespace jlx {
|
||||
export enum ast_type {
|
||||
Root,
|
||||
Expression,
|
||||
Block,
|
||||
FunctionDeclaration,
|
||||
SimpleIdentifier,
|
||||
LiteralValue
|
||||
};
|
||||
|
||||
export template<class T>
|
||||
concept token_iterator = requires() {
|
||||
requires std::same_as<decltype(typename T::value_type), jlx::token>;
|
||||
std::bidirectional_iterator<T>;
|
||||
};
|
||||
|
||||
struct statement {
|
||||
ast_type type;
|
||||
|
||||
statement(ast_type type) : type(type) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
struct root_statement : public statement {
|
||||
root_statement() : statement(Root) {
|
||||
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<statement>> statements;
|
||||
};
|
||||
|
||||
struct expression : public statement {
|
||||
expression() : statement(Expression) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
struct block : public statement {
|
||||
block() : statement(Block) {
|
||||
|
||||
}
|
||||
std::vector<std::unique_ptr<statement>> statements;
|
||||
};
|
||||
|
||||
struct function_parameter {
|
||||
std::string name;
|
||||
std::string type;
|
||||
};
|
||||
|
||||
struct function_declaration : public statement {
|
||||
function_declaration() : statement(FunctionDeclaration) {
|
||||
|
||||
}
|
||||
|
||||
std::string name;
|
||||
std::vector<function_parameter> parameters;
|
||||
std::optional<std::string> return_type;
|
||||
std::unique_ptr<block> body;
|
||||
};
|
||||
|
||||
|
||||
export template<token_iterator T, std::sentinel_for<T> E>
|
||||
class parser {
|
||||
T current;
|
||||
E last;
|
||||
|
||||
inline void fail_invalid_token(const token& t) {
|
||||
throw std::runtime_error(std::format("Invalid token {} at {}:{}", t.content, t.line, t.col).c_str());
|
||||
}
|
||||
|
||||
inline void fail_invalid_eof() {
|
||||
throw std::runtime_error("Unexpected end-of-file");
|
||||
}
|
||||
|
||||
void next() {
|
||||
current++;
|
||||
|
||||
if (current == last) {
|
||||
fail_invalid_eof();
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<block> parse_block() {
|
||||
if (current->type != Punctuation || current->content != "{") {
|
||||
fail_invalid_token(*current);
|
||||
}
|
||||
next();
|
||||
|
||||
while(current->type != Punctuation && current->type != "}") {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<function_declaration> parse_function() {
|
||||
if (current->type != Keyword || current->content != "fun") {
|
||||
fail_invalid_token(*current);
|
||||
}
|
||||
|
||||
next();
|
||||
|
||||
if (current->type != Identifier) {
|
||||
fail_invalid_token(*current);
|
||||
}
|
||||
|
||||
std::string function_name = current->content;
|
||||
|
||||
next();
|
||||
|
||||
if (current->type != Punctuation || current->content != "(") {
|
||||
fail_invalid_token(*current);
|
||||
}
|
||||
|
||||
next();
|
||||
|
||||
std::vector<function_parameter> params;
|
||||
std::optional<std::string> return_type;
|
||||
|
||||
bool first = true;
|
||||
while (current->type != Punctuation && current->content != ")") {
|
||||
if (!first) {
|
||||
if(current->type != Punctuation || current->content != ",") {
|
||||
fail_invalid_token(*current);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
std::string name;
|
||||
|
||||
if (current->type != Identifier) {
|
||||
fail_invalid_token(*current);
|
||||
}
|
||||
|
||||
name = current->content;
|
||||
|
||||
next();
|
||||
|
||||
if (current->type != Punctuation || current->content != ":") {
|
||||
fail_invalid_token(*current);
|
||||
}
|
||||
|
||||
next();
|
||||
|
||||
auto param_type = parse_type(current, last);
|
||||
params.push_back(std::move(name), std::move(param_type));
|
||||
|
||||
next();
|
||||
first = false;
|
||||
}
|
||||
next();
|
||||
|
||||
if (current->type == Punctuation && current->content == ":") {
|
||||
next();
|
||||
|
||||
return_type = parse_type(current, last);
|
||||
next();
|
||||
}
|
||||
|
||||
auto block = parse_block();
|
||||
|
||||
return std::make_unique<function_declaration>(std::move(function_name), std::move(params), std::move(return_type), std::move(block));
|
||||
}
|
||||
|
||||
std::string parse_type(){
|
||||
if (current->type != Identifier) {
|
||||
fail_invalid_token(*current);
|
||||
}
|
||||
|
||||
return current->content;
|
||||
}
|
||||
|
||||
std::unique_ptr<statement> parse_top_level_statement() {
|
||||
if (current == last) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (current->type == token_type::Keyword) {
|
||||
switch(current->content) {
|
||||
case "let":
|
||||
case "var":
|
||||
parse_variable_declaration(current, last);
|
||||
break;
|
||||
case "if":
|
||||
parse_if_statement(current, last);
|
||||
break;
|
||||
case "fun":
|
||||
return parse_function(current, last);
|
||||
}
|
||||
}
|
||||
}
|
||||
public:
|
||||
parser(T current, E last) : current(current), last(last) {
|
||||
|
||||
}
|
||||
|
||||
std::unique_ptr<statement> parse() {
|
||||
|
||||
std::vector<std::unique_ptr<statement>> top_level_statements;
|
||||
while(current != last) {
|
||||
auto s = parse_top_level_statement();
|
||||
if (s == nullptr) {
|
||||
throw std::runtime_error("No statement parsed...");
|
||||
}
|
||||
top_level_statements.push_back(std::move(s));
|
||||
}
|
||||
|
||||
return std::make_unique<root_statement>(top_level_statements);
|
||||
}
|
||||
};
|
||||
}
|
||||
6
libjlx/modules/main.cppm
Normal file
6
libjlx/modules/main.cppm
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
module;
|
||||
|
||||
export module jlx;
|
||||
export import :source_stream;
|
||||
export import :tokenizer;
|
||||
export import :ast;
|
||||
60
libjlx/modules/sourceStream.cppm
Normal file
60
libjlx/modules/sourceStream.cppm
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
module;
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
export module jlx:source_stream;
|
||||
|
||||
namespace jlx {
|
||||
export template<typename CharT>
|
||||
class source_stream {
|
||||
public:
|
||||
using char_traits = std::char_traits<CharT>;
|
||||
using str = std::basic_string<CharT, char_traits>;
|
||||
protected:
|
||||
str::size_type pos = 0;
|
||||
std::size_t line = 1;
|
||||
std::size_t col = 0;
|
||||
const str input;
|
||||
public:
|
||||
source_stream(const str input) : input(std::move(input)){
|
||||
|
||||
}
|
||||
|
||||
std::optional<CharT> next() {
|
||||
if (pos >= input.length()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto ch = input.at(pos++);
|
||||
if(char_traits::to_int_type(ch) == 10) {
|
||||
line += 1;
|
||||
col = 0;
|
||||
} else {
|
||||
col++;
|
||||
}
|
||||
return ch;
|
||||
}
|
||||
|
||||
std::optional<CharT> peek() const {
|
||||
if (pos >= input.length()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return input.at(pos);
|
||||
}
|
||||
|
||||
bool eof() {
|
||||
return pos >= input.length();
|
||||
}
|
||||
|
||||
size_t current_line() const {
|
||||
return line;
|
||||
}
|
||||
|
||||
size_t current_col() const {
|
||||
return col;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
348
libjlx/modules/tokenizer.cppm
Normal file
348
libjlx/modules/tokenizer.cppm
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
module;
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <cctype>
|
||||
#include <sstream>
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
|
||||
export module jlx:tokenizer;
|
||||
import :source_stream;
|
||||
import utils;
|
||||
|
||||
namespace jlx {
|
||||
export enum token_type {
|
||||
Invalid = 0,
|
||||
Punctuation,
|
||||
Number,
|
||||
String,
|
||||
Keyword,
|
||||
Identifier,
|
||||
Operator
|
||||
};
|
||||
|
||||
export constexpr std::string token_type_to_string(token_type t) {
|
||||
switch(t) {
|
||||
case Punctuation:
|
||||
return "Punctuation";
|
||||
case Number:
|
||||
return "Number";
|
||||
case String:
|
||||
return "String";
|
||||
case Keyword:
|
||||
return "Keyword";
|
||||
case Identifier:
|
||||
return "Identifier";
|
||||
case Operator:
|
||||
return "Operator";
|
||||
default:
|
||||
return "Invalid";
|
||||
}
|
||||
}
|
||||
|
||||
export struct token {
|
||||
token_type type;
|
||||
std::string content;
|
||||
std::size_t line;
|
||||
std::size_t col;
|
||||
};
|
||||
|
||||
export constexpr std::string token_to_string(const token& t) {
|
||||
return std::format("{}({})", token_type_to_string(t.type), t.content);
|
||||
}
|
||||
|
||||
export class tokenizer_exception {
|
||||
protected:
|
||||
std::string msg;
|
||||
public:
|
||||
tokenizer_exception(std::string msg, std::size_t line, std::size_t col) : msg(std::format("Tokenizer exception at %d:%d. %s", line, col, msg)) {
|
||||
|
||||
}
|
||||
|
||||
const std::string& what() const {
|
||||
return msg;
|
||||
}
|
||||
};
|
||||
|
||||
export class tokenizer {
|
||||
source_stream<char> source;
|
||||
|
||||
static constexpr std::array<std::string, 5> keywords = {{
|
||||
"if",
|
||||
"else",
|
||||
"fun",
|
||||
//"struct",
|
||||
"let",
|
||||
"var"
|
||||
}};
|
||||
|
||||
static constexpr std::array<char, 7> punctiations = {{
|
||||
'.',
|
||||
'(',
|
||||
'(',
|
||||
'{',
|
||||
'}',
|
||||
':',
|
||||
';'
|
||||
}};
|
||||
|
||||
static constexpr std::array<std::string, 12> operators = {{
|
||||
"=",
|
||||
"+",
|
||||
"-",
|
||||
"*",
|
||||
"/",
|
||||
"%",
|
||||
"==",
|
||||
"!=",
|
||||
"<=",
|
||||
">=",
|
||||
">",
|
||||
"<"
|
||||
}};
|
||||
|
||||
void skip_whitespace() {
|
||||
while(!source.eof()) {
|
||||
auto ch = source.peek();
|
||||
if (!ch.has_value() || !std::isspace(static_cast<unsigned int>(ch.value()))) {
|
||||
return;
|
||||
}
|
||||
|
||||
source.next();
|
||||
}
|
||||
}
|
||||
|
||||
token read_string_token() {
|
||||
auto start_line = source.current_line();
|
||||
auto start_col = source.current_col();
|
||||
source.next();
|
||||
bool escape = false;
|
||||
std::stringstream buffer;
|
||||
while(!source.eof()) {
|
||||
auto ch = source.next();
|
||||
if (!ch.has_value() || (!escape && ch.value() == '"')) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto val = ch.value();
|
||||
|
||||
if (val == '\n') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (escape) {
|
||||
switch(val) {
|
||||
case '"':
|
||||
buffer.put('"');
|
||||
break;
|
||||
case '\\':
|
||||
buffer.put('\\');
|
||||
break;
|
||||
case 'n':
|
||||
buffer.put('\n');
|
||||
break;
|
||||
default:
|
||||
throw tokenizer_exception("Invalid escape sequance ", source.current_line(), source.current_col());
|
||||
}
|
||||
escape = false;
|
||||
} else if (val == '\\') {
|
||||
escape = true;
|
||||
} else {
|
||||
buffer.put(val);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
token_type::String,
|
||||
buffer.str(),
|
||||
start_line,
|
||||
start_col
|
||||
};
|
||||
}
|
||||
|
||||
std::optional<token> read_decimal_token() {
|
||||
auto res = source.peek();
|
||||
|
||||
if (!res.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::stringstream buffer;
|
||||
std::size_t start_line = source.current_line();
|
||||
std::size_t start_col = source.current_col();
|
||||
bool found_period = false;
|
||||
|
||||
while(res.has_value() && (std::isdigit(static_cast<unsigned char>(res.value())) || res.value() == '.')) {
|
||||
auto val = res.value();
|
||||
if (val == '.') {
|
||||
if (found_period) {
|
||||
throw tokenizer_exception("Too many periods in numeric value", source.current_line(), source.current_col());
|
||||
} else {
|
||||
found_period = true;
|
||||
}
|
||||
}
|
||||
buffer.put(val);
|
||||
source.next();
|
||||
res = source.peek();
|
||||
}
|
||||
|
||||
return token {
|
||||
token_type::Number,
|
||||
buffer.str(),
|
||||
start_line,
|
||||
start_col
|
||||
};
|
||||
}
|
||||
|
||||
constexpr bool is_valid_identifier_start(char ch) {
|
||||
return ch == '_' || isletter(ch);
|
||||
}
|
||||
|
||||
std::optional<token> read_identifier() {
|
||||
std::stringstream buffer;
|
||||
|
||||
auto start_line = source.current_line();
|
||||
auto start_col = source.current_col();
|
||||
|
||||
while(!source.eof()) {
|
||||
auto res = source.peek();
|
||||
|
||||
if (!res.has_value()) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto val = res.value();
|
||||
|
||||
if (val != '_' && !isletter(val) && !std::isdigit(static_cast<unsigned char>(val))) {
|
||||
break;
|
||||
}
|
||||
|
||||
buffer.put(val);
|
||||
source.next();
|
||||
}
|
||||
|
||||
auto word = buffer.str();
|
||||
|
||||
if (std::find(keywords.begin(), keywords.end(), word) != keywords.end()) {
|
||||
return token {
|
||||
token_type::Keyword,
|
||||
word,
|
||||
start_line,
|
||||
start_col
|
||||
};
|
||||
} else {
|
||||
return token {
|
||||
token_type::Identifier,
|
||||
word,
|
||||
start_line,
|
||||
start_col
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<token> read_punctiation_token() {
|
||||
auto res = source.peek();
|
||||
|
||||
if (!res.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto val = res.value();
|
||||
|
||||
if (std::find(punctiations.begin(), punctiations.end(), val) != punctiations.end()) {
|
||||
auto line = source.current_line();
|
||||
auto col = source.current_col();
|
||||
source.next();
|
||||
return token {
|
||||
token_type::Punctuation,
|
||||
std::string() + val,
|
||||
line,
|
||||
col
|
||||
};
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<token> read_operator_token() {
|
||||
std::stringstream buffer;
|
||||
|
||||
auto line = source.current_line();
|
||||
auto col = source.current_col();
|
||||
while(!source.eof()) {
|
||||
auto res = source.peek();
|
||||
|
||||
if (!res.has_value()) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto val = res.value();
|
||||
|
||||
if (!is_valid_character_from_set(operators, val)) {
|
||||
break;
|
||||
}
|
||||
|
||||
buffer.put(val);
|
||||
source.next();
|
||||
}
|
||||
|
||||
|
||||
auto word = buffer.str();
|
||||
|
||||
if (std::find(operators.begin(), operators.end(), word) != operators.end()) {
|
||||
return token {
|
||||
token_type::Operator,
|
||||
word,
|
||||
line,
|
||||
col
|
||||
};
|
||||
}
|
||||
|
||||
throw tokenizer_exception(std::format("Unknown operator '%s'", word), line, col);
|
||||
}
|
||||
|
||||
public:
|
||||
tokenizer(std::string source) : source(std::move(source)) {
|
||||
|
||||
}
|
||||
|
||||
std::optional<token> read_token() {
|
||||
skip_whitespace();
|
||||
if (source.eof()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto result = source.peek();
|
||||
if (!result.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto val = result.value();
|
||||
|
||||
if (val == '"') {
|
||||
return read_string_token();
|
||||
}
|
||||
|
||||
if (std::isdigit(static_cast<unsigned char>(val))) {
|
||||
return read_decimal_token();
|
||||
}
|
||||
|
||||
if (is_valid_identifier_start(val)) {
|
||||
return read_identifier();
|
||||
}
|
||||
|
||||
auto punctiation_res = read_punctiation_token();
|
||||
if (punctiation_res.has_value()) {
|
||||
return punctiation_res;
|
||||
}
|
||||
|
||||
auto op_res = read_operator_token();
|
||||
if (op_res.has_value()) {
|
||||
return op_res;
|
||||
}
|
||||
|
||||
throw tokenizer_exception(std::format("Unknown character '%c'", val), source.current_line(), source.current_col());
|
||||
}
|
||||
};
|
||||
}
|
||||
25
libjlx/modules/utils.cppm
Normal file
25
libjlx/modules/utils.cppm
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
module;
|
||||
|
||||
#include <cstddef>
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <initializer_list>
|
||||
|
||||
export module utils;
|
||||
|
||||
export constexpr bool isletter(char ch) {
|
||||
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');
|
||||
}
|
||||
|
||||
|
||||
export template<std::size_t N>
|
||||
constexpr bool is_valid_character_from_set(std::array<std::string, N> haystack, char needle) {
|
||||
for(auto& a : haystack) {
|
||||
for(auto& c : a) {
|
||||
if (c == needle) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue