Skip to content

Instantly share code, notes, and snippets.

@jdanyow
Last active November 20, 2018 07:16
Show Gist options
  • Save jdanyow/c2394c5748d27f3dc77acabc7b8f2752 to your computer and use it in GitHub Desktop.
Save jdanyow/c2394c5748d27f3dc77acabc7b8f2752 to your computer and use it in GitHub Desktop.
AST Visualizer
<template>
<require from="./expression"></require>
<button class="button-outline button-small"
repeat.for="example of examples"
click.delegate="expressionString = example.expression">
${example.name}
</button>
<label class="expression-input">
Binding Expression:
<input type="text" value.bind="expressionString" placeholder="Enter binding expression...">
</label>
<expression if.bind="value" value.bind="value"></expression>
${error}
</template>
import {
inject,
observable,
Parser,
Chain
}
from 'aurelia-framework';
@inject(Parser)
export class App {
@observable expressionString;
value = null;
examples = [
{ name: 'Basic Property', expression: `firstName` },
{ name: 'Property Path', expression: `person.firstName` },
{ name: 'Conditional', expression: `isActive ? 'active' : ''` },
{ name: 'Array Index', expression: `myArray[index]` },
{ name: 'Binary', expression: `x * y` },
{ name: 'Object Literal', expression: `{ x: 3, y: height, z: depth }` },
{ name: 'Literal Array', expression: `[a, 1, 'hello', null, undefined]` },
{ name: 'Call Method', expression: `save(entity)` },
{ name: 'Assignment', expression: `width = rangeInput.value` },
{ name: 'Value Converter', expression: `startDate | dateFormat:'MM/dd/yyyy'` },
{ name: 'Binding Behavior', expression: `lastName & updateTrigger:'blur'` },
{ name: 'Kitchen Sink', expression: `getPosts({ start: minDate, end: maxDate })[0].timestamp | timeAgo & signal:'tick'` },
];
constructor(parser) {
this.parser = parser;
this.expressionString = ``;
}
expressionStringChanged(newValue, oldValue) {
this.error = '';
this.expression = null;
try {
let value = { role: 'Root', expression: this.parser.parse(newValue) };
if (value.expression instanceof Chain) {
value = null;
}
this.value = value;
}
catch(e) {
this.value = null;
this.error = e.toString();
}
}
}
ul.expression-tree {
padding: 0;
margin: 0;
list-style-type: none;
position: relative;
font-family: sans-serif;
font-size: 100%;
}
ul.expression-tree li {
list-style-type: none;
border-left: 2px solid #808080;
margin-left: 1em;
margin-bottom: 0;
}
ul.expression-tree li div {
padding-left: 1em;
position: relative;
}
ul.expression-tree li div::before {
content: '';
position: absolute;
top: 0;
left: -2px;
bottom: 50%;
width: 0.75em;
border: 2px solid #808080;
border-top: 0 none transparent;
border-right: 0 none transparent;
}
ul.expression-tree > li:last-child {
border-left: 2px solid transparent;
}
.expression-type {
color: blue;
font-weight: bold;
}
.expression-role {
color: green;
}
.expression-text {
font-family: monospace;
color: #808080;
background-color: transparent;
}
<template>
<require from="./expression.css"></require>
<ul class="expression-tree">
<div>
<span class="expression-type">${value.expression.constructor.name}</span>
<span class="expression-role">[${value.role}]</span>
<code class="expression-text">${value.expression}</code>
</div>
<li repeat.for="child of children">
<expression value.bind="child"></expression>
</li>
</ul>
</template>
import {
AccessThis,
AccessScope,
AccessMember,
AccessKeyed,
Assign,
Binary,
BindingBehavior,
CallFunction,
CallMember,
CallScope,
Conditional,
LiteralPrimitive,
LiteralArray,
LiteralObject,
LiteralString,
PrefixNot,
ValueConverter,
bindable,
containerless,
}
from 'aurelia-framework';
@containerless
export class Expression {
@bindable value;
children = null;
valueChanged({ role, expression }) {
if (expression instanceof AccessThis) {
this.children = null;
} else if (expression instanceof AccessScope) {
this.children = null;
} else if (expression instanceof AccessMember) {
this.children = [{ role: 'Object', expression: expression.object }];
} else if (expression instanceof AccessKeyed) {
this.children = [
{ role: 'Object', expression: expression.object },
{ role: 'Key', expression: expression.key }
];
} else if (expression instanceof Assign) {
this.children = [
{ role: 'Target', expression: expression.target },
{ role: 'Value', expression: expression.value }
];
} else if (expression instanceof Binary) {
this.children = [
{ role: 'Left', expression: expression.left },
{ role: 'Right', expression: expression.right }
];
} else if (expression instanceof BindingBehavior) {
this.children = [
{ role: 'Target', expression: expression.expression },
...expression.args.map(x => ({ role: 'Argument', expression: x }))
];
} else if (expression instanceof CallFunction) {
this.children = [
{ role: 'Function', expression: expression.func },
...expression.args.map(x => ({ role: 'Argument', expression: x }))
];
} else if (expression instanceof CallMember) {
this.children = [
{ role: 'Object', expression: expression.object },
...expression.args.map(x => ({ role: 'Argument', expression: x }))
];
} else if (expression instanceof CallScope) {
this.children = expression.args.map(x => ({ role: 'Argument', expression: x }));
} else if (expression instanceof Conditional) {
this.children = [
{ role: 'Condition', expression: expression.condition },
{ role: 'True-Value', expression: expression.yes },
{ role: 'False-Value', expression: expression.no }
];
} else if (expression instanceof LiteralPrimitive || expression instanceof LiteralString) {
this.children = null;
} else if (expression instanceof LiteralArray) {
this.children = expression.elements.map(x => ({ role: 'Element', expression: x }));
} else if (expression instanceof LiteralObject) {
this.children = expression.values.map(x => ({ role: 'Property Value', expression: x }));
} else if (expression instanceof PrefixNot) {
this.children = [{ role: 'Target', expression: expression.expression }];
} else if (expression instanceof ValueConverter) {
this.children = [
{ role: 'Target', expression: expression.allArgs[0] },
...expression.args.map(x => ({ role: 'Argument', expression: x }))
];
} else {
this.children = null;
}
}
}
<!doctype html>
<html>
<head>
<title>Aurelia</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.3/normalize.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/milligram/1.1.0/milligram.min.css">
<style>
body {
padding-left: 20px;
padding-right: 20px;
min-height: 300px;
}
.button-small {
font-size: .8rem;
height: 2rem;
line-height: 2rem;
padding: 0 .5rem;
margin-bottom: 0px;
}
.expression-input {
display: block;
margin-top: 10px;
width: 100%;
margin-bottom: 5px;
}
</style>
</head>
<body aurelia-app>
<h1>Loading...</h1>
<script src="https://jdanyow.github.io/rjs-bundle/node_modules/requirejs/require.js"></script>
<script src="https://jdanyow.github.io/rjs-bundle/config.js"></script>
<script src="https://jdanyow.github.io/rjs-bundle/bundles/aurelia.js"></script>
<script src="https://jdanyow.github.io/rjs-bundle/bundles/babel.js"></script>
<script>
require(['aurelia-bootstrapper']);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment