353 lines
11 KiB
C++
353 lines
11 KiB
C++
// AUTOGENERATED COPYRIGHT HEADER START
|
|
// Copyright (C) 2017-2025 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
|
|
// AUTOGENERATED COPYRIGHT HEADER END
|
|
#include "ast.hpp"
|
|
#include "util.hpp"
|
|
|
|
blitz::ast::variable::~variable()
|
|
{
|
|
/* Variable Parsing
|
|
*
|
|
* Declaration:
|
|
* - 8bit Signed Integer Variable
|
|
* Variable:Byte
|
|
* Variable:Int8
|
|
* - 8bit Unsigned Integer Variable
|
|
* Variable:UByte
|
|
* Variable:UInt8
|
|
* - 16bit Signed Integer Variable
|
|
* Variable:Short
|
|
* Variable:Int16
|
|
* - 16bit Unsigned Integer Variable
|
|
* Variable:UShort
|
|
* Variable:UInt16
|
|
* - 32bit Signed Integer Variable
|
|
* Variable
|
|
* Variable%
|
|
* Variable:Int
|
|
* Variable:Int32
|
|
* - 32bit Unsigned Integer Variable
|
|
* Variable:UInt
|
|
* Variable:UInt32
|
|
* - 64bit Signed Integer Variable
|
|
* Variable%%
|
|
* Variable:Long
|
|
* Variable:Int64
|
|
* - 64bit Unsigned Integer Variable
|
|
* Variable:ULong
|
|
* Variable:UInt64
|
|
* - 32bit Real Variable
|
|
* Variable#
|
|
* Variable:Float
|
|
* Variable:Float32
|
|
* Variable:Real
|
|
* Variable:Real32
|
|
* - 64bit Real Variable
|
|
* Variable##
|
|
* Variable:Double
|
|
* Variable:Float64
|
|
* Variable:Real64
|
|
* - UTF-8 String Variable
|
|
* Variable$
|
|
* Variable:String
|
|
* - Struct Variable
|
|
* Variable.StructName
|
|
* Variable:StructName
|
|
*
|
|
* Access:
|
|
* - Struct Access:
|
|
* Variable\Key
|
|
* - Array Access:
|
|
* Variable[IntegerIndex]
|
|
* - Dynamic Array Access:
|
|
* Variable(IntegerIndex)
|
|
* - Direct Access:
|
|
* Variable
|
|
*/
|
|
}
|
|
|
|
bool blitz::ast::variable::can_parse(std::shared_ptr<blitz::lexer> lexer)
|
|
{
|
|
return lexer->current().type == blitz::token::variant::TEXT;
|
|
}
|
|
|
|
std::shared_ptr<blitz::ast::node> blitz::ast::variable::try_parse(std::shared_ptr<blitz::lexer> lexer)
|
|
{
|
|
auto file = lexer->file();
|
|
|
|
auto name_tk = lexer->current();
|
|
if (name_tk.type != blitz::token::variant::TEXT) {
|
|
throw blitz::error(file, name_tk.location, name_tk.location, blitz::format("Unexpected %s, expected text.", name_tk.to_string().c_str()));
|
|
}
|
|
|
|
auto node = std::make_shared<blitz::ast::variable>();
|
|
node->tokens.push_back(name_tk);
|
|
node->type = blitz::types::type::UNKNOWN;
|
|
node->name = name_tk.text;
|
|
|
|
// Check if this has a type definition
|
|
auto symbol_tk = lexer->peek();
|
|
if (symbol_tk.type != blitz::token::variant::SYMBOL) {
|
|
return node;
|
|
} else if (symbol_tk.location.second != (name_tk.location.second + name_tk.text.length())) {
|
|
// We only care about these if they're immediately attached to us.
|
|
return node;
|
|
}
|
|
if (symbol_tk.text == ":") {
|
|
// :Type
|
|
node->tokens.push_back(lexer->next()); // Advance to next token.
|
|
auto type_tk = lexer->next();
|
|
if (type_tk != blitz::token::variant::TEXT) {
|
|
throw blitz::error(file, name_tk.location, type_tk.location, blitz::format("Unexpected %s, expected text.", type_tk.to_string().c_str()));
|
|
}
|
|
if (type_tk.location.second != (symbol_tk.location.second + 1)) {
|
|
throw blitz::error(file, name_tk.location, type_tk.location, blitz::format("Unexpected white space, expected text."));
|
|
}
|
|
|
|
auto type = blitz::types::from_string(type_tk.text);
|
|
if (type == blitz::types::type::UNKNOWN) {
|
|
throw blitz::error(file, name_tk.location, type_tk.location, blitz::format("Unexpected %s, expected built-in type name.", type_tk.text.c_str()));
|
|
}
|
|
node->tokens.push_back(type_tk);
|
|
|
|
node->type = type;
|
|
} else if (symbol_tk.text == ".") {
|
|
// .Struct
|
|
node->tokens.push_back(lexer->next()); // Advance to next token.
|
|
auto type_tk = lexer->next();
|
|
if (type_tk != blitz::token::variant::TEXT) {
|
|
throw blitz::error(file, name_tk.location, type_tk.location, blitz::format("Unexpected %s, expected text.", type_tk.to_string().c_str()));
|
|
}
|
|
if (type_tk.location.second != (symbol_tk.location.second + 1)) {
|
|
throw blitz::error(file, name_tk.location, type_tk.location, blitz::format("Unexpected white space, expected text."));
|
|
}
|
|
node->tokens.push_back(type_tk);
|
|
|
|
node->type = blitz::types::type::STRUCT;
|
|
node->struct_name = type_tk.text;
|
|
} else if (symbol_tk.text == "%") {
|
|
// Int32
|
|
node->tokens.push_back(lexer->next()); // Advance to next token.
|
|
node->type = blitz::types::type::INT32;
|
|
} else if (symbol_tk.text == "#") {
|
|
// Float
|
|
node->tokens.push_back(lexer->next()); // Advance to next token.
|
|
node->type = blitz::types::type::FLOAT32;
|
|
} else if (symbol_tk.text == "$") {
|
|
// String
|
|
node->tokens.push_back(lexer->next()); // Advance to next token.
|
|
node->type = blitz::types::type::STRING;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
blitz::ast::value::~value() {}
|
|
|
|
bool blitz::ast::value::can_parse(std::shared_ptr<blitz::lexer> lexer)
|
|
{
|
|
auto tk = lexer->current();
|
|
switch (tk.type) {
|
|
case blitz::token::variant::STRING:
|
|
case blitz::token::variant::REAL:
|
|
case blitz::token::variant::INTEGER:
|
|
return true;
|
|
case blitz::token::variant::TEXT: {
|
|
// We can only parse True, False, Null
|
|
std::string text = tk.text;
|
|
std::transform(text.cbegin(), text.cend(), text.begin(), blitz::utility::utf8_safe_tolower);
|
|
if (text == "false") {
|
|
return true;
|
|
} else if (text == "true") {
|
|
return true;
|
|
} else if (text == "null") {
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
std::shared_ptr<blitz::ast::node> blitz::ast::value::try_parse(std::shared_ptr<blitz::lexer> lexer)
|
|
{
|
|
auto file = lexer->file();
|
|
auto tk = lexer->current();
|
|
|
|
auto node = std::make_shared<blitz::ast::value>();
|
|
node->type = variant::UNKNOWN;
|
|
|
|
if (tk.type == blitz::token::variant::STRING) {
|
|
node->type = variant::STRING;
|
|
node->text = tk.text;
|
|
return node;
|
|
} else if (tk.type == blitz::token::variant::REAL) {
|
|
node->type = variant::REAL;
|
|
node->number.f = strtod(tk.text.c_str(), nullptr);
|
|
if (errno == ERANGE) {
|
|
throw blitz::error(file, tk.location, tk.location, blitz::format("Real '%s' is not representable on this system.", tk.text.c_str()));
|
|
}
|
|
} else if (tk.type == blitz::token::variant::INTEGER) {
|
|
// Figure out which base this integer is in (and where it starts).
|
|
int base = 10;
|
|
const char* text = tk.text.c_str();
|
|
if ((tk.text.length() > 1) && (text[0] == '0')) {
|
|
if (text[1] == 'x') { // Base 16
|
|
base = 16;
|
|
text = text += 2;
|
|
} else if (text[1] == 'b') { // Base 2
|
|
base = 2;
|
|
text = text += 2;
|
|
} else if (isdigit(text[1])) {
|
|
base = 8;
|
|
text = text += 1;
|
|
}
|
|
}
|
|
|
|
if (tk.text[tk.text.length() - 1] == 'u') {
|
|
// User wants this to be unsigned.
|
|
node->type = variant::UNSIGNED_INTEGER;
|
|
node->number.ui = strtoull(text, nullptr, base);
|
|
if (errno == ERANGE) {
|
|
throw blitz::error(file, tk.location, tk.location, blitz::format("Integer '%s' is not representable on this system.", tk.text.c_str()));
|
|
}
|
|
} else {
|
|
// Try and figure out if it is unsigned.
|
|
node->number.i = strtoll(text, nullptr, base);
|
|
if (errno == ERANGE) {
|
|
node->type = variant::UNSIGNED_INTEGER;
|
|
node->number.ui = strtoull(text, nullptr, base);
|
|
if (errno == ERANGE) {
|
|
throw blitz::error(file, tk.location, tk.location, blitz::format("Integer '%s' is not representable on this system.", tk.text.c_str()));
|
|
}
|
|
} else {
|
|
node->type = variant::INTEGER;
|
|
}
|
|
}
|
|
} else if (tk.type == blitz::token::variant::TEXT) {
|
|
std::string text = tk.text;
|
|
std::transform(text.cbegin(), text.cend(), text.begin(), blitz::utility::utf8_safe_tolower);
|
|
if (text == "false") {
|
|
node->type = variant::BOOL;
|
|
node->number.b = false;
|
|
} else if (text == "true") {
|
|
node->type = variant::BOOL;
|
|
node->number.b = true;
|
|
} else if (text == "null") {
|
|
node->type = variant::NULL;
|
|
node->number.ui = 0;
|
|
}
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
blitz::ast::declare::~declare()
|
|
{
|
|
/* Variable Declaration
|
|
*
|
|
* Examples:
|
|
* - Local myVar1, myVar2%, myVar3 = "Help", myVar4$ = "Me"
|
|
* - Global myVar2g# = 3.147
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
}
|
|
|
|
bool blitz::ast::declare::can_parse(std::shared_ptr<blitz::lexer> lexer)
|
|
{
|
|
auto tk = lexer->current();
|
|
if (tk != blitz::token::variant::TEXT) {
|
|
return false;
|
|
}
|
|
|
|
std::string text = tk.text;
|
|
std::transform(text.cbegin(), text.cend(), text.begin(), blitz::utility::utf8_safe_tolower);
|
|
if (text == "local") {
|
|
return true;
|
|
} else if (text == "global") {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
std::shared_ptr<blitz::ast::node> blitz::ast::declare::try_parse(std::shared_ptr<blitz::lexer> lexer)
|
|
{
|
|
bool is_global;
|
|
|
|
auto file = lexer->file();
|
|
auto tk = lexer->current();
|
|
if (tk != blitz::token::variant::TEXT) {
|
|
throw blitz::error(file, tk.location, tk.location, blitz::format("Unexpected %s, expected text.", tk.to_string().c_str()));
|
|
}
|
|
|
|
// Check if this is Local or Global.
|
|
std::string text = tk.text;
|
|
std::transform(text.cbegin(), text.cend(), text.begin(), blitz::utility::utf8_safe_tolower);
|
|
if (text == "local") {
|
|
is_global = false;
|
|
} else if (text == "global") {
|
|
is_global = true;
|
|
} else {
|
|
throw blitz::error(file, tk.location, tk.location, blitz::format("Unexpected %s, expected Local or Global.", tk.to_string().c_str()));
|
|
}
|
|
|
|
auto node = std::make_shared<blitz::ast::declare>();
|
|
node->global = is_global;
|
|
|
|
// Local myVar
|
|
// Local myVar, myVar2
|
|
// Local myVar = Expression
|
|
// Local myVar = Expression, myVar2
|
|
// Local myVar = Expression :
|
|
// Local myVar : Local MyVar
|
|
// Local myVar:Int
|
|
// Local myVar.StructType
|
|
|
|
for (tk = lexer->peek(); tk != blitz::token::variant::ENDOFFILE; tk = lexer->peek()) {
|
|
// Declarations require a valid variable name
|
|
if (tk.type != blitz::token::variant::TEXT) {
|
|
throw blitz::error(file, tk.location, tk.location, blitz::format("Expected variable name, got %s.", tk.to_string().c_str()));
|
|
} else if (!blitz::ast::variable::can_parse(lexer)) {
|
|
throw blitz::error(file, tk.location, tk.location, blitz::format("Expected variable name, got %s.", tk.to_string().c_str()));
|
|
}
|
|
|
|
// Advance the lexer and parse the variable declaration.
|
|
tk = lexer->next();
|
|
auto variable_nd = blitz::ast::variable::try_parse(lexer);
|
|
node->nodes.push_back(variable_nd);
|
|
|
|
// Peek at what's coming up and decide on behavior.
|
|
tk = lexer->peek();
|
|
if ((tk.type == blitz::token::variant::NEWLINE) || ((tk.type == blitz::token::variant::SYMBOL) && (tk.text == ":")) || (tk.type == blitz::token::variant::ENDOFFILE)) {
|
|
// Nothing useful, break out here.
|
|
break;
|
|
} else if ((tk.type == blitz::token::variant::SYMBOL) && (tk.text == ",")) {
|
|
// Next variable is being declared.
|
|
lexer->next();
|
|
continue;
|
|
} else if ((tk.type == blitz::token::variant::SYMBOL) && (tk.text == "=")) {
|
|
// Assignment, not implemented yet. Skip until next valid symbol.
|
|
lexer->next();
|
|
do {
|
|
if ((tk.type == blitz::token::variant::SYMBOL) && (tk.text == ",")) {
|
|
// Next variable is being declared.
|
|
break;
|
|
} else if ((tk.type == blitz::token::variant::NEWLINE) || ((tk.type == blitz::token::variant::SYMBOL) && (tk.text == ":"))) {
|
|
return node;
|
|
} else if (tk.type == blitz::token::variant::ENDOFFILE) {
|
|
return node;
|
|
}
|
|
tk = lexer->next();
|
|
} while (true);
|
|
}
|
|
}
|
|
|
|
return node;
|
|
}
|