/*
* Copyright 2008 Google 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.google.template.soy;
import com.google.auto.value.AutoValue;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.template.soy.base.internal.IdGenerator;
import com.google.template.soy.base.internal.IncrementingIdGenerator;
import com.google.template.soy.base.internal.SoyFileSupplier;
import com.google.template.soy.error.ErrorReporter;
import com.google.template.soy.passes.PassManager;
import com.google.template.soy.shared.SoyAstCache;
import com.google.template.soy.shared.SoyAstCache.VersionedFile;
import com.google.template.soy.soyparse.SoyFileParser;
import com.google.template.soy.soytree.SoyFileNode;
import com.google.template.soy.soytree.SoyFileSetNode;
import com.google.template.soy.soytree.TemplateRegistry;
import com.google.template.soy.types.SoyTypeRegistry;
import java.io.IOException;
import java.io.Reader;
import javax.annotation.Nullable;
/**
* Static functions for parsing a set of Soy files into a {@link SoyFileSetNode}.
*
* <p>Important: Do not use outside of Soy code (treat as superpackage-private).
*
*/
public final class SoyFileSetParser {
/** A simple tuple for the result of a parse operation. */
@AutoValue
public abstract static class ParseResult {
static ParseResult create(SoyFileSetNode soyTree, TemplateRegistry registry) {
return new AutoValue_SoyFileSetParser_ParseResult(soyTree, registry);
}
public abstract SoyFileSetNode fileSet();
public abstract TemplateRegistry registry();
}
/** The type registry to resolve type names. */
private final SoyTypeRegistry typeRegistry;
/** Optional file cache. */
@Nullable private final SoyAstCache cache;
/** The suppliers of the Soy files to parse. */
private final ImmutableMap<String, ? extends SoyFileSupplier> soyFileSuppliers;
/** Parsing passes. null means that they are disabled. */
@Nullable private final PassManager passManager;
/** For reporting parse errors. */
private final ErrorReporter errorReporter;
/**
* @param typeRegistry The type registry to resolve type names.
* @param astCache The AST cache to use, if any.
* @param soyFileSuppliers The suppliers for the Soy files. Each must have a unique file name.
*/
public SoyFileSetParser(
SoyTypeRegistry typeRegistry,
@Nullable SoyAstCache astCache,
ImmutableMap<String, ? extends SoyFileSupplier> soyFileSuppliers,
@Nullable PassManager passManager,
ErrorReporter errorReporter) {
Preconditions.checkArgument(
(astCache == null) || (passManager != null),
"AST caching is only allowed when all parsing and checking passes are enabled, to avoid "
+ "caching inconsistent versions");
this.typeRegistry = typeRegistry;
this.cache = astCache;
this.soyFileSuppliers = soyFileSuppliers;
this.errorReporter = errorReporter;
this.passManager = passManager;
}
/** Parses a set of Soy files, returning a structure containing the parse tree and any errors. */
public ParseResult parse() {
try {
return parseWithVersions();
} catch (IOException e) {
// parse has 9 callers in SoyFileSet, and those are public API methods,
// whose signatures it is infeasible to change.
throw new RuntimeException(e);
}
}
/**
* Parses a set of Soy files, returning a structure containing the parse tree and template
* registry.
*/
private ParseResult parseWithVersions() throws IOException {
Preconditions.checkState(
(cache == null) || passManager != null,
"AST caching is only allowed when all parsing and checking passes are enabled, to avoid "
+ "caching inconsistent versions");
IdGenerator nodeIdGen =
(cache != null) ? cache.getNodeIdGenerator() : new IncrementingIdGenerator();
SoyFileSetNode soyTree = new SoyFileSetNode(nodeIdGen.genId(), nodeIdGen);
boolean filesWereSkipped = false;
for (SoyFileSupplier fileSupplier : soyFileSuppliers.values()) {
SoyFileSupplier.Version version = fileSupplier.getVersion();
VersionedFile cachedFile =
cache != null ? cache.get(fileSupplier.getFilePath(), version) : null;
SoyFileNode node;
if (cachedFile == null) {
//noinspection SynchronizationOnLocalVariableOrMethodParameter IntelliJ
synchronized (nodeIdGen) { // Avoid using the same ID generator in multiple threads.
node = parseSoyFileHelper(fileSupplier, nodeIdGen, typeRegistry);
// TODO(user): implement error recovery and keep on trucking in order to display
// as many errors as possible. Currently, the later passes just spew NPEs if run on
// a malformed parse tree.
if (node == null) {
filesWereSkipped = true;
continue;
}
if (passManager != null) {
// Run passes that are considered part of initial parsing.
passManager.runSingleFilePasses(node, nodeIdGen);
}
}
// Run passes that check the tree.
if (cache != null) {
cache.put(fileSupplier.getFilePath(), VersionedFile.of(node, version));
}
} else {
node = cachedFile.file();
}
soyTree.addChild(node);
}
TemplateRegistry registry = new TemplateRegistry(soyTree, errorReporter);
// Run passes that check the tree iff we successfully parsed every file.
if (!filesWereSkipped && passManager != null) {
passManager.runWholeFilesetPasses(registry, soyTree);
}
return ParseResult.create(soyTree, registry);
}
/**
* Private helper for {@code parseWithVersions()} to parse one Soy file.
*
* @param soyFileSupplier Supplier of the Soy file content and path.
* @param nodeIdGen The generator of node ids.
* @return The resulting parse tree for one Soy file and the version from which it was parsed.
*/
private SoyFileNode parseSoyFileHelper(
SoyFileSupplier soyFileSupplier, IdGenerator nodeIdGen, SoyTypeRegistry typeRegistry)
throws IOException {
try (Reader soyFileReader = soyFileSupplier.open()) {
return new SoyFileParser(
typeRegistry,
nodeIdGen,
soyFileReader,
soyFileSupplier.getSoyFileKind(),
soyFileSupplier.getFilePath(),
errorReporter)
.parseSoyFile();
}
}
}