This commit is contained in:
Matthias Koefferlein 2024-08-14 23:57:21 +02:00
parent a0758aa493
commit 85696e2bc2
5 changed files with 187 additions and 32 deletions

View File

@ -437,7 +437,7 @@ public:
throw tl::EvalError (tl::to_string (tr ("Annotation function must not have arguments")), context);
}
const Object &obj = mp_eval->obj ();
const ant::Object &obj = mp_eval->obj ();
const db::DFTrans &trans = mp_eval->trans ();
if (m_function == 'L') {
@ -471,18 +471,18 @@ public:
}
}
db::DPoint p1 (const Object &obj) const
db::DPoint p1 (const ant::Object &obj) const
{
return obj.seg_p1 (m_index);
}
db::DPoint p2 (const Object &obj) const
db::DPoint p2 (const ant::Object &obj) const
{
return obj.seg_p2 (m_index);
}
double
delta_x (const Object &obj, const db::DFTrans &t) const
delta_x (const ant::Object &obj, const db::DFTrans &t) const
{
double dx = ((t * p2 (obj)).x () - (t * p1 (obj)).x ());
@ -495,7 +495,7 @@ public:
}
double
delta_y (const Object &obj, const db::DFTrans &t) const
delta_y (const ant::Object &obj, const db::DFTrans &t) const
{
double dy = ((t * p2 (obj)).y () - (t * p1 (obj)).y ());

View File

@ -590,6 +590,12 @@ static void def_func (tl::Eval *eval, const std::string &name, FunctionBody *fun
eval->define_function (name, func);
}
static void def_global_func (const std::string &name, FunctionBody *func)
{
func->keep ();
tl::Eval::define_global_function (name, func);
}
Class<FunctionBody> decl_FunctionBody ("tl", "FunctionBody",
gsi::method ("with_kwargs=", &FunctionBody::set_with_kwargs, gsi::arg ("f"),
"@brief Sets a value indicating whether this function accepts keyword arguments.\n"
@ -625,12 +631,115 @@ Class<FunctionBody> decl_FunctionBody ("tl", "FunctionBody",
"This class has been introduced in version 0.29.6."
);
tl::Eval *new_expr_ctx0 ()
{
return new tl::Eval ();
}
tl::Eval *new_expr_ctx1 (tl::Eval *parent)
{
return new tl::Eval (parent);
}
tl::Eval *new_expr_ctx2 (tl::Eval *global, tl::Eval *parent)
{
return new tl::Eval (global, parent);
}
void import3 (tl::Eval *eval, tl::Eval *from, const std::string &name)
{
tl::Variant *var = from->var (name);
if (var) {
eval->set_var (name, *var);
} else {
tl::EvalFunction *func = from->function (name);
if (func) {
eval->define_function (name, func);
}
}
}
void import4 (tl::Eval *eval, tl::Eval *from, const std::vector<std::string> &names)
{
for (auto i = names.begin (); i != names.end (); ++i) {
import3 (eval, from, *i);
}
}
void import1 (tl::Eval *eval, const std::string &name)
{
import3 (eval, &tl::Eval::global_context (), name);
}
void import2 (tl::Eval *eval, const std::vector<std::string> &names)
{
import4 (eval, &tl::Eval::global_context (), names);
}
Class<tl::Eval> decl_ExpressionContext ("tl", "ExpressionContext",
gsi::constructor ("new", &new_expr_ctx0,
"@brief Creates a new expression context\n"
"The expression context acts as a namespace for variables and functions. "
"This version of the context is connected to the global, singleton context. "
"There are other constructors available for creating expression contexts with "
"a parent context or connected to another global context.\n"
) +
gsi::constructor ("new", &new_expr_ctx1, gsi::arg ("parent"),
"@brief Creates a context with a parent context.\n"
"This context uses a parent context which is searched for variables and functions "
"when not local definition can be found. This version of the context also connects to "
"the global singleton context.\n"
"\n"
"This constructor was introduced in version 0.29.6."
) +
gsi::constructor ("new", &new_expr_ctx2, gsi::arg ("global"), gsi::arg ("parent"),
"@brief Creates a context with a parent context and connecting to a separate global context.\n"
"This version allows specifying a global and parent context. The global context is not "
"the singleton context in this case. Specifically, this methods allows creating a detached "
"context by using 'nil' for both global and parent contexts. Such a context provides no "
"specific definitions and can be used to establish a safe environment without access to "
"higher-level classes.\n"
"\n"
"@code\n"
"ctx = RBA::ExpressionContext::new(nil, nil)\n"
"# imports the 'Box' class from the global singleton namespace\n"
"ctx.import('Box')\n"
"# Box is the only class that can be used here:\n"
"ctx.eval('Box(0,0,100,200)')\n"
"@/code\n"
"\n"
"This constructor was introduced in version 0.29.6."
) +
gsi::method_ext ("import", &import1, gsi::arg ("name"),
"@brief Imports a variable from the global, singleton namespace.\n"
"This method can be used for importing classes from the global namespace and make it accessible to "
"this context, even if it is not connected to the global singleton one.\n"
"\n"
"This method has been introduced in version 0.29.6."
) +
gsi::method_ext ("import", &import2, gsi::arg ("names"),
"@brief Imports variables from the global, singleton namespace.\n"
"This variant allows specifying a list of names to import.\n"
"\n"
"This method has been introduced in version 0.29.6."
) +
gsi::method_ext ("import", &import3, gsi::arg ("from"), gsi::arg ("name"),
"@brief Import a variable from the given context.\n"
"This variant allows to give a source context.\n"
"\n"
"This method has been introduced in version 0.29.6."
) +
gsi::method_ext ("import", &import4, gsi::arg ("from"), gsi::arg ("names"),
"@brief Imports variables from the given context.\n"
"This variant allows to give a source context and a list of names to import.\n"
"\n"
"This method has been introduced in version 0.29.6."
) +
gsi::method ("var", &tl::Eval::set_var, gsi::arg ("name"), gsi::arg ("value"),
"@brief Defines a variable with the given name and value\n"
"@brief Defines a variable with the given name and value.\n"
) +
gsi::method_ext ("func", &def_func, gsi::arg ("name"), gsi::arg ("body"),
"@brief Defines a function with the given name and function body\n"
"@brief Defines a function with the given name and function body.\n"
"The function body is an implementation of the \\FunctionBody class. To use it, create a subclass, i.e.\n"
"\n"
"@code\n"
@ -651,9 +760,18 @@ Class<tl::Eval> decl_ExpressionContext ("tl", "ExpressionContext",
"\n"
"This method has been introduced in version 0.29.6."
) +
gsi::method ("global_func", &def_global_func, gsi::arg ("name"), gsi::arg ("body"),
"Defines a function in the global namespace.\n"
"This method defines a function in the global singleton namespace. It works like \\func and acts "
"on the global namespace like \\global_var.\n"
"Note that the new function only becomes visible to contexts that connect to the global context.\n"
"\n"
"It has been introduced in version 0.29.6."
) +
gsi::method ("global_var", &tl::Eval::set_global_var, gsi::arg ("name"), gsi::arg ("value"),
"@brief Defines a global variable with the given name and value\n"
"Global variables are available to all expressions sharing the same global context."
"This method defines a variable in the global singleton namespace. "
"Note that the new variable only becomes visible to contexts that connect to the global context.\n"
) +
gsi::method ("eval", &tl::Eval::eval, gsi::arg ("expr"),
"@brief Compiles and evaluates the given expression in this context\n"

View File

@ -3184,14 +3184,11 @@ Eval::Eval (Eval *global, Eval *parent, bool sloppy)
Eval::~Eval ()
{
for (std::map <std::string, EvalFunction *>::iterator f = m_local_functions.begin (); f != m_local_functions.end (); ++f) {
delete f->second;
}
m_local_functions.clear ();
}
void
Eval::check ()
Eval::check () const
{
if (m_has_parent) {
if (! mp_parent.get ()) {
@ -3227,11 +3224,7 @@ Eval::var (const std::string &name)
void
Eval::define_function (const std::string &name, EvalFunction *function)
{
EvalFunction *&f = m_local_functions.insert (std::make_pair (name, (EvalFunction *) 0)).first->second;
if (f != 0) {
delete f;
}
f = function;
m_local_functions [name].reset (function);
}
EvalFunction *
@ -3239,7 +3232,7 @@ Eval::function (const std::string &name)
{
auto f = m_local_functions.find (name);
if (f != m_local_functions.end ()) {
return f->second;
return f->second.get ();
} else {
return 0;
}
@ -4040,13 +4033,11 @@ Eval::resolve_name (const std::string &t, const EvalFunction *&function, const t
value = 0;
var = 0;
std::map <std::string, EvalFunction *>::const_iterator f;
f = m_local_functions.find (t);
auto f = m_local_functions.find (t);
if (f != m_local_functions.end ()) {
function = f->second;
function = f->second.get ();
} else if ((function = EvalStaticFunction::function_by_name (t)) == 0) {
std::map<std::string, tl::Variant>::iterator v;
v = m_local_vars.find (t);
auto v = m_local_vars.find (t);
if (v != m_local_vars.end ()) {
var = &v->second;
} else {
@ -4054,12 +4045,11 @@ Eval::resolve_name (const std::string &t, const EvalFunction *&function, const t
}
}
if (! function && ! value && ! var) {
if (mp_parent) {
mp_parent->resolve_name (t, function, value, var);
} else if (mp_global) {
mp_global->resolve_name (t, function, value, var);
}
if (mp_parent && ! function && ! value && ! var) {
mp_parent->resolve_name (t, function, value, var);
}
if (mp_global && ! function && ! value && ! var) {
mp_global->resolve_name (t, function, value, var);
}
}

View File

@ -270,6 +270,7 @@ public:
* @brief A base class for a function
*/
class TL_PUBLIC EvalFunction
: public tl::Object
{
public:
/**
@ -583,7 +584,7 @@ public:
/**
* @brief Checks the contexts and throws an exception if one of them got lost
*/
void check ();
void check () const;
private:
friend class Expression;
@ -593,7 +594,7 @@ private:
tl::weak_ptr<Eval> mp_global;
bool m_has_global;
std::map <std::string, tl::Variant> m_local_vars;
std::map <std::string, EvalFunction *> m_local_functions;
std::map <std::string, tl::shared_ptr<EvalFunction> > m_local_functions;
bool m_sloppy;
const ContextHandler *mp_ctx_handler;
std::vector<std::string> m_match_substrings;

View File

@ -274,7 +274,53 @@ class Tl_TestClass < TestBase
pc._destroy
self.assert_equal(e1.eval, 5)
begin
e1.eval
self.assert_equal(true, false)
rescue => ex
self.assert_equal(ex.to_s, "Parent context was destroyed in Expression::eval")
end
end
# Parent contexts
def test_5_GlobalContext
# this is a new disconnected context
pc = RBA::ExpressionContext::new(nil, nil)
pc.var("A", 10)
# this is a new disconnected context
gc = RBA::ExpressionContext::new(nil, nil)
gc.var("B", 1.5)
e = RBA::Expression::new(gc, pc)
e.text = "B * A"
self.assert_equal(e.eval, 15)
# built-in functions still work
e.text = "pow(A,2)"
self.assert_equal(e.eval, 100)
# but other classes don't
begin
e.text = "Box.new(1, 2, 3, 4)"
self.assert_equal(true, false)
rescue => ex
end
# borrow "Box" from the global context (reference context)
e.import("Box")
e.text = "Box.new(1, 2, 3, 4)"
self.assert_equal(e.eval.to_s, "(1,2;3,4)")
# DBox still does not work
begin
e.text = "DBox.new(1, 2, 3, 4)"
self.assert_equal(true, false)
rescue => ex
end
end