Skip to content

Commit

Permalink
hotfix [assignment]: Fixed assignment with incorrect scope resolution
Browse files Browse the repository at this point in the history
During getters or functions, the context of the given function would
be set to resolve to `self` of the context's scope. This in result
would dereference to the same object when evaluated and therefore
refer to `self` in the given parent scope whose context has been
incorrectly been set.

Signed-off-by: Vihan B <contact@vihan.org>
  • Loading branch information
vihanb committed Nov 23, 2016
1 parent 57f552a commit 27a758e
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 6 deletions.
41 changes: 37 additions & 4 deletions src/interpreter/core/env/func.es6
Expand Up @@ -108,26 +108,39 @@ export default class CheddarFunction extends CheddarClass {
return true;
}

/**
* Executes a function given:
* input: array of arguments
* self: context to run function in
* (i.e. item to set to `self`)
*/
exec(input, self) {
let scope = this.generateScope(input, self);

if (!(scope instanceof CheddarScope))
return scope;

let tmp;

// Detemine if body is JS Function, or Cheddar item
if (typeof this.body === 'function') {
return this.body(
scope,
name => (tmp = scope.accessor(name)) && tmp.Value,
input
);
} else {
// Determine if body is expression or block
let executor = require(
this.body.constructor.name === "StatementExpression" ?
'../eval/eval' :
'../../exec'
);

// Adjust Syntax Tree for proper excution pass:
// 1) Syntax Tree
// 2) Callback scope
// 3) Context data (for private variable accessing)
let res = new executor(
this.body.constructor.name === "StatementExpression" ?
this.body :
Expand All @@ -136,6 +149,7 @@ export default class CheddarFunction extends CheddarClass {
this.data
).exec();

// Handle signals such as break and return
if (res instanceof Signal) {
if (res.is(Signal.RETURN)) {
res = res.data;
Expand All @@ -147,39 +161,51 @@ export default class CheddarFunction extends CheddarClass {
}

generateScope(input, self) {
// The scope to inherit from (static)
let args = new CheddarScope(this.inherited || null);

let CheddarArray = require('../primitives/Array');
let tmp;

// Set `self` variable is applicable
if (self) {
args.setter("self", new CheddarVariable(self, {
Writeable: false
}));
}

// TCO-self reference for recursion
// f in `n f -> ...`
if (this.selfRef) {
args.setter(this.selfRef, new CheddarVariable(this, {}))
}

// Match and verify each argument
for (let i = 0; i < this.args.length; i++) {
tmp = this.args[i][1];

// Pass if undefined
if (!tmp) continue;

if (tmp.Splat === true) {
// Put arugments as a Cheddar array
let splat = new CheddarArray();
splat.init(...input.slice(i));

args.setter(this.args[i][0], new CheddarVariable(
splat
));

// No other arguments should come over this
// This is enforced parse-time
break;
}
else if (input[i]) {
// No modifier so just perform valudation

if (tmp.Type) {
// Check type, handles multiple allowed typed `[A, B]`
// throws applicable error
if (Array.isArray(tmp.Type)) {
if (!tmp.Type.some(
t => input[i] instanceof t
Expand Down Expand Up @@ -208,33 +234,40 @@ export default class CheddarFunction extends CheddarClass {
}`;
}
}

// Simply assigns variable
args.setter(this.args[i][0], new CheddarVariable(
input[i]
));
}
else {
// If no value was passed, we're here

if (tmp.Optional === true) {
// Since no value was passed, and it's optional, just set to null
args.setter(this.args[i][0], new CheddarVariable(
new NIL
));
}
else if (tmp.Default) {
let res = tmp.Default;
// If it's an expression
if (tmp.Default.constructor.name === "StatementExpression") {
let res = new (require('../eval/eval'))(
// Evaluate it in the current context
// Gives access to such variables
res = new (require('../eval/eval'))(
tmp.Default,
args
).exec();

if (typeof res === 'string') {
return res;
}

tmp.Default = res;
}

// Set to resulting default value
args.setter(this.args[i][0], new CheddarVariable(
tmp.Default
res
));
}
else {
Expand Down
9 changes: 7 additions & 2 deletions src/interpreter/core/env/scope.es6
Expand Up @@ -17,6 +17,11 @@ import CheddarVariable from './var';

import { DEFAULT_OP, DEFAULT_RHS_OP, DEFAULT_CAST } from './defaults';

/**
* token: variable to set
* value: value to set
* iv: whether result should be wrapped
*/
function enforceset(token, value, iv) {
let self;

Expand Down Expand Up @@ -84,7 +89,7 @@ export default class CheddarScope {
static accessor(token) {
let value = this.Scope.get(token);

if (value && value.Value) {
if (value && value.Value && token !== "self") {
value.Value.Reference = token;
value.Value.scope = this;
}
Expand Down Expand Up @@ -140,7 +145,7 @@ export default class CheddarScope {
value = null;
}

if (value && value.Value) {
if (value && value.Value && token !== "self") {
value.Value.Reference = token;
value.Value.scope = this;
}
Expand Down

0 comments on commit 27a758e

Please sign in to comment.