xo-interpreter: implement variable lookup

This commit is contained in:
Roland Conybeare 2025-11-25 12:43:57 -05:00
commit d91a2ae08e
10 changed files with 140 additions and 13 deletions

View file

@ -39,6 +39,10 @@ namespace xo {
template <typename S>
gc_ptr(const gc_ptr<S> & x) : ptr_{x.ptr()} {}
/** runtime downcast. shorthand for dynamic_cast<T*> **/
template <typename S>
static gc_ptr<T> from(const gc_ptr<S> & x) { return gc_ptr<T>{dynamic_cast<T*>(x.ptr())}; }
/** convenience for static asserts **/
static constexpr bool is_gc_ptr = true;
/** see also: xo/refcnt/Refcounted.hpp **/

View file

@ -13,7 +13,7 @@ namespace xo {
bp<Expression>
GlobalSymtab::require_global(const std::string & vname,
bp<Expression> expr)
bp<Expression> expr)
{
this->global_map_[vname] = expr.get();

View file

@ -24,6 +24,11 @@ namespace xo {
/** true iff @p vname is present in Symtab for innermost environment **/
virtual bool local_contains_var(const std::string & vname) const = 0;
/** Fetch storage location for innermost binding of variable with name @p vname.
* nullptr if not found
**/
virtual gp<Object> * lookup_slot(const std::string & vname) = 0;
/** require storage for variable @p v.
* will also establish binding path.
*
@ -31,8 +36,10 @@ namespace xo {
* replacing any previous variable with the same name.
*
* Beware of invalidating type correctness
*
* @return slot address for runtime value of @p v
**/
virtual void establish_var(bp<Variable> v) = 0;
virtual gp<Object> * establish_var(bp<Variable> v) = 0;
//gp<Object> lookup_symbol(const std::string & name) const;
};

View file

@ -21,6 +21,11 @@ namespace xo {
static gp<ExpressionBoxed> make(gc::IAlloc * mm,
bp<Expression> c);
/** runtime downcast **/
static gp<ExpressionBoxed> from(gp<Object> x) {
return gp<ExpressionBoxed>::from(x);
}
const rp<Expression> & contents() const { return contents_; }
// inherited from Object

View file

@ -28,7 +28,8 @@ namespace xo {
// inherited from Env..
virtual bool local_contains_var(const std::string & vname) const final override;
virtual void establish_var(bp<Variable> var) final override;
virtual gp<Object> * lookup_slot(const std::string & vname) final override;
virtual gp<Object> * establish_var(bp<Variable> var) final override;
// inherited from Object..
virtual TaggedPtr self_tp() const final override;

View file

@ -64,10 +64,12 @@ namespace xo {
virtual bool local_contains_var(const std::string & vname) const final override;
virtual gp<Object> * lookup_slot(const std::string & vname) final override;
/** LocalEnv policy is that variable can be established once only.
* For example function arguments must all have distinct names
* For example function arguments must all have distinct names.
**/
virtual void establish_var(bp<Variable> v) final override;
virtual gp<Object> * establish_var(bp<Variable> v) final override;
// inherited from Object..
virtual TaggedPtr self_tp() const final override;

View file

@ -83,10 +83,12 @@ namespace xo {
/** interpret define expression **/
void eval_define_op();
/** continue after establishing value fo rhs of define exprsssion **/
void do_defexpr_assign_op();
/** interpret variable expression **/
void eval_variable_op();
/** goto error state with message @p err **/
void report_error(const std::string & err);

View file

@ -35,9 +35,28 @@ namespace xo {
return symtab_->lookup_local(vname).get();
}
void
gp<Object> *
GlobalEnv::lookup_slot(const std::string & vname)
{
scope log(XO_DEBUG(true), xtag("name", vname));
assert(slot_map_.get());
auto ix = slot_map_->find(vname);
if (ix == slot_map_->end()) {
return nullptr;
} else {
log && log("binding found", xtag("vname", vname));
return &(ix->second);
}
}
gp<Object> *
GlobalEnv::establish_var(bp<Variable> var)
{
scope log(XO_DEBUG(true), xtag("name", var->name()), xtag("type", var->valuetype()));
// Warning: altering declared type for an already-existing variable
// invalidates any type checking that relied on that variable.
//
@ -60,7 +79,12 @@ namespace xo {
this->symtab_->require_global(var->name(), var);
(*this->slot_map_)[var->name()] = gp<Object>();
gp<Object> &slot = (*this->slot_map_)[var->name()];
/* discard any pre-existing value, we're redefining a variable */
slot = gp<Object>();
return &slot;
}
TaggedPtr

View file

@ -52,7 +52,25 @@ namespace xo {
return symtab_->lookup_local(vname);
}
void
gp<Object> *
LocalEnv::lookup_slot(const std::string & vname)
{
binding_path b = symtab_->lookup_local_binding(vname);
if (b.i_link_ == 0) {
assert((b.j_slot_ >= 0) && (static_cast<size_t>(b.j_slot_) < slot_v_.size()));
return &(slot_v_[b.j_slot_]);
}
if (parent_.get()) {
return parent_->lookup_slot(vname);
}
return nullptr;
}
gp<Object> *
LocalEnv::establish_var(bp<Variable> v)
{
assert(v);

View file

@ -149,27 +149,32 @@ namespace xo {
case Opcode::eval:
{
log && log("eval");
log && log("Opcode::eval");
/* generally speaking: opcode will be 1:1 with extypes */
switch (expr_->extype()) {
case exprtype::constant:
log && log("eval -> constant");
this->eval_constant_op();
break;
case exprtype::define:
log && log("eval -> define");
this->eval_define_op();
break;
case exprtype::variable:
log && log("eval -> variable");
this->eval_variable_op();
break;
case exprtype::invalid:
case exprtype::primitive:
case exprtype::assign:
case exprtype::apply:
case exprtype::lambda:
case exprtype::variable:
case exprtype::ifexpr:
case exprtype::sequence:
case exprtype::convert:
@ -266,29 +271,88 @@ namespace xo {
/* when control arrives at .cont_, will have:
* .value_ -> result of evaluating expr->rhs()
*/
this->stack_ = VsmStackFrame::push1(mm, this->stack_, lhs_0, cont_);
/* .stack_:
* frame
* [0] = lhs_0 (boxed lhs Variable)
*/
this->cont_ = &VsmOps::defexpr_assign_op;
}
void
VirtualSchematikaMachine::do_defexpr_assign_op()
{
scope log(XO_DEBUG(true));
/*
* - value: contains result of evaluating rhs of define
* - stack: top frame has 1 slot, holds variable to receive assignment
*/
assert(value_.get());
assert(stack_.get());
assert(env_.get());
gp<VsmStackFrame> sp0 = this->stack_;
bp<Variable> var = Variable::from(ExpressionBoxed::from((*sp0)[0])->contents());
assert(var.get());
gp<Object> * slot = this->env_->establish_var(var);
assert(slot);
*slot = this->value_;
//this->value_ = this->value_; // preserve value from rhs of defexpr
this->stack_ = sp0->parent();
this->pc_ = this->cont_ = sp0->continuation();
}
void
VirtualSchematikaMachine::eval_variable_op()
{
using xo::scm::Variable;
scope log(XO_DEBUG(true));
bp<Variable> var = Variable::from(expr_);
assert(var.get());
assert(env_.get());
const gp<Object> * slot = env_->lookup_slot(var->name());
if (slot) {
this->value_ = *slot;
this->pc_ = cont_;
} else {
/* Unknown variable error will often be recognized in expression parser,
* in such cases this path won't be used.
*
* In interactive environment will need some kind of support for modifying
* code (e.g. replacing top-level functions/variables), and in particular,
* replacements may have different type signature.
* It's possible that allowing for such replacements winds up giving up
* typesafety guarantees. In that case this path may get activated after
* all.
*/
std::string err = tostr("no binding for variable", xtag("name", var->name()));
this->value_ = nullptr;
this->error_ = SchematikaError(err);
/* note: poor man's exception */
this->pc_ = nullptr;
this->cont_ = nullptr;
this->pc_ = cont_;
}
}
} /*namespace scm*/
} /*namespace xo*/