/******************************************************************************* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.sling.scripting.sightly.compiler; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.sling.scripting.sightly.compiler.backend.BackendCompiler; import org.apache.sling.scripting.sightly.compiler.commands.CommandStream; import org.apache.sling.scripting.sightly.impl.compiler.CompilationResultImpl; import org.apache.sling.scripting.sightly.impl.compiler.CompilerFrontend; import org.apache.sling.scripting.sightly.impl.compiler.CompilerMessageImpl; import org.apache.sling.scripting.sightly.impl.compiler.PushStream; import org.apache.sling.scripting.sightly.impl.compiler.debug.SanityChecker; import org.apache.sling.scripting.sightly.impl.compiler.frontend.SimpleFrontend; import org.apache.sling.scripting.sightly.impl.compiler.optimization.CoalescingWrites; import org.apache.sling.scripting.sightly.impl.compiler.optimization.DeadCodeRemoval; import org.apache.sling.scripting.sightly.impl.compiler.optimization.SequenceStreamTransformer; import org.apache.sling.scripting.sightly.impl.compiler.optimization.StreamTransformer; import org.apache.sling.scripting.sightly.impl.compiler.optimization.SyntheticMapRemoval; import org.apache.sling.scripting.sightly.impl.compiler.optimization.UnusedVariableRemoval; import org.apache.sling.scripting.sightly.impl.compiler.optimization.reduce.ConstantFolding; import org.apache.sling.scripting.sightly.impl.filter.Filter; import org.apache.sling.scripting.sightly.impl.filter.FormatFilter; import org.apache.sling.scripting.sightly.impl.filter.I18nFilter; import org.apache.sling.scripting.sightly.impl.filter.JoinFilter; import org.apache.sling.scripting.sightly.impl.filter.URIManipulationFilter; import org.apache.sling.scripting.sightly.impl.filter.XSSFilter; import org.apache.sling.scripting.sightly.impl.plugin.AttributePlugin; import org.apache.sling.scripting.sightly.impl.plugin.CallPlugin; import org.apache.sling.scripting.sightly.impl.plugin.ElementPlugin; import org.apache.sling.scripting.sightly.impl.plugin.IncludePlugin; import org.apache.sling.scripting.sightly.impl.plugin.ListPlugin; import org.apache.sling.scripting.sightly.impl.plugin.Plugin; import org.apache.sling.scripting.sightly.impl.plugin.RepeatPlugin; import org.apache.sling.scripting.sightly.impl.plugin.ResourcePlugin; import org.apache.sling.scripting.sightly.impl.plugin.TemplatePlugin; import org.apache.sling.scripting.sightly.impl.plugin.TestPlugin; import org.apache.sling.scripting.sightly.impl.plugin.TextPlugin; import org.apache.sling.scripting.sightly.impl.plugin.UnwrapPlugin; import org.apache.sling.scripting.sightly.impl.plugin.UsePlugin; import org.osgi.service.component.annotations.Component; /** * <p> * The {@link SightlyCompiler} interprets a HTL script and transforms it internally into a {@link CommandStream}. The * {@link CommandStream} can be fed to a {@link BackendCompiler} for transforming the stream into executable code, either by * transpiling the commands to a JVM supported language or by directly executing them. * </p> */ @Component( service = SightlyCompiler.class ) public final class SightlyCompiler { private StreamTransformer optimizer; private CompilerFrontend frontend; public SightlyCompiler() { ArrayList<StreamTransformer> transformers = new ArrayList<>(5); transformers.add(ConstantFolding.transformer()); transformers.add(DeadCodeRemoval.transformer()); transformers.add(SyntheticMapRemoval.TRANSFORMER); transformers.add(UnusedVariableRemoval.TRANSFORMER); transformers.add(CoalescingWrites.TRANSFORMER); optimizer = new SequenceStreamTransformer(transformers); // register plugins final List<Plugin> plugins = new ArrayList<>(12); plugins.add(new AttributePlugin()); plugins.add(new CallPlugin()); plugins.add(new ElementPlugin()); plugins.add(new IncludePlugin()); plugins.add(new ListPlugin()); plugins.add(new RepeatPlugin()); plugins.add(new ResourcePlugin()); plugins.add(new TemplatePlugin()); plugins.add(new TestPlugin()); plugins.add(new TextPlugin()); plugins.add(new UnwrapPlugin()); plugins.add(new UsePlugin()); Collections.sort(plugins); // register filters final List<Filter> filters = new ArrayList<>(5); filters.add(I18nFilter.getInstance()); filters.add(FormatFilter.getInstance()); filters.add(JoinFilter.getInstance()); filters.add(URIManipulationFilter.getInstance()); filters.add(XSSFilter.getInstance()); Collections.sort(filters); frontend = new SimpleFrontend(plugins, filters); } /** * Compiles a {@link CompilationUnit}. * * @param compilationUnit a compilation unit * @return the compilation result */ public CompilationResult compile(CompilationUnit compilationUnit) { return compile(compilationUnit, null); } /** * Compiles a {@link CompilationUnit}, passing the processed {@link CommandStream} to the provided {@link BackendCompiler}. * * @param compilationUnit a compilation unit * @param backendCompiler the backend compiler * @return the compilation result */ public CompilationResult compile(CompilationUnit compilationUnit, BackendCompiler backendCompiler) { String scriptName = compilationUnit.getScriptName(); String scriptSource = null; PushStream stream = new PushStream(); SanityChecker.attachChecker(stream); CommandStream optimizedStream = optimizer.transform(stream); CompilationResultImpl compilationResult = new CompilationResultImpl(optimizedStream); try { scriptSource = IOUtils.toString(compilationUnit.getScriptReader()); //optimizedStream.addHandler(LoggingHandler.INSTANCE); if (backendCompiler != null) { backendCompiler.handle(optimizedStream); } frontend.compile(stream, scriptSource); for (PushStream.StreamMessage w : stream.getWarnings()) { ScriptError warning = getScriptError(scriptSource, w.getCode(), 1, 0, w.getMessage()); compilationResult.getWarnings().add(new CompilerMessageImpl(scriptName, warning.errorMessage, warning.lineNumber, warning .column)); } } catch (SightlyCompilerException e) { ScriptError scriptError = getScriptError(scriptSource, e.getOffendingInput(), e.getLine(), e.getColumn(), e.getMessage()); compilationResult.getErrors().add(new CompilerMessageImpl(scriptName, scriptError.errorMessage, scriptError.lineNumber, scriptError.column)); } catch (IOException e) { throw new SightlyCompilerException("Unable to read source code from CompilationUnit identifying script " + scriptName, e); } compilationResult.seal(); return compilationResult; } private ScriptError getScriptError(String documentFragment, String offendingInput, int lineOffset, int columnOffset, String message) { if (StringUtils.isNotEmpty(offendingInput)) { int offendingInputIndex = documentFragment.indexOf(offendingInput); if (offendingInputIndex > -1) { String textBeforeError = documentFragment.substring(0, offendingInputIndex); int line = lineOffset; int lastNewLineIndex = 0; for (String s : new String[] {"\r\n", "\r", "\n"}) { int l = textBeforeError.split(s, -1).length - 1; if (l + lineOffset > line) { line = l + lineOffset; int ix = textBeforeError.lastIndexOf(s); if (ix > 0) { lastNewLineIndex = ix + s.length() - 1; } } } int column = textBeforeError.substring(lastNewLineIndex).length(); if (column != columnOffset) { column +=columnOffset; } return new ScriptError(line, column, offendingInput + ": " + message); } } return new ScriptError(lineOffset, columnOffset, message); } private static class ScriptError { private int lineNumber; private int column; private String errorMessage; public ScriptError(int lineNumber, int column, String errorMessage) { this.lineNumber = lineNumber; this.column = column; this.errorMessage = errorMessage; } } }