Add class keyword for easier user defined types.

Issue #118
This commit is contained in:
Jason Turner 2014-08-22 21:11:49 -06:00
parent cb1c7730cf
commit fa1f4b795b
6 changed files with 205 additions and 31 deletions

View File

@ -736,6 +736,19 @@ namespace chaiscript
return functions.find(name) != functions.end();
}
/// \returns All values in the local thread state in the parent scope, or if it doesn't exist,
/// the current scope.
std::map<std::string, Boxed_Value> get_parent_locals() const
{
StackData &stack = get_stack_data();
if (stack.size() > 1)
{
return stack[1];
} else {
return stack[0];
}
}
/// \returns All values in the local thread state, added through the add() function
std::map<std::string, Boxed_Value> get_locals() const
{

View File

@ -37,7 +37,7 @@ namespace chaiscript
Comparison, Addition, Subtraction, Multiplication, Division, Modulus, Array_Call, Dot_Access, Quoted_String, Single_Quoted_String,
Lambda, Block, Def, While, If, For, Inline_Array, Inline_Map, Return, File, Prefix, Break, Continue, Map_Pair, Value_Range,
Inline_Range, Annotation, Try, Catch, Finally, Method, Attr_Decl, Shift, Equality, Bitwise_And, Bitwise_Xor, Bitwise_Or,
Logical_And, Logical_Or, Reference, Switch, Case, Default, Ternary_Cond, Noop
Logical_And, Logical_Or, Reference, Switch, Case, Default, Ternary_Cond, Noop, Class
};
};
@ -510,6 +510,9 @@ namespace chaiscript
/// Creates a new scope then pops it on destruction
struct Scope_Push_Pop
{
Scope_Push_Pop(const Scope_Push_Pop &) = delete;
Scope_Push_Pop& operator=(const Scope_Push_Pop &) = delete;
Scope_Push_Pop(chaiscript::detail::Dispatch_Engine &t_de)
: m_de(t_de)
{
@ -523,9 +526,6 @@ namespace chaiscript
private:
// explicitly unimplemented copy and assignment
Scope_Push_Pop(const Scope_Push_Pop &);
Scope_Push_Pop& operator=(const Scope_Push_Pop &);
chaiscript::detail::Dispatch_Engine &m_de;
};
@ -533,6 +533,9 @@ namespace chaiscript
/// Creates a new function call and pops it on destruction
struct Function_Push_Pop
{
Function_Push_Pop(const Function_Push_Pop &) = delete;
Function_Push_Pop& operator=(const Function_Push_Pop &) = delete;
Function_Push_Pop(chaiscript::detail::Dispatch_Engine &t_de)
: m_de(t_de)
{
@ -551,9 +554,6 @@ namespace chaiscript
private:
// explicitly unimplemented copy and assignment
Function_Push_Pop(const Function_Push_Pop &);
Function_Push_Pop& operator=(const Function_Push_Pop &);
chaiscript::detail::Dispatch_Engine &m_de;
};
@ -561,6 +561,9 @@ namespace chaiscript
/// Creates a new scope then pops it on destruction
struct Stack_Push_Pop
{
Stack_Push_Pop(const Stack_Push_Pop &) = delete;
Stack_Push_Pop& operator=(const Stack_Push_Pop &) = delete;
Stack_Push_Pop(chaiscript::detail::Dispatch_Engine &t_de)
: m_de(t_de)
{
@ -574,9 +577,6 @@ namespace chaiscript
private:
// explicitly unimplemented copy and assignment
Stack_Push_Pop(const Stack_Push_Pop &);
Stack_Push_Pop& operator=(const Stack_Push_Pop &);
chaiscript::detail::Dispatch_Engine &m_de;
};

View File

@ -330,6 +330,7 @@ namespace chaiscript
m_engine.add_reserved_word("break");
m_engine.add_reserved_word("true");
m_engine.add_reserved_word("false");
m_engine.add_reserved_word("class");
m_engine.add_reserved_word("_");
if (t_lib)

View File

@ -856,7 +856,23 @@ namespace chaiscript
return Boxed_Value();
}
};
struct Class_AST_Node : public AST_Node {
public:
Class_AST_Node(const std::string &t_ast_node_text = "", const std::shared_ptr<std::string> &t_fname=std::shared_ptr<std::string>(), int t_start_line = 0, int t_start_col = 0, int t_end_line = 0, int t_end_col = 0) :
AST_Node(t_ast_node_text, AST_Node_Type::Class, t_fname, t_start_line, t_start_col, t_end_line, t_end_col) { }
virtual ~Class_AST_Node() {}
virtual Boxed_Value eval_internal(chaiscript::detail::Dispatch_Engine &t_ss) CHAISCRIPT_OVERRIDE {
chaiscript::eval::detail::Scope_Push_Pop spp(t_ss);
// put class name in current scope so it can be looked up by the attrs and methods
t_ss.add_object("_current_class_name", const_var(this->children[0]->text));
this->children[1]->eval(t_ss);
return Boxed_Value();
}
};
struct Ternary_Cond_AST_Node : public AST_Node {
@ -1400,23 +1416,29 @@ namespace chaiscript
std::vector<std::string> t_param_names;
AST_NodePtr guardnode;
auto d = t_ss.get_parent_locals();
auto itr = d.find("_current_class_name");
int class_offset = 0;
if (itr != d.end()) class_offset = -1;
const std::string & class_name = (itr != d.end())?std::string(boxed_cast<std::string>(itr->second)):this->children[0]->text;
//The first param of a method is always the implied this ptr.
t_param_names.push_back("this");
if ((this->children.size() > 3) && (this->children[2]->identifier == AST_Node_Type::Arg_List)) {
for (size_t i = 0; i < this->children[2]->children.size(); ++i) {
t_param_names.push_back(this->children[2]->children[i]->text);
if ((this->children.size() > (3 + class_offset)) && (this->children[(2 + class_offset)]->identifier == AST_Node_Type::Arg_List)) {
for (size_t i = 0; i < this->children[(2 + class_offset)]->children.size(); ++i) {
t_param_names.push_back(this->children[(2 + class_offset)]->children[i]->text);
}
if (this->children.size() > 4) {
guardnode = this->children[3];
if (this->children.size() > (4 + class_offset)) {
guardnode = this->children[(3 + class_offset)];
}
}
else {
//no parameters
if (this->children.size() > 3) {
guardnode = this->children[2];
if (this->children.size() > (3 + class_offset)) {
guardnode = this->children[(2 + class_offset)];
}
}
@ -1432,8 +1454,9 @@ namespace chaiscript
try {
const std::string & l_annotation = this->annotation?this->annotation->text:"";
const std::string & class_name = this->children[0]->text;
const std::string & function_name = this->children[1]->text;
const std::string & function_name = this->children[(1 + class_offset)]->text;
if (function_name == class_name) {
t_ss.add(Proxy_Function
(new dispatch::detail::Dynamic_Object_Constructor(class_name, Proxy_Function
@ -1478,22 +1501,28 @@ namespace chaiscript
Attr_Decl_AST_Node(const std::string &t_ast_node_text = "", const std::shared_ptr<std::string> &t_fname=std::shared_ptr<std::string>(), int t_start_line = 0, int t_start_col = 0, int t_end_line = 0, int t_end_col = 0) :
AST_Node(t_ast_node_text, AST_Node_Type::Attr_Decl, t_fname, t_start_line, t_start_col, t_end_line, t_end_col) { }
virtual ~Attr_Decl_AST_Node() {}
virtual Boxed_Value eval_internal(chaiscript::detail::Dispatch_Engine &t_ss) CHAISCRIPT_OVERRIDE{
try {
virtual Boxed_Value eval_internal(chaiscript::detail::Dispatch_Engine &t_ss) CHAISCRIPT_OVERRIDE
{
const auto &d = t_ss.get_parent_locals();
const auto itr = d.find("_current_class_name");
int class_offset = 0;
if (itr != d.end()) class_offset = -1;
std::string class_name = (itr != d.end())?std::string(boxed_cast<std::string>(itr->second)):this->children[0]->text;
try {
t_ss.add(Proxy_Function
(new dispatch::detail::Dynamic_Object_Function(
this->children[0]->text,
class_name,
fun(std::function<Boxed_Value (dispatch::Dynamic_Object &)>(std::bind(&dispatch::Dynamic_Object::get_attr,
std::placeholders::_1,
this->children[1]->text
this->children[(1 + class_offset)]->text
)))
)
), this->children[1]->text);
), this->children[(1 + class_offset)]->text);
}
catch (const exception::reserved_word_error &) {
throw exception::eval_error("Reserved word used as attribute '" + this->children[1]->text + "'");
throw exception::eval_error("Reserved word used as attribute '" + this->children[(1 + class_offset)]->text + "'");
} catch (const exception::name_conflict_error &e) {
throw exception::eval_error("Attribute redefined '" + e.name() + "'");
}

View File

@ -1358,7 +1358,7 @@ namespace chaiscript
/**
* Reads a function definition from input
*/
bool Def() {
bool Def(bool t_class_context = false) {
bool retval = false;
bool is_annotated = false;
AST_NodePtr annotation;
@ -1410,7 +1410,7 @@ namespace chaiscript
throw exception::eval_error("Incomplete function definition", File_Position(m_line, m_col), *m_filename);
}
if (is_method) {
if (is_method || t_class_context) {
build_match(AST_NodePtr(new eval::Method_AST_Node()), prev_stack_top);
}
else {
@ -1555,6 +1555,35 @@ namespace chaiscript
return retval;
}
/**
* Reads a class block from input
*/
bool Class() {
bool retval = false;
size_t prev_stack_top = m_match_stack.size();
if (Keyword("class")) {
retval = true;
if (!Id(true)) {
throw exception::eval_error("Missing class name in definition", File_Position(m_line, m_col), *m_filename);
}
while (Eol()) {}
if (!Class_Block()) {
throw exception::eval_error("Incomplete 'class' block", File_Position(m_line, m_col), *m_filename);
}
build_match(AST_NodePtr(new eval::Class_AST_Node()), prev_stack_top);
}
return retval;
}
/**
* Reads a while block from input
*/
@ -1737,6 +1766,28 @@ namespace chaiscript
}
/**
* Reads a curly-brace C-style class block from input
*/
bool Class_Block() {
bool retval = false;
size_t prev_stack_top = m_match_stack.size();
if (Char('{')) {
retval = true;
Class_Statements();
if (!Char('}')) {
throw exception::eval_error("Incomplete class block", File_Position(m_line, m_col), *m_filename);
}
build_match(AST_NodePtr(new eval::Block_AST_Node()), prev_stack_top);
}
return retval;
}
/**
* Reads a curly-brace C-style block from input
*/
@ -1875,12 +1926,20 @@ namespace chaiscript
/**
* Reads a variable declaration from input
*/
bool Var_Decl() {
bool Var_Decl(bool t_class_context = false) {
bool retval = false;
size_t prev_stack_top = m_match_stack.size();
if (Keyword("auto") || Keyword("var")) {
if (t_class_context && (Keyword("attr") || Keyword("auto") || Keyword("var"))) {
retval = true;
if (!Id(true)) {
throw exception::eval_error("Incomplete attribute declaration", File_Position(m_line, m_col), *m_filename);
}
build_match(AST_NodePtr(new eval::Attr_Decl_AST_Node()), prev_stack_top);
} else if (Keyword("auto") || Keyword("var")) {
retval = true;
if (!(Reference() || Id(true))) {
@ -1888,8 +1947,7 @@ namespace chaiscript
}
build_match(AST_NodePtr(new eval::Var_Decl_AST_Node()), prev_stack_top);
}
else if (Keyword("attr")) {
} else if (Keyword("attr")) {
retval = true;
if (!Id(true)) {
@ -2262,6 +2320,44 @@ namespace chaiscript
return retval;
}
/**
* Parses statements allowed inside of a class block
*/
bool Class_Statements() {
bool retval = false;
bool has_more = true;
bool saw_eol = true;
while (has_more) {
int prev_line = m_line;
int prev_col = m_col;
if (Def(true)) {
if (!saw_eol) {
throw exception::eval_error("Two function definitions missing line separator", File_Position(prev_line, prev_col), *m_filename);
}
has_more = true;
retval = true;
saw_eol = true;
} else if (Var_Decl(true)) {
if (!saw_eol) {
throw exception::eval_error("Two function definitions missing line separator", File_Position(prev_line, prev_col), *m_filename);
}
has_more = true;
retval = true;
saw_eol = true;
} 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
*/
@ -2306,6 +2402,14 @@ namespace chaiscript
retval = true;
saw_eol = true;
}
else if (Class()) {
if (!saw_eol) {
throw exception::eval_error("Two function definitions missing line separator", File_Position(prev_line, prev_col), *m_filename);
}
has_more = true;
retval = true;
saw_eol = true;
}
else if (For()) {
if (!saw_eol) {
throw exception::eval_error("Two function definitions missing line separator", File_Position(prev_line, prev_col), *m_filename);

27
unittests/class.chai Normal file
View File

@ -0,0 +1,27 @@
class Vector3
{
// you can use attr, auto or var in this context
attr x
auto y
var z
def Vector3(x,y,z)
{
this.x = x
this.y = y
this.z = z
}
def doSomething(mult)
{
return this.x * this.y * this.z * mult
}
}
auto v = Vector3(1,2,3)
assert_equal(1, v.x)
assert_equal(v.doSomething(2), 12)