Skip to content

Commit

Permalink
dynamically scoped variable accessors
Browse files Browse the repository at this point in the history
This adds the expressison syntax `{{-get-dynamic-var "yourVariableName"}}` and the statement syntax `{{#-with-dynamic-var "yourVariableName" someValue}}...{{/with-dynamic-var}}`.
  • Loading branch information
ef4 committed Aug 29, 2016
1 parent a41a900 commit ca894ed
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 2 deletions.
4 changes: 4 additions & 0 deletions packages/glimmer-compiler/lib/javascript-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ export default class JavaScriptCompiler {
this.template.yields.add(name);
}

getDynamicVar(varName: string) {
this.pushValue<Expressions.GetDynamicVar>(['get-dynamic-var', varName]);
}

/// Expressions

literal(value: Expressions.Value | undefined) {
Expand Down
24 changes: 23 additions & 1 deletion packages/glimmer-compiler/lib/template-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,13 +180,20 @@ export default class TemplateCompiler {
this.opcode('hasBlockParams', action, name);
}

getDynamicVar(varName: string) {
this.opcode('getDynamicVar', null, varName);
}

builtInHelper(expr) {
if (isHasBlock(expr)) {
let name = assertValidHasBlock(expr);
this.hasBlock(name, expr);
} else if (isHasBlockParams(expr)) {
let name = assertValidHasBlockParams(expr);
this.hasBlockParams(name, expr);
} else if (isGetDynamicVar(expr)) {
let varName = assertValidGetDynamicVarParams(expr);
this.getDynamicVar(varName);
}
}

Expand Down Expand Up @@ -352,9 +359,14 @@ function isHasBlockParams({ path }) {
return path.original === 'has-block-params';
}

function isGetDynamicVar({ path }) {
return path.original === '-get-dynamic-var';
}

function isBuiltInHelper(expr) {
return isHasBlock(expr)
|| isHasBlockParams(expr);
|| isHasBlockParams(expr)
|| isGetDynamicVar(expr);
}

function assertValidYield({ hash }): string {
Expand Down Expand Up @@ -398,3 +410,13 @@ function assertValidHasBlockParams({ params }): string {
throw new Error(`has-block-params only takes a single positional argument`);
}
}

function assertValidGetDynamicVarParams({ params }): string {
if (params.length !== 1) {
throw new Error(`get-dynamic-var requires exactly one parameter (the name of the dynamic variable you wish to access)`);
}
if (params[0].type !== 'StringLiteral') {
throw new Error(`get-dynamic-var only accepts string literals`);
}
return params[0].value;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import VM from '../../vm/append';
import { CompiledExpression } from '../expressions';
import { UNDEFINED_REFERENCE } from '../../references';
import { Opaque } from 'glimmer-util';
import { PathReference } from 'glimmer-reference';

export default class CompiledGetDynamicVar extends CompiledExpression<any> {
public type = "get-dynamic-var";
public varName: string;

constructor({ varName }: { varName: string }) {
super();
this.varName = varName;
}

evaluate(vm: VM): PathReference<Opaque> {
let scope = vm.dynamicScope();
if (scope.hasOwnProperty(this.varName)) {
return scope[this.varName];
} else {
return UNDEFINED_REFERENCE;
}
}

toJSON(): string {
return `get-dynamic-var(${this.varName})`;
}
}
3 changes: 3 additions & 0 deletions packages/glimmer-runtime/lib/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import * as Syntax from './syntax/core';
import IfSyntax from './syntax/builtins/if';
import UnlessSyntax from './syntax/builtins/unless';
import WithSyntax from './syntax/builtins/with';
import WithDynamicVarSyntax from './syntax/builtins/with-dynamic-var';
import EachSyntax from './syntax/builtins/each';
import PartialSyntax from './syntax/builtins/partial';

Expand Down Expand Up @@ -174,6 +175,8 @@ export abstract class Environment {
return new IfSyntax({ args, templates });
case 'with':
return new WithSyntax({ args, templates });
case '-with-dynamic-var':
return new WithDynamicVarSyntax({ args, templates });
case 'unless':
return new UnlessSyntax({ args, templates });
}
Expand Down
44 changes: 44 additions & 0 deletions packages/glimmer-runtime/lib/syntax/builtins/with-dynamic-var.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {
Statement as StatementSyntax
} from '../../syntax';

import OpcodeBuilderDSL from '../../compiled/opcodes/builder';
import * as Syntax from '../core';
import Environment from '../../environment';
import { default as VM } from '../../vm/append';
import { DynamicScope } from '../../environment';
import { EvaluatedArgs } from '../../compiled/expressions/args';

export default class WithDynamicVarSyntax extends StatementSyntax {
type = "with-dynamic-var-statement";

public args: Syntax.Args;
public templates: Syntax.Templates;
public isStatic = false;

constructor({ args, templates }: { args: Syntax.Args, templates: Syntax.Templates }) {
super();
this.args = args;
this.templates = templates;
}

compile(dsl: OpcodeBuilderDSL, env: Environment) {
let callback = (_vm: VM, _scope: DynamicScope) => {
let vm = _vm as any;
let scope = _scope as any;

let args: EvaluatedArgs = vm.frame.getArgs();

scope[<any>args.positional.values[0].value()] = args.positional.values[1];
};

let { args, templates } = this;

dsl.unit({ templates }, dsl => {
dsl.putArgs(args);
dsl.setupDynamicScope(callback);
dsl.evaluate('default');
dsl.popDynamicScope();
});
}
}
29 changes: 29 additions & 0 deletions packages/glimmer-runtime/lib/syntax/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ import {

import CompiledHasBlock from '../compiled/expressions/has-block';

import CompiledGetDynamicVar from '../compiled/expressions/get-dynamic-var';

import CompiledHasBlockParams from '../compiled/expressions/has-block-params';

import CompiledHelper from '../compiled/expressions/helper';
Expand Down Expand Up @@ -970,6 +972,33 @@ export class HasBlockParams extends ExpressionSyntax<boolean> {
}
}

export class GetDynamicVar extends ExpressionSyntax<any> {
type = "get-dynamic-var";

static fromSpec(sexp: SerializedExpressions.GetDynamicVar): GetDynamicVar {
let [, varName] = sexp;
return new GetDynamicVar({ varName });
}

static build(varName: string): GetDynamicVar {
console.log("build a dynamic var reference");
return new this({ varName });
}

varName: string;

constructor({ varName }: { varName: string }) {
super();
this.varName = varName;
}

compile(compiler: SymbolLookup, env: Environment): CompiledGetDynamicVar {
return new CompiledGetDynamicVar({
varName: this.varName
});
}
}

export class Concat {
type = "concat";

Expand Down
5 changes: 4 additions & 1 deletion packages/glimmer-runtime/lib/syntax/expressions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
HasBlockParams as HasBlockParamsSyntax,
Helper as HelperSyntax,
Unknown as UnknownSyntax,
GetDynamicVar as GetDynamicVarSyntax
} from './core';

import {
Expand All @@ -25,7 +26,8 @@ const {
isHelper,
isUnknown,
isPrimitiveValue,
isUndefined
isUndefined,
isGetDynamicVar
} = SerializedExpressions;

export default function(sexp: SerializedExpression): any {
Expand All @@ -39,6 +41,7 @@ export default function(sexp: SerializedExpression): any {
if (isUnknown(sexp)) return UnknownSyntax.fromSpec(sexp);
if (isHasBlock(sexp)) return HasBlockSyntax.fromSpec(sexp);
if (isHasBlockParams(sexp)) return HasBlockParamsSyntax.fromSpec(sexp);
if (isGetDynamicVar(sexp)) return GetDynamicVarSyntax.fromSpec(sexp);

throw new Error(`Unexpected wire format: ${JSON.stringify(sexp)}`);
};
37 changes: 37 additions & 0 deletions packages/glimmer-runtime/tests/ember-component-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,43 @@ testComponent('parameterized has-block (concatted attr, default) when block not
expected: '<button data-has-block="is-false"></button>'
});

module('Dynamically-scoped variable accessors');

testComponent('Can get and set dynamic variable', {
layout: '{{#-with-dynamic-var "myKeyword" @value}}{{yield}}{{/-with-dynamic-var}}',
invokeAs: {
template: '{{-get-dynamic-var "myKeyword"}}',
context: { value: "hello" },
args: { value: 'value' }
},
expected: 'hello',
updates: [{
expected: 'hello'
}, {
context: { value: 'goodbye' },
expected: 'goodbye'
}]
});

testComponent('Can shadow existing dynamic variable', {
layout: '{{#-with-dynamic-var "myKeyword" @outer}}<div>{{-get-dynamic-var "myKeyword"}}</div>{{#-with-dynamic-var "myKeyword" @inner}}{{yield}}{{/-with-dynamic-var}}<div>{{-get-dynamic-var "myKeyword"}}</div>{{/-with-dynamic-var}}',
invokeAs: {
template: '<div>{{-get-dynamic-var "myKeyword"}}</div>',
context: { outer: 'original', inner: 'shadowed' },
args: { outer: 'outer', inner: 'inner'}
},
expected: '<div>original</div><div>shadowed</div><div>original</div>',
updates: [{
expected: '<div>original</div><div>shadowed</div><div>original</div>'
}, {
context: { outer: 'original2', inner: 'shadowed' },
expected: '<div>original2</div><div>shadowed</div><div>original2</div>'
}, {
context: { outer: 'original2', inner: 'shadowed2' },
expected: '<div>original2</div><div>shadowed2</div><div>original2</div>'
}]
});

module('Components - has-block-params helper');

testComponent('parameterized has-block-params (subexpr, inverse) when inverse supplied without block params', {
Expand Down
3 changes: 3 additions & 0 deletions packages/glimmer-wire-format/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export namespace Expressions {
export type Get = ['get', Path];
export type SelfGet = ['self-get', Path];
export type Value = str | number | boolean | null; // tslint:disable-line
export type GetDynamicVar = ['get-dynamic-var', str];
export type HasBlock = ['has-block', str];
export type HasBlockParams = ['has-block-params', str];
export type Undefined = ['undefined'];
Expand All @@ -56,6 +57,7 @@ export namespace Expressions {
| Helper
| Undefined
| Value
| GetDynamicVar
;

export interface Concat extends Array<any> {
Expand All @@ -79,6 +81,7 @@ export namespace Expressions {
export const isHasBlock = is<HasBlock>('has-block');
export const isHasBlockParams = is<HasBlockParams>('has-block-params');
export const isUndefined = is<Undefined>('undefined');
export const isGetDynamicVar = is<GetDynamicVar>('get-dynamic-var');

export function isPrimitiveValue(value: any): value is Value {
if (value === null) {
Expand Down

0 comments on commit ca894ed

Please sign in to comment.