ChaiScript/include/chaiscript/language/chaiscript_parser.hpp
leftibot bb06919061
Fix #677: Add strong typedefs (#680)
* Fix #677: Add strong typedefs via 'using Type = BaseType' syntax

Strong typedefs create distinct types backed by Dynamic_Object, so
'using Meters = int' makes Meters a type that is not interchangeable
with int or other typedefs of int. The constructor Meters(val) wraps
the base value, and function dispatch enforces the type distinction.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Address review: add to_underlying function for strong typedefs

Registers a to_underlying() function for each strong typedef that
returns the wrapped base value from the Dynamic_Object's __value attr.

Requested by @lefticus in PR #680 review.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Address review: expose strongly-typed operators for strong typedefs

Register forwarding binary operators at typedef creation time via a
custom Proxy_Function_Base subclass (Strong_Typedef_Binary_Op). Each
operator unwraps __value from both operands, dispatches on the
underlying types, and re-wraps arithmetic results in the typedef.
Comparison operators return the raw bool.

- Arithmetic: +, -, *, /, % → Meters + Meters -> Meters
- Comparison: <, >, <=, >=, ==, != → Meters < Meters -> bool
- Operators that don't exist on the base type error at call time
  (e.g. StrongString * StrongString -> error)
- Users can extend typedefs with their own operations using
  to_underlying() for unwrapping

Tests cover int-based arithmetic, string-based concatenation, string
multiplication error, comparison ops, type safety of results, and
user-defined operator extensions.

Requested by @lefticus in PR #680 review.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Address review: conditionally register operators based on underlying type support

Only register strong typedef operators that actually exist for the
underlying type. Previously all operators were added unconditionally,
causing confusing reflection entries (e.g. * for StrongString) that
would fail at runtime. Now each operator is probed via call_match
against default-constructed base type values before registration.

Also adds bitwise/shift operators (&, |, ^, <<, >>) for types that
support them, and expands test coverage for unsupported operator
rejection.

Requested by @lefticus in PR #680 review.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Address review: register all operators unconditionally and add compound assignment operators

Remove conditional operator registration (op_exists_for_base_type check)
since users could add underlying operators later, and the runtime check was
expensive. Operators that fail on the underlying type now error at call time
instead of being absent. Add compound assignment operators (*=, +=, -=, /=,
%=, <<=, >>=, &=, |=, ^=) via Strong_Typedef_Compound_Assign_Op which
computes the base operation and stores the result back in __value.

Requested by @lefticus in PR #680 review.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Merge upstream/develop into fix/issue-677-add-strong-typedefs

Resolve merge conflicts with ChaiScript:develop. Upstream added
nested namespace support (#675), grammar railroad diagrams (#673),
and WASM exception support (#689). Conflicts in chaiscript_common.hpp,
chaiscript_eval.hpp, and chaiscript_parser.hpp resolved by keeping
both Using and Namespace_Block AST node types.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Address review: add strong typedef documentation to cheatsheet

Add a new "Strong Typedefs" section to the cheatsheet covering:
- Basic usage with `using Type = BaseType` syntax
- Arithmetic and comparison operator forwarding
- String-based strong typedefs
- Accessing the underlying value via to_underlying
- Extending strong typedefs with custom operations

Requested by @lefticus in PR #680 review.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: leftibot <leftibot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 14:48:49 -06:00

3030 lines
106 KiB
C++

// This file is distributed under the BSD License.
// See "license.txt" for details.
// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com)
// Copyright 2009-2018, Jason Turner (jason@emptycrate.com)
// http://www.chaiscript.com
// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
#ifndef CHAISCRIPT_PARSER_HPP_
#define CHAISCRIPT_PARSER_HPP_
#include <cctype>
#include <cstring>
#include <exception>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
#include "../dispatchkit/boxed_value.hpp"
#include "../utility/hash.hpp"
#include "../utility/static_string.hpp"
#include "chaiscript_common.hpp"
#include "chaiscript_optimizer.hpp"
#include "chaiscript_tracer.hpp"
#if defined(CHAISCRIPT_UTF16_UTF32)
#include <codecvt>
#include <locale>
#endif
#if defined(CHAISCRIPT_MSVC) && defined(max) && defined(min)
#define CHAISCRIPT_PUSHED_MIN_MAX
#pragma push_macro("max") // Why Microsoft? why? This is worse than bad
#undef max
#pragma push_macro("min")
#undef min
#endif
namespace chaiscript {
/// \brief Classes and functions used during the parsing process.
namespace parser {
/// \brief Classes and functions internal to the parsing process. Not supported for the end user.
namespace detail {
enum Alphabet {
symbol_alphabet = 0,
keyword_alphabet,
int_alphabet,
float_alphabet,
x_alphabet,
hex_alphabet,
b_alphabet,
bin_alphabet,
id_alphabet,
white_alphabet,
int_suffix_alphabet,
float_suffix_alphabet,
max_alphabet,
lengthof_alphabet = 256
};
// Generic for u16, u32 and wchar
template<typename string_type>
struct Char_Parser_Helper {
// common for all implementations
static std::string u8str_from_ll(long long val) {
using char_type = std::string::value_type;
char_type c[2];
c[1] = char_type(val);
c[0] = char_type(val >> 8);
if (c[0] == 0) {
return std::string(1, c[1]); // size, character
}
return std::string(c, 2); // char buffer, size
}
static string_type str_from_ll(long long val) {
using target_char_type = typename string_type::value_type;
#if defined(CHAISCRIPT_UTF16_UTF32)
// prepare converter
std::wstring_convert<std::codecvt_utf8<target_char_type>, target_char_type> converter;
// convert
return converter.from_bytes(u8str_from_ll(val));
#else
// no conversion available, just put value as character
return string_type(1, target_char_type(val)); // size, character
#endif
}
};
// Specialization for char AKA UTF-8
template<>
struct Char_Parser_Helper<std::string> {
static std::string str_from_ll(long long val) {
// little SFINAE trick to avoid base class
return Char_Parser_Helper<std::true_type>::u8str_from_ll(val);
}
};
} // namespace detail
template<typename Tracer, typename Optimizer, std::size_t Parse_Depth = 512>
class ChaiScript_Parser final : public ChaiScript_Parser_Base {
void *get_tracer_ptr() noexcept override { return &m_tracer; }
std::size_t m_current_parse_depth = 0;
struct Depth_Counter {
static const auto max_depth = Parse_Depth;
Depth_Counter(ChaiScript_Parser *t_parser)
: parser(t_parser) {
++parser->m_current_parse_depth;
if (parser->m_current_parse_depth > max_depth) {
throw exception::eval_error("Maximum parse depth exceeded",
File_Position(parser->m_position.line, parser->m_position.col),
*(parser->m_filename));
}
}
~Depth_Counter() noexcept { --parser->m_current_parse_depth; }
ChaiScript_Parser *parser;
};
template<typename Array2D, typename First, typename Second>
constexpr static void set_alphabet(Array2D &array, const First first, const Second second) noexcept {
auto *first_ptr = &std::get<0>(array) + static_cast<std::size_t>(first);
auto *second_ptr = &std::get<0>(*first_ptr) + static_cast<std::size_t>(second);
*second_ptr = true;
}
constexpr static std::array<std::array<bool, detail::lengthof_alphabet>, detail::max_alphabet> build_alphabet() noexcept {
std::array<std::array<bool, detail::lengthof_alphabet>, detail::max_alphabet> alphabet{};
set_alphabet(alphabet, detail::symbol_alphabet, '?');
set_alphabet(alphabet, detail::symbol_alphabet, '?');
set_alphabet(alphabet, detail::symbol_alphabet, '+');
set_alphabet(alphabet, detail::symbol_alphabet, '-');
set_alphabet(alphabet, detail::symbol_alphabet, '*');
set_alphabet(alphabet, detail::symbol_alphabet, '/');
set_alphabet(alphabet, detail::symbol_alphabet, '|');
set_alphabet(alphabet, detail::symbol_alphabet, '&');
set_alphabet(alphabet, detail::symbol_alphabet, '^');
set_alphabet(alphabet, detail::symbol_alphabet, '=');
set_alphabet(alphabet, detail::symbol_alphabet, '.');
set_alphabet(alphabet, detail::symbol_alphabet, '<');
set_alphabet(alphabet, detail::symbol_alphabet, '>');
for (size_t c = 'a'; c <= 'z'; ++c) {
set_alphabet(alphabet, detail::keyword_alphabet, c);
}
for (size_t c = 'A'; c <= 'Z'; ++c) {
set_alphabet(alphabet, detail::keyword_alphabet, c);
}
for (size_t c = '0'; c <= '9'; ++c) {
set_alphabet(alphabet, detail::keyword_alphabet, c);
}
set_alphabet(alphabet, detail::keyword_alphabet, '_');
for (size_t c = '0'; c <= '9'; ++c) {
set_alphabet(alphabet, detail::int_alphabet, c);
}
for (size_t c = '0'; c <= '9'; ++c) {
set_alphabet(alphabet, detail::float_alphabet, c);
}
set_alphabet(alphabet, detail::float_alphabet, '.');
for (size_t c = '0'; c <= '9'; ++c) {
set_alphabet(alphabet, detail::hex_alphabet, c);
}
for (size_t c = 'a'; c <= 'f'; ++c) {
set_alphabet(alphabet, detail::hex_alphabet, c);
}
for (size_t c = 'A'; c <= 'F'; ++c) {
set_alphabet(alphabet, detail::hex_alphabet, c);
}
set_alphabet(alphabet, detail::x_alphabet, 'x');
set_alphabet(alphabet, detail::x_alphabet, 'X');
for (size_t c = '0'; c <= '1'; ++c) {
set_alphabet(alphabet, detail::bin_alphabet, c);
}
set_alphabet(alphabet, detail::b_alphabet, 'b');
set_alphabet(alphabet, detail::b_alphabet, 'B');
for (size_t c = 'a'; c <= 'z'; ++c) {
set_alphabet(alphabet, detail::id_alphabet, c);
}
for (size_t c = 'A'; c <= 'Z'; ++c) {
set_alphabet(alphabet, detail::id_alphabet, c);
}
set_alphabet(alphabet, detail::id_alphabet, '_');
set_alphabet(alphabet, detail::white_alphabet, ' ');
set_alphabet(alphabet, detail::white_alphabet, '\t');
set_alphabet(alphabet, detail::int_suffix_alphabet, 'l');
set_alphabet(alphabet, detail::int_suffix_alphabet, 'L');
set_alphabet(alphabet, detail::int_suffix_alphabet, 'u');
set_alphabet(alphabet, detail::int_suffix_alphabet, 'U');
set_alphabet(alphabet, detail::float_suffix_alphabet, 'l');
set_alphabet(alphabet, detail::float_suffix_alphabet, 'L');
set_alphabet(alphabet, detail::float_suffix_alphabet, 'f');
set_alphabet(alphabet, detail::float_suffix_alphabet, 'F');
return alphabet;
}
struct Operator_Matches {
using SS = utility::Static_String;
std::array<utility::Static_String, 1> m_0{{SS("?")}};
std::array<utility::Static_String, 1> m_1{{SS("||")}};
std::array<utility::Static_String, 1> m_2{{SS("&&")}};
std::array<utility::Static_String, 1> m_3{{SS("|")}};
std::array<utility::Static_String, 1> m_4{{SS("^")}};
std::array<utility::Static_String, 1> m_5{{SS("&")}};
std::array<utility::Static_String, 2> m_6{{SS("=="), SS("!=")}};
std::array<utility::Static_String, 4> m_7{{SS("<"), SS("<="), SS(">"), SS(">=")}};
std::array<utility::Static_String, 2> m_8{{SS("<<"), SS(">>")}};
// We share precedence here but then separate them later
std::array<utility::Static_String, 2> m_9{{SS("+"), SS("-")}};
std::array<utility::Static_String, 3> m_10{{SS("*"), SS("/"), SS("%")}};
std::array<utility::Static_String, 6> m_11{{SS("++"), SS("--"), SS("-"), SS("+"), SS("!"), SS("~")}};
bool is_match(std::string_view t_str) const noexcept {
constexpr std::array<std::size_t, 12> groups{{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}};
return std::any_of(groups.begin(), groups.end(), [&t_str, this](const std::size_t group) { return is_match(group, t_str); });
}
template<typename Predicate>
bool any_of(const std::size_t t_group, Predicate &&predicate) const {
auto match = [&predicate](const auto &array) { return std::any_of(array.begin(), array.end(), predicate); };
switch (t_group) {
case 0:
return match(m_0);
case 1:
return match(m_1);
case 2:
return match(m_2);
case 3:
return match(m_3);
case 4:
return match(m_4);
case 5:
return match(m_5);
case 6:
return match(m_6);
case 7:
return match(m_7);
case 8:
return match(m_8);
case 9:
return match(m_9);
case 10:
return match(m_10);
case 11:
return match(m_11);
default:
return false;
}
}
constexpr bool is_match(const std::size_t t_group, std::string_view t_str) const noexcept {
auto match = [&t_str](const auto &array) {
return std::any_of(array.begin(), array.end(), [&t_str](const auto &v) { return v == t_str; });
};
switch (t_group) {
case 0:
return match(m_0);
case 1:
return match(m_1);
case 2:
return match(m_2);
case 3:
return match(m_3);
case 4:
return match(m_4);
case 5:
return match(m_5);
case 6:
return match(m_6);
case 7:
return match(m_7);
case 8:
return match(m_8);
case 9:
return match(m_9);
case 10:
return match(m_10);
case 11:
return match(m_11);
default:
return false;
}
}
};
constexpr static std::array<Operator_Precedence, 12> create_operators() noexcept {
std::array<Operator_Precedence, 12> operators = {{Operator_Precedence::Ternary_Cond,
Operator_Precedence::Logical_Or,
Operator_Precedence::Logical_And,
Operator_Precedence::Bitwise_Or,
Operator_Precedence::Bitwise_Xor,
Operator_Precedence::Bitwise_And,
Operator_Precedence::Equality,
Operator_Precedence::Comparison,
Operator_Precedence::Shift,
Operator_Precedence::Addition,
Operator_Precedence::Multiplication,
Operator_Precedence::Prefix}};
return operators;
}
constexpr static utility::Static_String m_multiline_comment_end{"*/"};
constexpr static utility::Static_String m_multiline_comment_begin{"/*"};
constexpr static utility::Static_String m_singleline_comment{"//"};
constexpr static utility::Static_String m_annotation{"#"};
constexpr static utility::Static_String m_cr_lf{"\r\n"};
constexpr static auto m_operators = create_operators();
std::shared_ptr<std::string> m_filename;
std::vector<eval::AST_Node_Impl_Ptr<Tracer>> m_match_stack;
struct Position {
constexpr Position() = default;
constexpr Position(const char *t_pos, const char *t_end) noexcept
: line(1)
, col(1)
, m_pos(t_pos)
, m_end(t_end)
, m_last_col(1) {
}
static std::string_view str(const Position &t_begin, const Position &t_end) noexcept {
if (t_begin.m_pos != nullptr && t_end.m_pos != nullptr) {
return std::string_view(t_begin.m_pos, std::size_t(std::distance(t_begin.m_pos, t_end.m_pos)));
} else {
return {};
}
}
constexpr Position &operator++() noexcept {
if (m_pos != m_end) {
if (*m_pos == '\n') {
++line;
m_last_col = col;
col = 1;
} else {
++col;
}
++m_pos;
}
return *this;
}
constexpr Position &operator--() noexcept {
--m_pos;
if (*m_pos == '\n') {
--line;
col = m_last_col;
} else {
--col;
}
return *this;
}
constexpr Position &operator+=(size_t t_distance) noexcept {
*this = (*this) + t_distance;
return *this;
}
constexpr Position operator+(size_t t_distance) const noexcept {
Position ret(*this);
for (size_t i = 0; i < t_distance; ++i) {
++ret;
}
return ret;
}
constexpr Position &operator-=(size_t t_distance) noexcept {
*this = (*this) - t_distance;
return *this;
}
constexpr Position operator-(size_t t_distance) const noexcept {
Position ret(*this);
for (size_t i = 0; i < t_distance; ++i) {
--ret;
}
return ret;
}
constexpr bool operator==(const Position &t_rhs) const noexcept { return m_pos == t_rhs.m_pos; }
constexpr bool operator!=(const Position &t_rhs) const noexcept { return m_pos != t_rhs.m_pos; }
constexpr bool has_more() const noexcept { return m_pos != m_end; }
constexpr size_t remaining() const noexcept { return static_cast<size_t>(m_end - m_pos); }
constexpr const char &operator*() const noexcept {
if (m_pos == m_end) {
return ""[0];
} else {
return *m_pos;
}
}
int line = -1;
int col = -1;
private:
const char *m_pos = nullptr;
const char *m_end = nullptr;
int m_last_col = -1;
};
Position m_position;
Tracer m_tracer;
Optimizer m_optimizer;
void validate_object_name(std::string_view name) const {
if (!Name_Validator::valid_object_name(name)) {
throw exception::eval_error("Invalid Object Name: " + std::string(name), File_Position(m_position.line, m_position.col), *m_filename);
}
}
public:
explicit ChaiScript_Parser(Tracer tracer = Tracer(), Optimizer optimizer = Optimizer())
: m_tracer(std::move(tracer))
, m_optimizer(std::move(optimizer)) {
m_match_stack.reserve(2);
}
Tracer &get_tracer() noexcept { return m_tracer; }
Optimizer &get_optimizer() noexcept { return m_optimizer; }
ChaiScript_Parser(const ChaiScript_Parser &) = delete;
ChaiScript_Parser &operator=(const ChaiScript_Parser &) = delete;
ChaiScript_Parser(ChaiScript_Parser &&) = default;
ChaiScript_Parser &operator=(ChaiScript_Parser &&) = delete;
constexpr static auto m_alphabet = build_alphabet();
constexpr static Operator_Matches m_operator_matches{};
/// test a char in an m_alphabet
constexpr bool char_in_alphabet(char c, detail::Alphabet a) const noexcept { return m_alphabet[a][static_cast<uint8_t>(c)]; }
/// Prints the parsed ast_nodes as a tree
void debug_print(const AST_Node &t, std::string prepend = "") const override {
std::cout << prepend << "(" << ast_node_type_to_string(t.identifier) << ") " << t.text << " : " << t.start().line << ", "
<< t.start().column << '\n';
for (const auto &node : t.get_children()) {
debug_print(node.get(), prepend + " ");
}
}
/// Helper function that collects ast_nodes from a starting position to the top of the stack into a new AST node
template<typename NodeType>
void build_match(size_t t_match_start, std::string t_text = "") {
bool is_deep = false;
Parse_Location filepos = [&]() -> Parse_Location {
// so we want to take everything to the right of this and make them children
if (t_match_start != m_match_stack.size()) {
is_deep = true;
return Parse_Location(m_filename,
m_match_stack[t_match_start]->location.start.line,
m_match_stack[t_match_start]->location.start.column,
m_position.line,
m_position.col);
} else {
return Parse_Location(m_filename, m_position.line, m_position.col, m_position.line, m_position.col);
}
}();
std::vector<eval::AST_Node_Impl_Ptr<Tracer>> new_children;
if (is_deep) {
new_children.assign(std::make_move_iterator(m_match_stack.begin() + static_cast<int>(t_match_start)),
std::make_move_iterator(m_match_stack.end()));
m_match_stack.erase(m_match_stack.begin() + static_cast<int>(t_match_start), m_match_stack.end());
}
/// \todo fix the fact that a successful match that captured no ast_nodes doesn't have any real start position
m_match_stack.push_back(
m_optimizer.optimize(chaiscript::make_unique<chaiscript::eval::AST_Node_Impl<Tracer>, NodeType>(std::move(t_text),
std::move(filepos),
std::move(new_children))));
}
/// Reads a symbol group from input if it matches the parameter, without skipping initial whitespace
inline auto Symbol_(const utility::Static_String &sym) noexcept {
const auto len = sym.size();
if (m_position.remaining() >= len) {
const char *file_pos = &(*m_position);
for (size_t pos = 0; pos < len; ++pos) {
if (sym.c_str()[pos] != file_pos[pos]) {
return false;
}
}
m_position += len;
return true;
}
return false;
}
/// Skips any multi-line or single-line comment
bool SkipComment() {
if (Symbol_(m_multiline_comment_begin)) {
while (m_position.has_more()) {
if (Symbol_(m_multiline_comment_end)) {
break;
} else if (!Eol_()) {
++m_position;
}
}
return true;
} else if (Symbol_(m_singleline_comment)) {
while (m_position.has_more()) {
if (Symbol_(m_cr_lf)) {
m_position -= 2;
break;
} else if (Char_('\n')) {
--m_position;
break;
} else {
++m_position;
}
}
return true;
} else if (Symbol_(m_annotation)) {
while (m_position.has_more()) {
if (Symbol_(m_cr_lf)) {
m_position -= 2;
break;
} else if (Char_('\n')) {
--m_position;
break;
} else {
++m_position;
}
}
return true;
}
return false;
}
/// Skips ChaiScript whitespace, which means space and tab, but not cr/lf
/// jespada: Modified SkipWS to skip optionally CR ('\n') and/or LF+CR ("\r\n")
/// AlekMosingiewicz: Added exception when illegal character detected
bool SkipWS(bool skip_cr = false) {
bool retval = false;
while (m_position.has_more()) {
if (static_cast<unsigned char>(*m_position) > 0x7e) {
throw exception::eval_error("Illegal character", File_Position(m_position.line, m_position.col), *m_filename);
}
auto end_line = (*m_position != 0) && ((*m_position == '\n') || (*m_position == '\r' && *(m_position + 1) == '\n'));
if (char_in_alphabet(*m_position, detail::white_alphabet) || (skip_cr && end_line)) {
if (end_line) {
if (*m_position == '\r') {
// discards lf
++m_position;
}
}
++m_position;
retval = true;
} else if (SkipComment()) {
retval = true;
} else {
break;
}
}
return retval;
}
/// Reads the optional exponent (scientific notation) and suffix for a Float
bool read_exponent_and_suffix() noexcept {
// Support a form of scientific notation: 1e-5, 35.5E+8, 0.01e19
if (m_position.has_more() && (std::tolower(*m_position) == 'e')) {
++m_position;
if (m_position.has_more() && ((*m_position == '-') || (*m_position == '+'))) {
++m_position;
}
auto exponent_pos = m_position;
while (m_position.has_more() && char_in_alphabet(*m_position, detail::int_alphabet)) {
++m_position;
}
if (m_position == exponent_pos) {
// Require at least one digit after the exponent
return false;
}
}
// Parse optional float suffix
while (m_position.has_more() && char_in_alphabet(*m_position, detail::float_suffix_alphabet)) {
++m_position;
}
return true;
}
/// Reads a floating point value from input, without skipping initial whitespace
bool Float_() noexcept {
if (m_position.has_more() && char_in_alphabet(*m_position, detail::float_alphabet)) {
while (m_position.has_more() && char_in_alphabet(*m_position, detail::int_alphabet)) {
++m_position;
}
if (m_position.has_more() && (std::tolower(*m_position) == 'e')) {
// The exponent is valid even without any decimal in the Float (1e8, 3e-15)
return read_exponent_and_suffix();
} else if (m_position.has_more() && (*m_position == '.')) {
++m_position;
if (m_position.has_more() && char_in_alphabet(*m_position, detail::int_alphabet)) {
while (m_position.has_more() && char_in_alphabet(*m_position, detail::int_alphabet)) {
++m_position;
}
// After any decimal digits, support an optional exponent (3.7e3)
return read_exponent_and_suffix();
} else {
--m_position;
}
}
}
return false;
}
/// Reads a hex value from input, without skipping initial whitespace
bool Hex_() noexcept {
if (m_position.has_more() && (*m_position == '0')) {
++m_position;
if (m_position.has_more() && char_in_alphabet(*m_position, detail::x_alphabet)) {
++m_position;
if (m_position.has_more() && char_in_alphabet(*m_position, detail::hex_alphabet)) {
while (m_position.has_more() && char_in_alphabet(*m_position, detail::hex_alphabet)) {
++m_position;
}
while (m_position.has_more() && char_in_alphabet(*m_position, detail::int_suffix_alphabet)) {
++m_position;
}
return true;
} else {
--m_position;
}
} else {
--m_position;
}
}
return false;
}
/// Reads an integer suffix
void IntSuffix_() {
while (m_position.has_more() && char_in_alphabet(*m_position, detail::int_suffix_alphabet)) {
++m_position;
}
}
/// Reads a binary value from input, without skipping initial whitespace
bool Binary_() {
if (m_position.has_more() && (*m_position == '0')) {
++m_position;
if (m_position.has_more() && char_in_alphabet(*m_position, detail::b_alphabet)) {
++m_position;
if (m_position.has_more() && char_in_alphabet(*m_position, detail::bin_alphabet)) {
while (m_position.has_more() && char_in_alphabet(*m_position, detail::bin_alphabet)) {
++m_position;
}
return true;
} else {
--m_position;
}
} else {
--m_position;
}
}
return false;
}
/// Parses a floating point value and returns a Boxed_Value representation of it
static Boxed_Value buildFloat(std::string_view t_val) {
bool float_ = false;
bool long_ = false;
auto i = t_val.size();
for (; i > 0; --i) {
char val = t_val[i - 1];
if (val == 'f' || val == 'F') {
float_ = true;
} else if (val == 'l' || val == 'L') {
long_ = true;
} else {
break;
}
}
if (float_) {
return const_var(parse_num<float>(t_val.substr(0, i)));
} else if (long_) {
return const_var(parse_num<long double>(t_val.substr(0, i)));
} else {
return const_var(parse_num<double>(t_val.substr(0, i)));
}
}
static Boxed_Value buildInt(const int base, std::string_view t_val, const bool prefixed) {
bool unsigned_ = false;
bool long_ = false;
bool longlong_ = false;
auto i = t_val.size();
for (; i > 0; --i) {
const char val = t_val[i - 1];
if (val == 'u' || val == 'U') {
unsigned_ = true;
} else if (val == 'l' || val == 'L') {
if (long_) {
longlong_ = true;
}
long_ = true;
} else {
break;
}
}
if (prefixed) {
t_val.remove_prefix(2);
}
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsign-compare"
#ifdef CHAISCRIPT_CLANG
#pragma GCC diagnostic ignored "-Wtautological-compare"
#pragma GCC diagnostic ignored "-Wtautological-unsigned-zero-compare"
#pragma GCC diagnostic ignored "-Wtautological-type-limit-compare"
#pragma GCC diagnostic ignored "-Wsign-conversion"
#endif
#endif
try {
/// TODO fix this to use from_chars
auto u = std::stoll(std::string(t_val), nullptr, base);
if (!unsigned_ && !long_ && u >= std::numeric_limits<int>::min() && u <= std::numeric_limits<int>::max()) {
return const_var(static_cast<int>(u));
} else if ((unsigned_ || base != 10) && !long_ && u >= std::numeric_limits<unsigned int>::min()
&& u <= std::numeric_limits<unsigned int>::max()) {
return const_var(static_cast<unsigned int>(u));
} else if (!unsigned_ && !longlong_ && u >= std::numeric_limits<long>::min() && u <= std::numeric_limits<long>::max()) {
return const_var(static_cast<long>(u));
} else if ((unsigned_ || base != 10) && !longlong_ && u >= std::numeric_limits<unsigned long>::min()
&& u <= std::numeric_limits<unsigned long>::max()) {
return const_var(static_cast<unsigned long>(u));
} else if (!unsigned_ && u >= std::numeric_limits<long long>::min() && u <= std::numeric_limits<long long>::max()) {
return const_var(static_cast<long long>(u));
} else {
return const_var(static_cast<unsigned long long>(u));
}
} catch (const std::out_of_range &) {
// too big to be signed
try {
/// TODO fix this to use from_chars
auto u = std::stoull(std::string(t_val), nullptr, base);
if (!longlong_ && u >= std::numeric_limits<unsigned long>::min() && u <= std::numeric_limits<unsigned long>::max()) {
return const_var(static_cast<unsigned long>(u));
} else {
return const_var(static_cast<unsigned long long>(u));
}
} catch (const std::out_of_range &) {
// it's just simply too big
return const_var(std::numeric_limits<long long>::max());
}
}
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
}
template<typename T, typename... Param>
std::unique_ptr<eval::AST_Node_Impl<Tracer>>
make_node(std::string_view t_match, const int t_prev_line, const int t_prev_col, Param &&...param) {
return chaiscript::make_unique<eval::AST_Node_Impl<Tracer>, T>(
std::string(t_match),
Parse_Location(m_filename, t_prev_line, t_prev_col, m_position.line, m_position.col),
std::forward<Param>(param)...);
}
/// Reads a number from the input, detecting if it's an integer or floating point
bool Num() {
SkipWS();
const auto start = m_position;
if (m_position.has_more() && char_in_alphabet(*m_position, detail::float_alphabet)) {
try {
if (Hex_()) {
auto match = Position::str(start, m_position);
auto bv = buildInt(16, match, true);
m_match_stack.emplace_back(make_node<eval::Constant_AST_Node<Tracer>>(match, start.line, start.col, std::move(bv)));
return true;
}
if (Binary_()) {
auto match = Position::str(start, m_position);
auto bv = buildInt(2, match, true);
m_match_stack.push_back(make_node<eval::Constant_AST_Node<Tracer>>(match, start.line, start.col, std::move(bv)));
return true;
}
if (Float_()) {
auto match = Position::str(start, m_position);
auto bv = buildFloat(match);
m_match_stack.push_back(make_node<eval::Constant_AST_Node<Tracer>>(match, start.line, start.col, std::move(bv)));
return true;
} else {
IntSuffix_();
auto match = Position::str(start, m_position);
if (!match.empty() && (match[0] == '0')) {
auto bv = buildInt(8, match, false);
m_match_stack.push_back(make_node<eval::Constant_AST_Node<Tracer>>(match, start.line, start.col, std::move(bv)));
} else if (!match.empty()) {
auto bv = buildInt(10, match, false);
m_match_stack.push_back(make_node<eval::Constant_AST_Node<Tracer>>(match, start.line, start.col, std::move(bv)));
} else {
return false;
}
return true;
}
} catch (const std::invalid_argument &) {
// error parsing number passed in to buildFloat/buildInt
return false;
}
} else {
return false;
}
}
/// Reads an identifier from input which conforms to C's identifier naming conventions, without skipping initial whitespace
bool Id_() {
if (m_position.has_more() && char_in_alphabet(*m_position, detail::id_alphabet)) {
while (m_position.has_more() && char_in_alphabet(*m_position, detail::keyword_alphabet)) {
++m_position;
}
return true;
} else if (m_position.has_more() && (*m_position == '`')) {
++m_position;
const auto start = m_position;
while (m_position.has_more() && (*m_position != '`')) {
if (Eol()) {
throw exception::eval_error("Carriage return in identifier literal",
File_Position(m_position.line, m_position.col),
*m_filename);
} else {
++m_position;
}
}
if (start == m_position) {
throw exception::eval_error("Missing contents of identifier literal", File_Position(m_position.line, m_position.col), *m_filename);
} else if (!m_position.has_more()) {
throw exception::eval_error("Incomplete identifier literal", File_Position(m_position.line, m_position.col), *m_filename);
}
++m_position;
return true;
}
return false;
}
/// Reads (and potentially captures) an identifier from input
bool Id(const bool validate) {
SkipWS();
const auto start = m_position;
if (Id_()) {
auto text = Position::str(start, m_position);
const auto text_hash = utility::hash(text);
if (validate) {
validate_object_name(text);
}
#ifdef CHAISCRIPT_MSVC
#pragma warning(push)
#pragma warning(disable : 4307)
#endif
switch (text_hash) {
case utility::hash("true"): {
m_match_stack.push_back(make_node<eval::Constant_AST_Node<Tracer>>(text, start.line, start.col, const_var(true)));
} break;
case utility::hash("false"): {
m_match_stack.push_back(make_node<eval::Constant_AST_Node<Tracer>>(text, start.line, start.col, const_var(false)));
} break;
case utility::hash("Infinity"): {
m_match_stack.push_back(make_node<eval::Constant_AST_Node<Tracer>>(text,
start.line,
start.col,
const_var(std::numeric_limits<double>::infinity())));
} break;
case utility::hash("NaN"): {
m_match_stack.push_back(make_node<eval::Constant_AST_Node<Tracer>>(text,
start.line,
start.col,
const_var(std::numeric_limits<double>::quiet_NaN())));
} break;
case utility::hash("__LINE__"): {
m_match_stack.push_back(make_node<eval::Constant_AST_Node<Tracer>>(text, start.line, start.col, const_var(start.line)));
} break;
case utility::hash("__FILE__"): {
m_match_stack.push_back(make_node<eval::Constant_AST_Node<Tracer>>(text, start.line, start.col, const_var(m_filename)));
} break;
case utility::hash("__FUNC__"): {
std::string fun_name = "NOT_IN_FUNCTION";
for (size_t idx = m_match_stack.empty() ? 0 : m_match_stack.size() - 1; idx > 0; --idx) {
if (m_match_stack[idx - 1]->identifier == AST_Node_Type::Id
&& m_match_stack[idx - 0]->identifier == AST_Node_Type::Arg_List) {
fun_name = m_match_stack[idx - 1]->text;
}
}
m_match_stack.push_back(make_node<eval::Constant_AST_Node<Tracer>>(text, start.line, start.col, const_var(fun_name)));
} break;
case utility::hash("__CLASS__"): {
std::string fun_name = "NOT_IN_CLASS";
for (size_t idx = m_match_stack.empty() ? 0 : m_match_stack.size() - 1; idx > 1; --idx) {
if (m_match_stack[idx - 2]->identifier == AST_Node_Type::Id && m_match_stack[idx - 1]->identifier == AST_Node_Type::Id
&& m_match_stack[idx - 0]->identifier == AST_Node_Type::Arg_List) {
fun_name = m_match_stack[idx - 2]->text;
}
}
m_match_stack.push_back(
make_node<eval::Constant_AST_Node<Tracer>>(std::move(text), start.line, start.col, const_var(fun_name)));
} break;
case utility::hash("_"): {
m_match_stack.push_back(
make_node<eval::Constant_AST_Node<Tracer>>(std::move(text),
start.line,
start.col,
Boxed_Value(std::make_shared<dispatch::Placeholder_Object>())));
} break;
default: {
auto val = text;
if (*start == '`') {
// 'escaped' literal, like an operator name
val = Position::str(start + 1, m_position - 1);
// val.remove_prefix(1); val.remove_suffix(1);
}
m_match_stack.push_back(make_node<eval::Id_AST_Node<Tracer>>(val, start.line, start.col));
} break;
}
#ifdef CHAISCRIPT_MSVC
#pragma warning(pop)
#endif
return true;
} else {
return false;
}
}
/// Reads an argument from input
bool Arg(const bool t_type_allowed = true) {
const auto prev_stack_top = m_match_stack.size();
SkipWS();
if (!Id(true)) {
return false;
}
SkipWS();
if (t_type_allowed) {
Id(true);
}
build_match<eval::Arg_AST_Node<Tracer>>(prev_stack_top);
return true;
}
/// Reads a quoted string from input, without skipping initial whitespace
bool Quoted_String_() {
if (m_position.has_more() && (*m_position == '\"')) {
char prev_char = *m_position;
++m_position;
int in_interpolation = 0;
bool in_quote = false;
while (m_position.has_more() && ((*m_position != '\"') || (in_interpolation > 0) || (prev_char == '\\'))) {
if (!Eol_()) {
if (prev_char == '$' && *m_position == '{') {
++in_interpolation;
} else if (prev_char != '\\' && *m_position == '"') {
in_quote = !in_quote;
} else if (*m_position == '}' && !in_quote) {
--in_interpolation;
}
if (prev_char == '\\') {
prev_char = 0;
} else {
prev_char = *m_position;
}
++m_position;
}
}
if (m_position.has_more()) {
++m_position;
} else {
throw exception::eval_error("Unclosed quoted string", File_Position(m_position.line, m_position.col), *m_filename);
}
return true;
}
return false;
}
template<typename string_type>
struct Char_Parser {
string_type &match;
using char_type = typename string_type::value_type;
bool is_escaped = false;
bool is_interpolated = false;
bool saw_interpolation_marker = false;
bool is_octal = false;
bool is_hex = false;
std::size_t unicode_size = 0;
const bool interpolation_allowed;
string_type octal_matches;
string_type hex_matches;
Char_Parser(string_type &t_match, const bool t_interpolation_allowed)
: match(t_match)
, interpolation_allowed(t_interpolation_allowed) {
}
Char_Parser &operator=(const Char_Parser &) = delete;
~Char_Parser() {
try {
if (is_octal) {
process_octal();
}
if (is_hex) {
process_hex();
}
if (unicode_size > 0) {
process_unicode();
}
} catch (const std::invalid_argument &) {
} catch (const exception::eval_error &) {
// Something happened with parsing, we'll catch it later?
}
}
void process_hex() {
if (!hex_matches.empty()) {
auto val = stoll(hex_matches, nullptr, 16);
match.push_back(char_type(val));
}
hex_matches.clear();
is_escaped = false;
is_hex = false;
}
void process_octal() {
if (!octal_matches.empty()) {
auto val = stoll(octal_matches, nullptr, 8);
match.push_back(char_type(val));
}
octal_matches.clear();
is_escaped = false;
is_octal = false;
}
void process_unicode() {
const auto ch = static_cast<uint32_t>(std::stoi(hex_matches, nullptr, 16));
const auto match_size = hex_matches.size();
hex_matches.clear();
is_escaped = false;
const auto u_size = unicode_size;
unicode_size = 0;
char buf[4];
if (u_size != match_size) {
throw exception::eval_error("Incomplete unicode escape sequence");
}
if (u_size == 4 && ch >= 0xD800 && ch <= 0xDFFF) {
throw exception::eval_error("Invalid 16 bit universal character");
}
if (ch < 0x80) {
match += static_cast<char>(ch);
} else if (ch < 0x800) {
buf[0] = static_cast<char>(0xC0 | (ch >> 6));
buf[1] = static_cast<char>(0x80 | (ch & 0x3F));
match.append(buf, 2);
} else if (ch < 0x10000) {
buf[0] = static_cast<char>(0xE0 | (ch >> 12));
buf[1] = static_cast<char>(0x80 | ((ch >> 6) & 0x3F));
buf[2] = static_cast<char>(0x80 | (ch & 0x3F));
match.append(buf, 3);
} else if (ch < 0x200000) {
buf[0] = static_cast<char>(0xF0 | (ch >> 18));
buf[1] = static_cast<char>(0x80 | ((ch >> 12) & 0x3F));
buf[2] = static_cast<char>(0x80 | ((ch >> 6) & 0x3F));
buf[3] = static_cast<char>(0x80 | (ch & 0x3F));
match.append(buf, 4);
} else {
// this must be an invalid escape sequence?
throw exception::eval_error("Invalid 32 bit universal character");
}
}
void parse(const char_type t_char, const int line, const int col, const std::string &filename) {
const bool is_octal_char = t_char >= '0' && t_char <= '7';
const bool is_hex_char = (t_char >= '0' && t_char <= '9') || (t_char >= 'a' && t_char <= 'f') || (t_char >= 'A' && t_char <= 'F');
if (is_octal) {
if (is_octal_char) {
octal_matches.push_back(t_char);
if (octal_matches.size() == 3) {
process_octal();
}
return;
} else {
process_octal();
}
} else if (is_hex) {
if (is_hex_char) {
hex_matches.push_back(t_char);
if (hex_matches.size() == 2 * sizeof(char_type)) {
// This rule differs from the C/C++ standard, but ChaiScript
// does not offer the same workaround options, and having
// hexadecimal sequences longer than can fit into the char
// type is undefined behavior anyway.
process_hex();
}
return;
} else {
process_hex();
}
} else if (unicode_size > 0) {
if (is_hex_char) {
hex_matches.push_back(t_char);
if (hex_matches.size() == unicode_size) {
// Format is specified to be 'slash'uABCD
// on collecting from A to D do parsing
process_unicode();
}
return;
} else {
// Not a unicode anymore, try parsing any way
// May be someone used 'slash'uAA only
process_unicode();
}
}
if (t_char == '\\') {
if (is_escaped) {
match.push_back('\\');
is_escaped = false;
} else {
is_escaped = true;
}
} else {
if (is_escaped) {
if (is_octal_char) {
is_octal = true;
octal_matches.push_back(t_char);
} else if (t_char == 'x') {
is_hex = true;
} else if (t_char == 'u') {
unicode_size = 4;
} else if (t_char == 'U') {
unicode_size = 8;
} else {
switch (t_char) {
case ('\''):
match.push_back('\'');
break;
case ('\"'):
match.push_back('\"');
break;
case ('?'):
match.push_back('?');
break;
case ('a'):
match.push_back('\a');
break;
case ('b'):
match.push_back('\b');
break;
case ('f'):
match.push_back('\f');
break;
case ('n'):
match.push_back('\n');
break;
case ('r'):
match.push_back('\r');
break;
case ('t'):
match.push_back('\t');
break;
case ('v'):
match.push_back('\v');
break;
case ('$'):
match.push_back('$');
break;
default:
throw exception::eval_error("Unknown escaped sequence in string", File_Position(line, col), filename);
}
is_escaped = false;
}
} else if (interpolation_allowed && t_char == '$') {
saw_interpolation_marker = true;
} else {
match.push_back(t_char);
}
}
}
};
/// Reads (and potentially captures) a quoted string from input. Translates escaped sequences.
bool Quoted_String() {
Depth_Counter dc{this};
SkipWS();
const auto start = m_position;
if (Quoted_String_()) {
std::string match;
const auto prev_stack_top = m_match_stack.size();
bool is_interpolated = [&]() -> bool {
Char_Parser<std::string> cparser(match, true);
auto s = start + 1, end = m_position - 1;
while (s != end) {
if (cparser.saw_interpolation_marker) {
if (*s == '{') {
// We've found an interpolation point
m_match_stack.push_back(make_node<eval::Constant_AST_Node<Tracer>>(match, start.line, start.col, const_var(match)));
if (cparser.is_interpolated) {
// If we've seen previous interpolation, add on instead of making a new one
build_match<eval::Binary_Operator_AST_Node<Tracer>>(prev_stack_top, "+");
}
// We've finished with the part of the string up to this point, so clear it
match.clear();
std::string eval_match;
++s;
while ((s != end) && (*s != '}')) {
eval_match.push_back(*s);
++s;
}
if (*s == '}') {
cparser.is_interpolated = true;
++s;
const auto tostr_stack_top = m_match_stack.size();
m_match_stack.push_back(make_node<eval::Id_AST_Node<Tracer>>("to_string", start.line, start.col));
const auto ev_stack_top = m_match_stack.size();
try {
m_match_stack.push_back(parse_instr_eval(eval_match));
} catch (const exception::eval_error &e) {
throw exception::eval_error(e.what(), File_Position(start.line, start.col), *m_filename);
}
build_match<eval::Arg_List_AST_Node<Tracer>>(ev_stack_top);
build_match<eval::Fun_Call_AST_Node<Tracer>>(tostr_stack_top);
build_match<eval::Binary_Operator_AST_Node<Tracer>>(prev_stack_top, "+");
} else {
throw exception::eval_error("Unclosed in-string eval", File_Position(start.line, start.col), *m_filename);
}
} else {
match.push_back('$');
}
cparser.saw_interpolation_marker = false;
} else {
cparser.parse(*s, start.line, start.col, *m_filename);
++s;
}
}
if (cparser.saw_interpolation_marker) {
match.push_back('$');
}
return cparser.is_interpolated;
}();
m_match_stack.push_back(make_node<eval::Constant_AST_Node<Tracer>>(match, start.line, start.col, const_var(match)));
if (is_interpolated) {
build_match<eval::Binary_Operator_AST_Node<Tracer>>(prev_stack_top, "+");
}
return true;
} else {
return false;
}
}
/// Reads a raw string from input R"delimiter(content)delimiter", without skipping initial whitespace
bool Raw_String_() {
if (m_position.has_more() && (*m_position == 'R') && m_position.remaining() > 1 && *(m_position + 1) == '"') {
auto s = m_position + 2;
// Read the delimiter (may be empty)
std::string delimiter;
while (s.has_more() && *s != '(' && *s != '\n') {
delimiter.push_back(*s);
++s;
}
if (!s.has_more() || *s != '(') {
return false;
}
// Skip the opening '('
++s;
// Build the closing sequence: )delimiter"
std::string close_seq = ")";
close_seq += delimiter;
close_seq += '"';
// Search for the closing sequence
while (s.has_more()) {
if (*s == close_seq[0]) {
// Check if we match the full closing sequence
auto t = s;
std::size_t i = 0;
while (t.has_more() && i < close_seq.size() && *t == close_seq[i]) {
++t;
++i;
}
if (i == close_seq.size()) {
// Found the closing sequence
m_position = t;
return true;
}
}
if (*s == '\n') {
m_position.col = 1;
}
++s;
}
throw exception::eval_error("Unclosed raw string", File_Position(m_position.line, m_position.col), *m_filename);
}
return false;
}
/// Reads and captures a raw string from input. No escape processing or interpolation.
bool Raw_String() {
Depth_Counter dc{this};
SkipWS();
const auto start = m_position;
if (Raw_String_()) {
// Extract content between R"delimiter( and )delimiter"
auto s = start + 2; // skip R"
// Skip delimiter
std::string delimiter;
while (*s != '(') {
delimiter.push_back(*s);
++s;
}
++s; // skip '('
// Build closing sequence
std::string close_seq = ")";
close_seq += delimiter;
close_seq += '"';
// Extract raw content up to closing sequence
std::string match;
auto end = m_position; // m_position is already past the closing sequence
// Content is from s up to (end - close_seq.size())
// We need to find the closing sequence from s
while (s.has_more()) {
if (*s == close_seq[0]) {
auto t = s;
std::size_t i = 0;
while (t.has_more() && i < close_seq.size() && *t == close_seq[i]) {
++t;
++i;
}
if (i == close_seq.size() && t == end) {
break;
}
}
match.push_back(*s);
++s;
}
m_match_stack.push_back(make_node<eval::Constant_AST_Node<Tracer>>(match, start.line, start.col, const_var(match)));
return true;
}
return false;
}
/// Reads a character group from input, without skipping initial whitespace
bool Single_Quoted_String_() {
bool retval = false;
if (m_position.has_more() && (*m_position == '\'')) {
retval = true;
char prev_char = *m_position;
++m_position;
while (m_position.has_more() && ((*m_position != '\'') || (prev_char == '\\'))) {
if (!Eol_()) {
if (prev_char == '\\') {
prev_char = 0;
} else {
prev_char = *m_position;
}
++m_position;
}
}
if (m_position.has_more()) {
++m_position;
} else {
throw exception::eval_error("Unclosed single-quoted string", File_Position(m_position.line, m_position.col), *m_filename);
}
}
return retval;
}
/// Reads (and potentially captures) a char group from input. Translates escaped sequences.
bool Single_Quoted_String() {
Depth_Counter dc{this};
SkipWS();
const auto start = m_position;
if (Single_Quoted_String_()) {
std::string match;
{
// scope for cparser destructor
Char_Parser<std::string> cparser(match, false);
for (auto s = start + 1, end = m_position - 1; s != end; ++s) {
cparser.parse(*s, start.line, start.col, *m_filename);
}
}
if (match.size() != 1) {
throw exception::eval_error("Single-quoted strings must be 1 character long",
File_Position(m_position.line, m_position.col),
*m_filename);
}
m_match_stack.push_back(make_node<eval::Constant_AST_Node<Tracer>>(match, start.line, start.col, const_var(char(match.at(0)))));
return true;
} else {
return false;
}
}
/// Reads a char from input if it matches the parameter, without skipping initial whitespace
bool Char_(const char c) {
if (m_position.has_more() && (*m_position == c)) {
++m_position;
return true;
} else {
return false;
}
}
/// Reads (and potentially captures) a char from input if it matches the parameter
bool Char(const char t_c) {
Depth_Counter dc{this};
SkipWS();
return Char_(t_c);
}
/// Reads a string from input if it matches the parameter, without skipping initial whitespace
bool Keyword_(const utility::Static_String &t_s) {
const auto len = t_s.size();
if (m_position.remaining() >= len) {
auto tmp = m_position;
for (size_t i = 0; tmp.has_more() && i < len; ++i) {
if (*tmp != t_s.c_str()[i]) {
return false;
}
++tmp;
}
m_position = tmp;
return true;
}
return false;
}
/// Reads (and potentially captures) a string from input if it matches the parameter
bool Keyword(const utility::Static_String &t_s) {
Depth_Counter dc{this};
SkipWS();
const auto start = m_position;
bool retval = Keyword_(t_s);
// ignore substring matches
if (retval && m_position.has_more() && char_in_alphabet(*m_position, detail::keyword_alphabet)) {
m_position = start;
retval = false;
}
return retval;
}
bool is_operator(std::string_view t_s) const noexcept { return m_operator_matches.is_match(t_s); }
/// Reads (and potentially captures) a symbol group from input if it matches the parameter
bool Symbol(const utility::Static_String &t_s, const bool t_disallow_prevention = false) {
Depth_Counter dc{this};
SkipWS();
const auto start = m_position;
bool retval = Symbol_(t_s);
// ignore substring matches
if (retval && m_position.has_more() && (t_disallow_prevention == false) && char_in_alphabet(*m_position, detail::symbol_alphabet)) {
if (*m_position != '=' && is_operator(Position::str(start, m_position)) && !is_operator(Position::str(start, m_position + 1))) {
// don't throw this away, it's a good match and the next is not
} else {
m_position = start;
retval = false;
}
}
return retval;
}
/// Reads an end-of-line group from input, without skipping initial whitespace
bool Eol_(const bool t_eos = false) {
bool retval = false;
if (m_position.has_more() && (Symbol_(m_cr_lf) || Char_('\n'))) {
retval = true;
//++m_position.line;
m_position.col = 1;
} else if (m_position.has_more() && !t_eos && Char_(';')) {
retval = true;
}
return retval;
}
/// Reads until the end of the current statement
bool Eos() {
Depth_Counter dc{this};
SkipWS();
return Eol_(true);
}
/// Reads (and potentially captures) an end-of-line group from input
bool Eol() {
Depth_Counter dc{this};
SkipWS();
return Eol_();
}
/// Reads a comma-separated list of values from input. Id's only, no types allowed
bool Id_Arg_List() {
Depth_Counter dc{this};
SkipWS(true);
bool retval = false;
const auto prev_stack_top = m_match_stack.size();
if (Arg(false)) {
retval = true;
while (Eol()) {
}
while (Char(',')) {
while (Eol()) {
}
if (!Arg(false)) {
throw exception::eval_error("Unexpected value in parameter list", File_Position(m_position.line, m_position.col), *m_filename);
}
}
}
build_match<eval::Arg_List_AST_Node<Tracer>>(prev_stack_top);
SkipWS(true);
return retval;
}
/// Reads a comma-separated list of values from input, for function declarations
bool Decl_Arg_List() {
Depth_Counter dc{this};
SkipWS(true);
bool retval = false;
const auto prev_stack_top = m_match_stack.size();
if (Arg()) {
retval = true;
while (Eol()) {
}
while (Char(',')) {
while (Eol()) {
}
if (!Arg()) {
throw exception::eval_error("Unexpected value in parameter list", File_Position(m_position.line, m_position.col), *m_filename);
}
}
}
build_match<eval::Arg_List_AST_Node<Tracer>>(prev_stack_top);
SkipWS(true);
return retval;
}
/// Reads a comma-separated list of values from input
bool Arg_List() {
Depth_Counter dc{this};
SkipWS(true);
bool retval = false;
const auto prev_stack_top = m_match_stack.size();
if (Equation()) {
retval = true;
while (Eol()) {
}
while (Char(',')) {
while (Eol()) {
}
if (!Equation()) {
throw exception::eval_error("Unexpected value in parameter list", File_Position(m_position.line, m_position.col), *m_filename);
}
}
}
build_match<eval::Arg_List_AST_Node<Tracer>>(prev_stack_top);
SkipWS(true);
return retval;
}
/// Reads possible special container values, including ranges and map_pairs
bool Container_Arg_List() {
Depth_Counter dc{this};
bool retval = false;
SkipWS(true);
const auto prev_stack_top = m_match_stack.size();
if (Value_Range()) {
retval = true;
build_match<eval::Arg_List_AST_Node<Tracer>>(prev_stack_top);
} else if (Map_Pair()) {
retval = true;
while (Eol()) {
}
while (Char(',')) {
while (Eol()) {
}
if (!Map_Pair()) {
throw exception::eval_error("Unexpected value in container", File_Position(m_position.line, m_position.col), *m_filename);
}
}
build_match<eval::Arg_List_AST_Node<Tracer>>(prev_stack_top);
} else if (Operator()) {
retval = true;
while (Eol()) {
}
while (Char(',')) {
while (Eol()) {
}
if (!Operator()) {
throw exception::eval_error("Unexpected value in container", File_Position(m_position.line, m_position.col), *m_filename);
}
}
build_match<eval::Arg_List_AST_Node<Tracer>>(prev_stack_top);
}
SkipWS(true);
return retval;
}
/// Reads a lambda (anonymous function) from input
bool Lambda() {
Depth_Counter dc{this};
bool retval = false;
const auto prev_stack_top = m_match_stack.size();
if (Keyword("fun")) {
retval = true;
if (Char('[')) {
Id_Arg_List();
if (!Char(']')) {
throw exception::eval_error("Incomplete anonymous function bind", File_Position(m_position.line, m_position.col), *m_filename);
}
} else {
// make sure we always have the same number of nodes
build_match<eval::Arg_List_AST_Node<Tracer>>(prev_stack_top);
}
if (Char('(')) {
Decl_Arg_List();
if (!Char(')')) {
throw exception::eval_error("Incomplete anonymous function", File_Position(m_position.line, m_position.col), *m_filename);
}
} else {
throw exception::eval_error("Incomplete anonymous function", File_Position(m_position.line, m_position.col), *m_filename);
}
while (Eol()) {
}
if (!Block()) {
throw exception::eval_error("Incomplete anonymous function", File_Position(m_position.line, m_position.col), *m_filename);
}
build_match<eval::Lambda_AST_Node<Tracer>>(prev_stack_top);
}
return retval;
}
/// Reads a function definition from input
bool Def(const bool t_class_context = false, const std::string &t_class_name = "") {
Depth_Counter dc{this};
bool retval = false;
const auto prev_stack_top = m_match_stack.size();
if (Keyword("def")) {
retval = true;
if (t_class_context) {
m_match_stack.push_back(make_node<eval::Id_AST_Node<Tracer>>(t_class_name, m_position.line, m_position.col));
}
if (!Id(true)) {
throw exception::eval_error("Missing function name in definition", File_Position(m_position.line, m_position.col), *m_filename);
}
bool is_method = false;
if (Symbol("::")) {
// We're now a method
is_method = true;
if (!Id(true)) {
throw exception::eval_error("Missing method name in definition", File_Position(m_position.line, m_position.col), *m_filename);
}
}
if (Char('(')) {
Decl_Arg_List();
if (!Char(')')) {
throw exception::eval_error("Incomplete function definition", File_Position(m_position.line, m_position.col), *m_filename);
}
}
while (Eos()) {
}
if (Char(':')) {
if (!Operator()) {
throw exception::eval_error("Missing guard expression for function",
File_Position(m_position.line, m_position.col),
*m_filename);
}
}
while (Eol()) {
}
if (!Block()) {
throw exception::eval_error("Incomplete function definition", File_Position(m_position.line, m_position.col), *m_filename);
}
if (is_method || t_class_context) {
build_match<eval::Method_AST_Node<Tracer>>(prev_stack_top);
} else {
build_match<eval::Def_AST_Node<Tracer>>(prev_stack_top);
}
}
return retval;
}
/// Reads a function definition from input
bool Try() {
Depth_Counter dc{this};
bool retval = false;
const auto prev_stack_top = m_match_stack.size();
if (Keyword("try")) {
retval = true;
while (Eol()) {
}
if (!Block()) {
throw exception::eval_error("Incomplete 'try' block", File_Position(m_position.line, m_position.col), *m_filename);
}
bool has_matches = true;
while (has_matches) {
while (Eol()) {
}
has_matches = false;
if (Keyword("catch")) {
const auto catch_stack_top = m_match_stack.size();
if (Char('(')) {
if (!(Arg() && Char(')'))) {
throw exception::eval_error("Incomplete 'catch' expression",
File_Position(m_position.line, m_position.col),
*m_filename);
}
}
while (Eol()) {
}
if (!Block()) {
throw exception::eval_error("Incomplete 'catch' block", File_Position(m_position.line, m_position.col), *m_filename);
}
build_match<eval::Catch_AST_Node<Tracer>>(catch_stack_top);
has_matches = true;
}
}
while (Eol()) {
}
if (Keyword("finally")) {
const auto finally_stack_top = m_match_stack.size();
while (Eol()) {
}
if (!Block()) {
throw exception::eval_error("Incomplete 'finally' block", File_Position(m_position.line, m_position.col), *m_filename);
}
build_match<eval::Finally_AST_Node<Tracer>>(finally_stack_top);
}
build_match<eval::Try_AST_Node<Tracer>>(prev_stack_top);
}
return retval;
}
/// Reads an if/else if/else block from input
bool If() {
Depth_Counter dc{this};
bool retval = false;
const auto prev_stack_top = m_match_stack.size();
if (Keyword("if")) {
retval = true;
if (!Char('(')) {
throw exception::eval_error("Incomplete 'if' expression", File_Position(m_position.line, m_position.col), *m_filename);
}
if (!Equation()) {
throw exception::eval_error("Incomplete 'if' expression", File_Position(m_position.line, m_position.col), *m_filename);
}
const bool is_if_init = Eol() && Equation();
if (!Char(')')) {
throw exception::eval_error("Incomplete 'if' expression", File_Position(m_position.line, m_position.col), *m_filename);
}
while (Eol()) {
}
if (!Block()) {
throw exception::eval_error("Incomplete 'if' block", File_Position(m_position.line, m_position.col), *m_filename);
}
bool has_matches = true;
while (has_matches) {
while (Eol()) {
}
has_matches = false;
if (Keyword("else")) {
if (If()) {
has_matches = true;
} else {
while (Eol()) {
}
if (!Block()) {
throw exception::eval_error("Incomplete 'else' block", File_Position(m_position.line, m_position.col), *m_filename);
}
has_matches = true;
}
}
}
const auto num_children = m_match_stack.size() - prev_stack_top;
if ((is_if_init && num_children == 3) || (!is_if_init && num_children == 2)) {
m_match_stack.push_back(chaiscript::make_unique<eval::AST_Node_Impl<Tracer>, eval::Noop_AST_Node<Tracer>>());
}
if (!is_if_init) {
build_match<eval::If_AST_Node<Tracer>>(prev_stack_top);
} else {
build_match<eval::If_AST_Node<Tracer>>(prev_stack_top + 1);
build_match<eval::Block_AST_Node<Tracer>>(prev_stack_top);
}
}
return retval;
}
/// Reads a class block from input
bool Namespace_Block() {
Depth_Counter dc{this};
const auto prev_stack_top = m_match_stack.size();
const auto prev_pos = m_position;
if (Keyword("namespace")) {
if (Id(true)) {
std::string ns_name = m_match_stack.back()->text;
while (Symbol("::")) {
if (!Id(true)) {
throw exception::eval_error("Incomplete namespace name after '::'",
File_Position(m_position.line, m_position.col),
*m_filename);
}
ns_name += "::" + m_match_stack.back()->text;
m_match_stack.pop_back();
}
m_match_stack.back() = make_node<eval::Id_AST_Node<Tracer>>(ns_name, prev_pos.line, prev_pos.col);
while (Eol()) {
}
if (Block()) {
build_match<eval::Namespace_Block_AST_Node<Tracer>>(prev_stack_top);
return true;
}
}
m_position = prev_pos;
while (prev_stack_top != m_match_stack.size()) {
m_match_stack.pop_back();
}
}
return false;
}
bool Class(const bool t_class_allowed) {
Depth_Counter dc{this};
bool retval = false;
size_t prev_stack_top = m_match_stack.size();
if (Keyword("class")) {
if (!t_class_allowed) {
throw exception::eval_error("Class definitions only allowed at top scope",
File_Position(m_position.line, m_position.col),
*m_filename);
}
retval = true;
if (!Id(true)) {
throw exception::eval_error("Missing class name in definition", File_Position(m_position.line, m_position.col), *m_filename);
}
const auto class_name = m_match_stack.back()->text;
// Optionally parse ': BaseClassName' for inheritance
if (Char(':')) {
if (!Id(true)) {
throw exception::eval_error("Missing base class name in definition", File_Position(m_position.line, m_position.col), *m_filename);
}
}
while (Eol()) {
}
if (!Class_Block(class_name)) {
throw exception::eval_error("Incomplete 'class' block", File_Position(m_position.line, m_position.col), *m_filename);
}
build_match<eval::Class_AST_Node<Tracer>>(prev_stack_top);
}
return retval;
}
bool Using(const bool t_class_allowed) {
Depth_Counter dc{this};
const auto prev_stack_top = m_match_stack.size();
if (Keyword("using")) {
if (!t_class_allowed) {
throw exception::eval_error("Type alias definitions only allowed at top scope",
File_Position(m_position.line, m_position.col),
*m_filename);
}
if (!Id(true)) {
throw exception::eval_error("Missing type name in 'using' declaration",
File_Position(m_position.line, m_position.col),
*m_filename);
}
if (!Symbol("=", true)) {
throw exception::eval_error("Missing '=' in 'using' declaration",
File_Position(m_position.line, m_position.col),
*m_filename);
}
if (!Id(true)) {
throw exception::eval_error("Missing base type name in 'using' declaration",
File_Position(m_position.line, m_position.col),
*m_filename);
}
build_match<eval::Using_AST_Node<Tracer>>(prev_stack_top);
return true;
}
return false;
}
bool Enum(const bool t_allowed) {
Depth_Counter dc{this};
bool retval = false;
const auto prev_stack_top = m_match_stack.size();
if (Keyword("enum")) {
if (!Keyword("class") && !Keyword("struct")) {
throw exception::eval_error("Expected 'class' or 'struct' after 'enum' (only 'enum class'/'enum struct' is supported)",
File_Position(m_position.line, m_position.col),
*m_filename);
}
if (!t_allowed) {
throw exception::eval_error("Enum definitions only allowed at top scope",
File_Position(m_position.line, m_position.col),
*m_filename);
}
retval = true;
if (!Id(true)) {
throw exception::eval_error("Missing enum class name in definition", File_Position(m_position.line, m_position.col), *m_filename);
}
std::string underlying_type = "int";
if (Char(':')) {
if (!Id(false)) {
throw exception::eval_error("Expected underlying type after ':'",
File_Position(m_position.line, m_position.col),
*m_filename);
}
underlying_type = m_match_stack.back()->text;
m_match_stack.pop_back();
}
m_match_stack.push_back(
make_node<eval::Constant_AST_Node<Tracer>>(underlying_type, m_position.line, m_position.col, const_var(underlying_type)));
if (!Char('{')) {
throw exception::eval_error("Expected '{' after enum class declaration", File_Position(m_position.line, m_position.col), *m_filename);
}
int next_value = 0;
while (Eol()) {
}
if (!Char('}')) {
do {
while (Eol()) {
}
if (!Id(true)) {
throw exception::eval_error("Expected enum value name", File_Position(m_position.line, m_position.col), *m_filename);
}
if (Symbol("=")) {
if (!Num()) {
throw exception::eval_error("Expected integer after '=' in enum definition",
File_Position(m_position.line, m_position.col),
*m_filename);
}
next_value = static_cast<int>(std::stoi(m_match_stack.back()->text));
m_match_stack.pop_back();
}
m_match_stack.push_back(
make_node<eval::Constant_AST_Node<Tracer>>(std::to_string(next_value), m_position.line, m_position.col, const_var(next_value)));
++next_value;
while (Eol()) {
}
} while (Char(',') && !Char('}'));
while (Eol()) {
}
if (!Char('}')) {
throw exception::eval_error("Expected '}' to close enum class definition",
File_Position(m_position.line, m_position.col),
*m_filename);
}
}
build_match<eval::Enum_AST_Node<Tracer>>(prev_stack_top);
}
return retval;
}
/// Reads a while block from input
bool While() {
Depth_Counter dc{this};
bool retval = false;
const auto prev_stack_top = m_match_stack.size();
if (Keyword("while")) {
retval = true;
if (!Char('(')) {
throw exception::eval_error("Incomplete 'while' expression", File_Position(m_position.line, m_position.col), *m_filename);
}
if (!(Operator() && Char(')'))) {
throw exception::eval_error("Incomplete 'while' expression", File_Position(m_position.line, m_position.col), *m_filename);
}
while (Eol()) {
}
if (!Block()) {
throw exception::eval_error("Incomplete 'while' block", File_Position(m_position.line, m_position.col), *m_filename);
}
build_match<eval::While_AST_Node<Tracer>>(prev_stack_top);
}
return retval;
}
/// Reads the ranged `for` conditions from input
bool Range_Expression() {
Depth_Counter dc{this};
// the first element will have already been captured by the For_Guards() call that preceeds it
return Char(':') && Equation();
}
/// Reads the C-style `for` conditions from input
bool For_Guards() {
Depth_Counter dc{this};
if (!(Equation() && Eol())) {
if (!Eol()) {
return false;
} else {
m_match_stack.push_back(chaiscript::make_unique<eval::AST_Node_Impl<Tracer>, eval::Noop_AST_Node<Tracer>>());
}
}
if (!(Equation() && Eol())) {
if (!Eol()) {
return false;
} else {
m_match_stack.push_back(
chaiscript::make_unique<eval::AST_Node_Impl<Tracer>, eval::Constant_AST_Node<Tracer>>(Boxed_Value(true)));
}
}
if (!Equation()) {
m_match_stack.push_back(chaiscript::make_unique<eval::AST_Node_Impl<Tracer>, eval::Noop_AST_Node<Tracer>>());
}
return true;
}
/// Reads a for block from input
bool For() {
Depth_Counter dc{this};
bool retval = false;
const auto prev_stack_top = m_match_stack.size();
if (Keyword("for")) {
retval = true;
if (!Char('(')) {
throw exception::eval_error("Incomplete 'for' expression", File_Position(m_position.line, m_position.col), *m_filename);
}
const bool classic_for = For_Guards() && Char(')');
if (!classic_for && !(Range_Expression() && Char(')'))) {
throw exception::eval_error("Incomplete 'for' expression", File_Position(m_position.line, m_position.col), *m_filename);
}
while (Eol()) {
}
if (!Block()) {
throw exception::eval_error("Incomplete 'for' block", File_Position(m_position.line, m_position.col), *m_filename);
}
const auto num_children = m_match_stack.size() - prev_stack_top;
if (classic_for) {
if (num_children != 4) {
throw exception::eval_error("Incomplete 'for' expression", File_Position(m_position.line, m_position.col), *m_filename);
}
build_match<eval::For_AST_Node<Tracer>>(prev_stack_top);
} else {
if (num_children != 3) {
throw exception::eval_error("Incomplete ranged-for expression", File_Position(m_position.line, m_position.col), *m_filename);
}
build_match<eval::Ranged_For_AST_Node<Tracer>>(prev_stack_top);
}
}
return retval;
}
/// Reads a case block from input
bool Case() {
Depth_Counter dc{this};
bool retval = false;
const auto prev_stack_top = m_match_stack.size();
if (Keyword("case")) {
retval = true;
if (!Char('(')) {
throw exception::eval_error("Incomplete 'case' expression", File_Position(m_position.line, m_position.col), *m_filename);
}
if (!(Operator() && Char(')'))) {
throw exception::eval_error("Incomplete 'case' expression", File_Position(m_position.line, m_position.col), *m_filename);
}
while (Eol()) {
}
if (!Block()) {
throw exception::eval_error("Incomplete 'case' block", File_Position(m_position.line, m_position.col), *m_filename);
}
build_match<eval::Case_AST_Node<Tracer>>(prev_stack_top);
} else if (Keyword("default")) {
retval = true;
while (Eol()) {
}
if (!Block()) {
throw exception::eval_error("Incomplete 'default' block", File_Position(m_position.line, m_position.col), *m_filename);
}
build_match<eval::Default_AST_Node<Tracer>>(prev_stack_top);
}
return retval;
}
/// Reads a switch statement from input
bool Switch() {
Depth_Counter dc{this};
const auto prev_stack_top = m_match_stack.size();
if (Keyword("switch")) {
if (!Char('(')) {
throw exception::eval_error("Incomplete 'switch' expression", File_Position(m_position.line, m_position.col), *m_filename);
}
if (!(Operator() && Char(')'))) {
throw exception::eval_error("Incomplete 'switch' expression", File_Position(m_position.line, m_position.col), *m_filename);
}
while (Eol()) {
}
if (Char('{')) {
while (Eol()) {
}
while (Case()) {
while (Eol()) {
} // eat
}
while (Eol()) {
} // eat
if (!Char('}')) {
throw exception::eval_error("Incomplete block", File_Position(m_position.line, m_position.col), *m_filename);
}
} else {
throw exception::eval_error("Incomplete block", File_Position(m_position.line, m_position.col), *m_filename);
}
build_match<eval::Switch_AST_Node<Tracer>>(prev_stack_top);
return true;
} else {
return false;
}
}
/// Reads a curly-brace C-style class block from input
bool Class_Block(const std::string &t_class_name) {
Depth_Counter dc{this};
bool retval = false;
const auto prev_stack_top = m_match_stack.size();
if (Char('{')) {
retval = true;
Class_Statements(t_class_name);
if (!Char('}')) {
throw exception::eval_error("Incomplete class block", File_Position(m_position.line, m_position.col), *m_filename);
}
if (m_match_stack.size() == prev_stack_top) {
m_match_stack.push_back(chaiscript::make_unique<eval::AST_Node_Impl<Tracer>, eval::Noop_AST_Node<Tracer>>());
}
build_match<eval::Block_AST_Node<Tracer>>(prev_stack_top);
}
return retval;
}
/// Reads a curly-brace C-style block from input
bool Block() {
Depth_Counter dc{this};
bool retval = false;
const auto prev_stack_top = m_match_stack.size();
if (Char('{')) {
retval = true;
Statements();
if (!Char('}')) {
throw exception::eval_error("Incomplete block", File_Position(m_position.line, m_position.col), *m_filename);
}
if (m_match_stack.size() == prev_stack_top) {
m_match_stack.push_back(chaiscript::make_unique<eval::AST_Node_Impl<Tracer>, eval::Noop_AST_Node<Tracer>>());
}
build_match<eval::Block_AST_Node<Tracer>>(prev_stack_top);
}
return retval;
}
/// Reads a return statement from input
bool Return() {
Depth_Counter dc{this};
const auto prev_stack_top = m_match_stack.size();
if (Keyword("return")) {
Equation();
build_match<eval::Return_AST_Node<Tracer>>(prev_stack_top);
return true;
} else {
return false;
}
}
/// Reads a break statement from input
bool Break() {
Depth_Counter dc{this};
const auto prev_stack_top = m_match_stack.size();
if (Keyword("break")) {
build_match<eval::Break_AST_Node<Tracer>>(prev_stack_top);
return true;
} else {
return false;
}
}
/// Reads a continue statement from input
bool Continue() {
Depth_Counter dc{this};
const auto prev_stack_top = m_match_stack.size();
if (Keyword("continue")) {
build_match<eval::Continue_AST_Node<Tracer>>(prev_stack_top);
return true;
} else {
return false;
}
}
/// Reads a dot expression(member access), then proceeds to check if it's a function or array call
bool Dot_Fun_Array() {
Depth_Counter dc{this};
bool retval = false;
const auto prev_stack_top = m_match_stack.size();
if (Lambda() || Num() || Raw_String() || Quoted_String() || Single_Quoted_String() || Paren_Expression() || Inline_Container() || Id(false)) {
retval = true;
bool has_more = true;
while (has_more) {
has_more = false;
if (Char('(')) {
has_more = true;
Arg_List();
if (!Char(')')) {
throw exception::eval_error("Incomplete function call", File_Position(m_position.line, m_position.col), *m_filename);
}
build_match<eval::Fun_Call_AST_Node<Tracer>>(prev_stack_top);
/// \todo Work around for method calls until we have a better solution
if (!m_match_stack.back()->children.empty()) {
if (m_match_stack.back()->children[0]->identifier == AST_Node_Type::Dot_Access) {
if (m_match_stack.empty()) {
throw exception::eval_error("Incomplete dot access fun call",
File_Position(m_position.line, m_position.col),
*m_filename);
}
if (m_match_stack.back()->children.empty()) {
throw exception::eval_error("Incomplete dot access fun call",
File_Position(m_position.line, m_position.col),
*m_filename);
}
auto dot_access = std::move(m_match_stack.back()->children[0]);
auto func_call = std::move(m_match_stack.back());
m_match_stack.pop_back();
func_call->children.erase(func_call->children.begin());
if (dot_access->children.empty()) {
throw exception::eval_error("Incomplete dot access fun call",
File_Position(m_position.line, m_position.col),
*m_filename);
}
func_call->children.insert(func_call->children.begin(), std::move(dot_access->children.back()));
dot_access->children.pop_back();
dot_access->children.push_back(std::move(func_call));
if (dot_access->children.size() != 2) {
throw exception::eval_error("Incomplete dot access fun call",
File_Position(m_position.line, m_position.col),
*m_filename);
}
m_match_stack.push_back(std::move(dot_access));
}
}
} else if (Char('[')) {
has_more = true;
if (!(Operator() && Char(']'))) {
throw exception::eval_error("Incomplete array access", File_Position(m_position.line, m_position.col), *m_filename);
}
build_match<eval::Array_Call_AST_Node<Tracer>>(prev_stack_top);
} else if (Symbol(".") || Symbol("::")) {
has_more = true;
if (!(Id(true))) {
throw exception::eval_error("Incomplete dot access fun call", File_Position(m_position.line, m_position.col), *m_filename);
}
if (std::distance(m_match_stack.begin() + static_cast<int>(prev_stack_top), m_match_stack.end()) != 2) {
throw exception::eval_error("Incomplete dot access fun call", File_Position(m_position.line, m_position.col), *m_filename);
}
build_match<eval::Dot_Access_AST_Node<Tracer>>(prev_stack_top);
} else if (Eol()) {
auto start = --m_position;
while (Eol()) {
}
if (Symbol(".")) {
has_more = true;
--m_position;
} else {
m_position = start;
}
}
}
}
return retval;
}
/// Reads a variable declaration from input
bool Var_Decl(const bool t_class_context = false, const std::string &t_class_name = "") {
Depth_Counter dc{this};
bool retval = false;
const auto prev_stack_top = m_match_stack.size();
if (t_class_context && (Keyword("attr") || Keyword("auto") || Keyword("var"))) {
retval = true;
m_match_stack.push_back(make_node<eval::Id_AST_Node<Tracer>>(t_class_name, m_position.line, m_position.col));
if (!Id(true)) {
throw exception::eval_error("Incomplete attribute declaration", File_Position(m_position.line, m_position.col), *m_filename);
}
build_match<eval::Attr_Decl_AST_Node<Tracer>>(prev_stack_top);
} else if (Keyword("auto") || Keyword("var")) {
retval = true;
if (Reference()) {
// we built a reference node - continue
} else if (Id(true)) {
build_match<eval::Var_Decl_AST_Node<Tracer>>(prev_stack_top);
} else {
throw exception::eval_error("Incomplete variable declaration", File_Position(m_position.line, m_position.col), *m_filename);
}
} else if (Keyword("const")) {
retval = true;
// consume optional 'var' or 'auto' after 'const'
Keyword("var") || Keyword("auto");
if (Id(true)) {
build_match<eval::Const_Var_Decl_AST_Node<Tracer>>(prev_stack_top);
} else {
throw exception::eval_error("Incomplete const variable declaration", File_Position(m_position.line, m_position.col), *m_filename);
}
} else if (Keyword("global")) {
retval = true;
if (!(Reference() || Id(true))) {
throw exception::eval_error("Incomplete global declaration", File_Position(m_position.line, m_position.col), *m_filename);
}
build_match<eval::Global_Decl_AST_Node<Tracer>>(prev_stack_top);
} else if (Keyword("attr")) {
retval = true;
if (!Id(true)) {
throw exception::eval_error("Incomplete attribute declaration", File_Position(m_position.line, m_position.col), *m_filename);
}
if (!Symbol("::")) {
throw exception::eval_error("Incomplete attribute declaration", File_Position(m_position.line, m_position.col), *m_filename);
}
if (!Id(true)) {
throw exception::eval_error("Missing attribute name in definition", File_Position(m_position.line, m_position.col), *m_filename);
}
build_match<eval::Attr_Decl_AST_Node<Tracer>>(prev_stack_top);
}
return retval;
}
/// Reads an expression surrounded by parentheses from input
bool Paren_Expression() {
Depth_Counter dc{this};
if (Char('(')) {
if (!Operator()) {
throw exception::eval_error("Incomplete expression", File_Position(m_position.line, m_position.col), *m_filename);
}
if (!Char(')')) {
throw exception::eval_error("Missing closing parenthesis ')'", File_Position(m_position.line, m_position.col), *m_filename);
}
return true;
} else {
return false;
}
}
/// Reads, and identifies, a short-form container initialization from input
bool Inline_Container() {
Depth_Counter dc{this};
const auto prev_stack_top = m_match_stack.size();
if (Char('[')) {
Container_Arg_List();
if (!Char(']')) {
throw exception::eval_error("Missing closing square bracket ']' in container initializer",
File_Position(m_position.line, m_position.col),
*m_filename);
}
if ((prev_stack_top != m_match_stack.size()) && (!m_match_stack.back()->children.empty())) {
if (m_match_stack.back()->children[0]->identifier == AST_Node_Type::Value_Range) {
build_match<eval::Inline_Range_AST_Node<Tracer>>(prev_stack_top);
} else if (m_match_stack.back()->children[0]->identifier == AST_Node_Type::Map_Pair) {
build_match<eval::Inline_Map_AST_Node<Tracer>>(prev_stack_top);
} else {
build_match<eval::Inline_Array_AST_Node<Tracer>>(prev_stack_top);
}
} else {
build_match<eval::Inline_Array_AST_Node<Tracer>>(prev_stack_top);
}
return true;
} else {
return false;
}
}
/// Parses a variable specified with a & aka reference
bool Reference() {
Depth_Counter dc{this};
const auto prev_stack_top = m_match_stack.size();
if (Symbol("&")) {
if (!Id(true)) {
throw exception::eval_error("Incomplete '&' expression", File_Position(m_position.line, m_position.col), *m_filename);
}
build_match<eval::Reference_AST_Node<Tracer>>(prev_stack_top);
return true;
} else {
return false;
}
}
/// Reads a unary prefixed expression from input
bool Prefix() {
Depth_Counter dc{this};
const auto prev_stack_top = m_match_stack.size();
using SS = utility::Static_String;
const std::array<utility::Static_String, 6> prefix_opers{{SS{"++"}, SS{"--"}, SS{"-"}, SS{"+"}, SS{"!"}, SS{"~"}}};
for (const auto &oper : prefix_opers) {
const bool is_char = oper.size() == 1;
if ((is_char && Char(oper.c_str()[0])) || (!is_char && Symbol(oper))) {
if (!Operator(m_operators.size() - 1)) {
throw exception::eval_error("Incomplete prefix '" + std::string(oper.c_str()) + "' expression",
File_Position(m_position.line, m_position.col),
*m_filename);
}
build_match<eval::Prefix_AST_Node<Tracer>>(prev_stack_top, oper.c_str());
return true;
}
}
return false;
}
/// Parses any of a group of 'value' style ast_node groups from input
bool Value() {
Depth_Counter dc{this};
return Var_Decl() || Dot_Fun_Array() || Prefix();
}
bool Operator_Helper(const size_t t_precedence, std::string &oper) {
return m_operator_matches.any_of(t_precedence, [&oper, this](const auto &elem) {
if (Symbol(elem)) {
oper = elem.c_str();
return true;
} else {
return false;
}
});
}
bool Operator(const size_t t_precedence = 0) {
Depth_Counter dc{this};
bool retval = false;
const auto prev_stack_top = m_match_stack.size();
if (m_operators[t_precedence] != Operator_Precedence::Prefix) {
if (Operator(t_precedence + 1)) {
retval = true;
std::string oper;
while (Operator_Helper(t_precedence, oper)) {
while (Eol()) {
}
if (!Operator(t_precedence + 1)) {
throw exception::eval_error("Incomplete '" + oper + "' expression",
File_Position(m_position.line, m_position.col),
*m_filename);
}
switch (m_operators[t_precedence]) {
case (Operator_Precedence::Ternary_Cond):
if (Symbol(":")) {
if (!Operator(t_precedence + 1)) {
throw exception::eval_error("Incomplete '" + oper + "' expression",
File_Position(m_position.line, m_position.col),
*m_filename);
}
build_match<eval::If_AST_Node<Tracer>>(prev_stack_top);
} else {
throw exception::eval_error("Incomplete '" + oper + "' expression",
File_Position(m_position.line, m_position.col),
*m_filename);
}
break;
case (Operator_Precedence::Addition):
case (Operator_Precedence::Multiplication):
case (Operator_Precedence::Shift):
case (Operator_Precedence::Equality):
case (Operator_Precedence::Bitwise_And):
case (Operator_Precedence::Bitwise_Xor):
case (Operator_Precedence::Bitwise_Or):
case (Operator_Precedence::Comparison):
build_match<eval::Binary_Operator_AST_Node<Tracer>>(prev_stack_top, oper);
break;
case (Operator_Precedence::Logical_And):
build_match<eval::Logical_And_AST_Node<Tracer>>(prev_stack_top, oper);
break;
case (Operator_Precedence::Logical_Or):
build_match<eval::Logical_Or_AST_Node<Tracer>>(prev_stack_top, oper);
break;
case (Operator_Precedence::Prefix):
assert(false); // cannot reach here because of if() statement at the top
break;
// default:
// throw exception::eval_error("Internal error: unhandled ast_node",
// File_Position(m_position.line, m_position.col), *m_filename);
}
}
}
} else {
return Value();
}
return retval;
}
/// Reads a pair of values used to create a map initialization from input
bool Map_Pair() {
Depth_Counter dc{this};
bool retval = false;
const auto prev_stack_top = m_match_stack.size();
const auto prev_pos = m_position;
if (Operator()) {
if (Symbol(":")) {
retval = true;
if (!Operator()) {
throw exception::eval_error("Incomplete map pair", File_Position(m_position.line, m_position.col), *m_filename);
}
build_match<eval::Map_Pair_AST_Node<Tracer>>(prev_stack_top);
} else {
m_position = prev_pos;
while (prev_stack_top != m_match_stack.size()) {
m_match_stack.pop_back();
}
}
}
return retval;
}
/// Reads a pair of values used to create a range initialization from input
bool Value_Range() {
Depth_Counter dc{this};
bool retval = false;
const auto prev_stack_top = m_match_stack.size();
const auto prev_pos = m_position;
if (Operator()) {
if (Symbol("..")) {
retval = true;
if (!Operator()) {
throw exception::eval_error("Incomplete value range", File_Position(m_position.line, m_position.col), *m_filename);
}
build_match<eval::Value_Range_AST_Node<Tracer>>(prev_stack_top);
} else {
m_position = prev_pos;
while (prev_stack_top != m_match_stack.size()) {
m_match_stack.pop_back();
}
}
}
return retval;
}
/// Parses a string of binary equation operators
bool Equation() {
Depth_Counter dc{this};
const auto prev_stack_top = m_match_stack.size();
using SS = utility::Static_String;
if (Operator()) {
for (const auto &sym :
{SS{"="}, SS{":="}, SS{"+="}, SS{"-="}, SS{"*="}, SS{"/="}, SS{"%="}, SS{"<<="}, SS{">>="}, SS{"&="}, SS{"^="}, SS{"|="}}) {
if (Symbol(sym, true)) {
SkipWS(true);
if (!Equation()) {
throw exception::eval_error("Incomplete equation", File_Position(m_position.line, m_position.col), *m_filename);
}
build_match<eval::Equation_AST_Node<Tracer>>(prev_stack_top, sym.c_str());
return true;
}
}
return true;
}
return false;
}
/// Parses statements allowed inside of a class block
bool Class_Statements(const std::string &t_class_name) {
Depth_Counter dc{this};
bool retval = false;
bool has_more = true;
bool saw_eol = true;
while (has_more) {
const auto start = m_position;
if (Def(true, t_class_name)) {
if (!saw_eol) {
throw exception::eval_error("Two function definitions missing line separator",
File_Position(start.line, start.col),
*m_filename);
}
has_more = true;
retval = true;
saw_eol = true;
} else if (Var_Decl(true, t_class_name)) {
if (!saw_eol) {
throw exception::eval_error("Two expressions missing line separator",
File_Position(start.line, start.col),
*m_filename);
}
has_more = true;
retval = true;
saw_eol = false;
} else if (Eol()) {
has_more = true;
retval = true;
saw_eol = true;
} else {
has_more = false;
}
}
return retval;
}
/// Top level parser, starts parsing of all known parses
bool Statements(const bool t_class_allowed = false) {
Depth_Counter dc{this};
bool retval = false;
bool has_more = true;
bool saw_eol = true;
while (has_more) {
const auto start = m_position;
if (Def() || Try() || If() || While() || Namespace_Block() || Class(t_class_allowed) || Using(t_class_allowed) || Enum(t_class_allowed) || For() || Switch()) {
if (!saw_eol) {
throw exception::eval_error("Two function definitions missing line separator",
File_Position(start.line, start.col),
*m_filename);
}
has_more = true;
retval = true;
saw_eol = true;
} else if (Return() || Break() || Continue() || Equation()) {
if (!saw_eol) {
throw exception::eval_error("Two expressions missing line separator", File_Position(start.line, start.col), *m_filename);
}
has_more = true;
retval = true;
saw_eol = false;
} else if (Block() || Eol()) {
has_more = true;
retval = true;
saw_eol = true;
} else {
has_more = false;
}
}
return retval;
}
AST_NodePtr parse(const std::string &t_input, const std::string &t_fname) override {
ChaiScript_Parser<Tracer, Optimizer> parser(m_tracer, m_optimizer);
return parser.parse_internal(t_input, t_fname);
}
eval::AST_Node_Impl_Ptr<Tracer> parse_instr_eval(const std::string &t_input) {
auto last_position = m_position;
auto last_filename = m_filename;
auto last_match_stack = std::exchange(m_match_stack, decltype(m_match_stack){});
auto retval = parse_internal(t_input, "instr eval");
m_position = std::move(last_position);
m_filename = std::move(last_filename);
m_match_stack = std::move(last_match_stack);
return eval::AST_Node_Impl_Ptr<Tracer>(dynamic_cast<eval::AST_Node_Impl<Tracer> *>(retval.release()));
}
/// Parses the given input string, tagging parsed ast_nodes with the given m_filename.
AST_NodePtr parse_internal(const std::string &t_input, std::string t_fname) {
const auto begin = t_input.empty() ? nullptr : &t_input.front();
const auto end = begin == nullptr ? nullptr : begin + t_input.size();
m_position = Position(begin, end);
m_filename = std::make_shared<std::string>(std::move(t_fname));
if ((t_input.size() > 1) && (t_input[0] == '#') && (t_input[1] == '!')) {
while (m_position.has_more() && (!Eol())) {
++m_position;
}
}
if (Statements(true)) {
if (m_position.has_more()) {
throw exception::eval_error("Unparsed input", File_Position(m_position.line, m_position.col), *m_filename);
} else {
build_match<eval::File_AST_Node<Tracer>>(0);
}
} else {
m_match_stack.push_back(chaiscript::make_unique<eval::AST_Node_Impl<Tracer>, eval::Noop_AST_Node<Tracer>>());
}
AST_NodePtr retval(std::move(m_match_stack.front()));
m_match_stack.clear();
return retval;
}
};
} // namespace parser
} // namespace chaiscript
#if defined(CHAISCRIPT_MSVC) && defined(CHAISCRIPT_PUSHED_MIN_MAX)
#undef CHAISCRIPT_PUSHED_MIN_MAX
#pragma pop_macro("min")
#pragma pop_macro("max")
#endif
#endif /* CHAISCRIPT_PARSER_HPP_ */