com.squarespace.template.AstEmitter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of template-core Show documentation
Show all versions of template-core Show documentation
Squarespace template compiler
/**
* Copyright (c) 2017 SQUARESPACE, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squarespace.template;
import java.util.EnumMap;
import java.util.List;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.IntNode;
import com.squarespace.template.Instructions.AlternatesWithInst;
import com.squarespace.template.Instructions.BindVarInst;
import com.squarespace.template.Instructions.BlockInst;
import com.squarespace.template.Instructions.CommentInst;
import com.squarespace.template.Instructions.CtxVarInst;
import com.squarespace.template.Instructions.IfInst;
import com.squarespace.template.Instructions.IfPredicateInst;
import com.squarespace.template.Instructions.InjectInst;
import com.squarespace.template.Instructions.MacroInst;
import com.squarespace.template.Instructions.PredicateInst;
import com.squarespace.template.Instructions.RepeatedInst;
import com.squarespace.template.Instructions.RootInst;
import com.squarespace.template.Instructions.SectionInst;
import com.squarespace.template.Instructions.TextInst;
import com.squarespace.template.Instructions.VariableInst;
/**
* Emits a compact abstract syntax tree describing a compiled template, using
* S-expressions encoded in JSON.
*/
public class AstEmitter {
// Syntax tree version indicate by single integer on root node.
private static final JsonNode VERSION = new IntNode(1);
// Instead of "null" or "[]" we append a single zero digit. Empty arrays
// occur frequently, so we save a bit of space here. We use this in places
// where it is highly-likely to occur.
private static final JsonNode FAST_NULL = new IntNode(0);
// No-operator opcode should never occur, but adding for safety.
private static final IntNode NOOP = opcode(-1);
private AstEmitter() {
}
public static JsonNode get(Instruction inst) {
return emit(inst);
}
private static JsonNode emit(Instruction raw) {
InstructionType type = raw.getType();
switch (type) {
case TEXT:
{
ArrayNode obj = composite(raw);
TextInst inst = (TextInst) raw;
obj.add(inst.getView().toString());
return obj;
}
case VARIABLE:
{
ArrayNode obj = composite(raw);
VariableInst inst = (VariableInst) raw;
obj.add(variables(inst.getVariables()));
obj.add(formatters(inst.getFormatters()));
return obj;
}
case SECTION:
{
ArrayNode obj = composite(raw);
SectionInst inst = (SectionInst) raw;
obj.add(variable(inst.getVariable()));
emit(obj, inst);
return obj;
}
case REPEATED:
{
ArrayNode obj = composite(raw);
RepeatedInst inst = (RepeatedInst) raw;
obj.add(variable(inst.getVariable()));
emit(obj, inst);
AlternatesWithInst alt = inst.getAlternatesWith();
if (alt == null) {
obj.add(FAST_NULL);
} else {
obj.add(block(alt.consequent));
}
return obj;
}
case PREDICATE:
case OR_PREDICATE:
{
ArrayNode obj = composite(raw);
PredicateInst inst = (PredicateInst) raw;
Predicate predicate = inst.getPredicate();
if (predicate == null) {
obj.add(FAST_NULL);
} else {
obj.add(predicate.identifier());
}
obj.add(arguments(inst.getArguments()));
emit(obj, inst);
return obj;
}
case BINDVAR:
{
ArrayNode obj = composite(raw);
BindVarInst inst = (BindVarInst) raw;
obj.add(inst.getName());
obj.add(variables(inst.getVariables()));
obj.add(formatters(inst.getFormatters()));
return obj;
}
case CTXVAR:
{
ArrayNode obj = composite(raw);
CtxVarInst inst = (CtxVarInst) raw;
obj.add(inst.getName());
obj.add(bindings(inst.getBindings()));
return obj;
}
case ALTERNATES_WITH:
{
// Inlined as part of REPEATED above.
return NOOP;
}
case IF:
{
if (raw instanceof IfInst) {
ArrayNode obj = composite(raw);
IfInst inst = (IfInst) raw;
obj.add(operators(inst.getOperators()));
obj.add(variables(inst.getVariables()));
emit(obj, inst);
return obj;
} else {
// This form of IF is equivalent to a predicate, so just add a
// predicate to the tree.
ArrayNode obj = JsonUtils.createArrayNode();
obj.add(type(InstructionType.PREDICATE));
IfPredicateInst inst = (IfPredicateInst) raw;
Predicate predicate = inst.getPredicate();
if (predicate == null) {
obj.add(FAST_NULL);
} else {
obj.add(predicate.identifier());
}
obj.add(arguments(inst.getArguments()));
emit(obj, inst);
return obj;
}
}
case INJECT:
{
ArrayNode obj = composite(raw);
InjectInst inst = (InjectInst) raw;
obj.add(inst.variable());
obj.add(inst.filename());
obj.add(arguments(inst.arguments()));
return obj;
}
case MACRO:
{
ArrayNode obj = composite(raw);
MacroInst inst = (MacroInst) raw;
obj.add(inst.name());
obj.add(block(inst.getConsequent()));
return obj;
}
case COMMENT:
{
ArrayNode obj = composite(raw);
CommentInst inst = (CommentInst) raw;
obj.add(inst.getView().toString());
obj.add(inst.isMultiLine() ? 1 : 0);
return obj;
}
case ROOT:
{
ArrayNode obj = composite(raw);
obj.add(VERSION);
emit(obj, (RootInst) raw);
return obj;
}
default:
// Other instructions are atomic; their type is mapped directly to an opcode.
return type(raw.getType());
}
}
private static ArrayNode composite(Instruction inst) {
ArrayNode array = JsonUtils.createArrayNode();
array.add(type(inst.getType()));
return array;
}
private static IntNode type(InstructionType type) {
return OPCODES.getOrDefault(type, NOOP);
}
/**
* Appends the consequent block and alternative instruction to a JSON array.
*/
private static void emit(ArrayNode obj, BlockInst inst) {
obj.add(block(inst.consequent));
obj.add(emit(inst.alternative));
}
/**
* Encodes a block's instructions into a JSON array.
*/
private static JsonNode block(Block block) {
if (block == null) {
return FAST_NULL;
}
List instructions = block.getInstructions();
if (instructions == null) {
return FAST_NULL;
}
ArrayNode array = JsonUtils.createArrayNode();
for (Instruction inst : instructions) {
array.add(emit(inst));
}
return array;
}
/**
* Encodes a list of operators as a JSON array. Each operator is a
* single integer digit, with 0=OR 1=AND.
*/
private static JsonNode operators(List operators) {
ArrayNode array = JsonUtils.createArrayNode();
for (Operator operator : operators) {
array.add(operator == Operator.LOGICAL_AND ? 1 : 0);
}
return array;
}
/**
* Encodes a variable name as a string.
*/
private static ArrayNode variable(Object[] name) {
ArrayNode result = JsonUtils.createArrayNode();
if (name == null) {
result.add("@");
} else {
for (Object obj : name) {
if (obj instanceof Integer) {
result.add((Integer) obj);
} else {
result.add(obj.toString());
}
}
}
return result;
}
/**
* Encodes an array of bindings as a JSON array.
*/
private static ArrayNode bindings(List bindings) {
ArrayNode array = JsonUtils.createArrayNode();
for (Binding b : bindings) {
array.add(binding(b));
}
return array;
}
private static ArrayNode binding(Binding binding) {
ArrayNode array = JsonUtils.createArrayNode();
array.add(binding.getName());
array.add(variable(binding.getReference()));
return array;
}
/**
* Encodes an array of variables as a JSON array.
*/
private static ArrayNode variables(List
© 2015 - 2024 Weber Informatics LLC | Privacy Policy