diff options
Diffstat (limited to 'src/net/sourceforge/plantuml/tim/TContext.java')
-rw-r--r-- | src/net/sourceforge/plantuml/tim/TContext.java | 629 |
1 files changed, 629 insertions, 0 deletions
diff --git a/src/net/sourceforge/plantuml/tim/TContext.java b/src/net/sourceforge/plantuml/tim/TContext.java new file mode 100644 index 0000000..d1a25e8 --- /dev/null +++ b/src/net/sourceforge/plantuml/tim/TContext.java @@ -0,0 +1,629 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009-2020, Arnaud Roques + * + * Project Info: http://plantuml.com + * + * If you like this project or if you find it useful, you can support us at: + * + * http://plantuml.com/patreon (only 1$ per month!) + * http://plantuml.com/paypal + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * + * Original Author: Arnaud Roques + * + */ +package net.sourceforge.plantuml.tim; + +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import net.sourceforge.plantuml.DefinitionsContainer; +import net.sourceforge.plantuml.FileSystem; +import net.sourceforge.plantuml.LineLocation; +import net.sourceforge.plantuml.StringLocated; +import net.sourceforge.plantuml.command.CommandExecutionResult; +import net.sourceforge.plantuml.json.JsonObject; +import net.sourceforge.plantuml.json.JsonValue; +import net.sourceforge.plantuml.preproc.Defines; +import net.sourceforge.plantuml.preproc.FileWithSuffix; +import net.sourceforge.plantuml.preproc.ImportedFiles; +import net.sourceforge.plantuml.preproc.ReadLine; +import net.sourceforge.plantuml.preproc.ReadLineList; +import net.sourceforge.plantuml.preproc.ReadLineReader; +import net.sourceforge.plantuml.preproc.StartDiagramExtractReader; +import net.sourceforge.plantuml.preproc.Sub; +import net.sourceforge.plantuml.preproc.UncommentReadLine; +import net.sourceforge.plantuml.preproc2.PreprocessorIncludeStrategy; +import net.sourceforge.plantuml.preproc2.PreprocessorUtils; +import net.sourceforge.plantuml.tim.expression.Knowledge; +import net.sourceforge.plantuml.tim.expression.TValue; +import net.sourceforge.plantuml.tim.iterator.CodeIterator; +import net.sourceforge.plantuml.tim.iterator.CodeIteratorAffectation; +import net.sourceforge.plantuml.tim.iterator.CodeIteratorForeach; +import net.sourceforge.plantuml.tim.iterator.CodeIteratorFunction; +import net.sourceforge.plantuml.tim.iterator.CodeIteratorIf; +import net.sourceforge.plantuml.tim.iterator.CodeIteratorImpl; +import net.sourceforge.plantuml.tim.iterator.CodeIteratorInnerComment; +import net.sourceforge.plantuml.tim.iterator.CodeIteratorLegacyDefine; +import net.sourceforge.plantuml.tim.iterator.CodeIteratorLongComment; +import net.sourceforge.plantuml.tim.iterator.CodeIteratorShortComment; +import net.sourceforge.plantuml.tim.iterator.CodeIteratorSub; +import net.sourceforge.plantuml.tim.iterator.CodeIteratorWhile; +import net.sourceforge.plantuml.tim.stdlib.AlwaysFalse; +import net.sourceforge.plantuml.tim.stdlib.AlwaysTrue; +import net.sourceforge.plantuml.tim.stdlib.CallUserFunction; +import net.sourceforge.plantuml.tim.stdlib.DateFunction; +import net.sourceforge.plantuml.tim.stdlib.Dirpath; +import net.sourceforge.plantuml.tim.stdlib.FileExists; +import net.sourceforge.plantuml.tim.stdlib.Filename; +import net.sourceforge.plantuml.tim.stdlib.FunctionExists; +import net.sourceforge.plantuml.tim.stdlib.GetVariableValue; +import net.sourceforge.plantuml.tim.stdlib.GetVersion; +import net.sourceforge.plantuml.tim.stdlib.Getenv; +import net.sourceforge.plantuml.tim.stdlib.IntVal; +import net.sourceforge.plantuml.tim.stdlib.InvokeVoidFunction; +import net.sourceforge.plantuml.tim.stdlib.LogicalNot; +import net.sourceforge.plantuml.tim.stdlib.Lower; +import net.sourceforge.plantuml.tim.stdlib.RetrieveVoidFunction; +import net.sourceforge.plantuml.tim.stdlib.SetVariableValue; +import net.sourceforge.plantuml.tim.stdlib.Strlen; +import net.sourceforge.plantuml.tim.stdlib.Strpos; +import net.sourceforge.plantuml.tim.stdlib.Substr; +import net.sourceforge.plantuml.tim.stdlib.Upper; +import net.sourceforge.plantuml.tim.stdlib.VariableExists; + +public class TContext { + + private final List<StringLocated> resultList = new ArrayList<StringLocated>(); + private final List<StringLocated> debug = new ArrayList<StringLocated>(); + + public final FunctionsSet functionsSet = new FunctionsSet(); + + private ImportedFiles importedFiles; + private final String charset; + + private final Map<String, Sub> subs = new HashMap<String, Sub>(); + private final DefinitionsContainer definitionsContainer; + + // private final Set<FileWithSuffix> usedFiles = new HashSet<FileWithSuffix>(); + private final Set<FileWithSuffix> filesUsedCurrent = new HashSet<FileWithSuffix>(); + + private void addStandardFunctions(Defines defines) { + functionsSet.addFunction(new Strlen()); + functionsSet.addFunction(new Substr()); + functionsSet.addFunction(new FileExists()); + functionsSet.addFunction(new Getenv()); + functionsSet.addFunction(new Dirpath(defines)); + functionsSet.addFunction(new Filename(defines)); + functionsSet.addFunction(new DateFunction()); + functionsSet.addFunction(new Strpos()); + functionsSet.addFunction(new InvokeVoidFunction()); + functionsSet.addFunction(new AlwaysFalse()); + functionsSet.addFunction(new AlwaysTrue()); + functionsSet.addFunction(new LogicalNot()); + functionsSet.addFunction(new FunctionExists()); + functionsSet.addFunction(new VariableExists()); + functionsSet.addFunction(new CallUserFunction()); + functionsSet.addFunction(new RetrieveVoidFunction()); + functionsSet.addFunction(new SetVariableValue()); + functionsSet.addFunction(new GetVariableValue()); + functionsSet.addFunction(new IntVal()); + functionsSet.addFunction(new GetVersion()); + functionsSet.addFunction(new Upper()); + functionsSet.addFunction(new Lower()); + // !exit + // !log + // %min + // %max + // Regexp + // %plantuml_version + // %time + // %trim + // %str_replace + } + + public TContext(ImportedFiles importedFiles, Defines defines, String charset, + DefinitionsContainer definitionsContainer) { + this.definitionsContainer = definitionsContainer; + this.importedFiles = importedFiles; + this.charset = charset; + this.addStandardFunctions(defines); + } + + public Knowledge asKnowledge(final TMemory memory) { + return new Knowledge() { + + public TValue getVariable(String name) { + if (name.contains(".")) { + return fromJson(memory, name); + } + return memory.getVariable(name); + } + + public TFunction getFunction(TFunctionSignature name) { + return functionsSet.getFunctionSmart(name); + } + }; + } + + private TValue fromJson(TMemory memory, String name) { + final int x = name.indexOf('.'); + final TValue data = memory.getVariable(name.substring(0, x)); + if (data == null) { + return null; + } + final JsonObject json = (JsonObject) data.toJson(); +// System.err.println("json=" + json); +// System.err.println("json=" + json.getClass()); +// System.err.println("json2=" + name.substring(x + 1)); + final JsonValue result = json.get(name.substring(x + 1)); +// System.err.println("result=" + result); + return TValue.fromJson(result); + } + + private CodeIterator buildCodeIterator(TMemory memory, List<StringLocated> body) { + final CodeIterator it10 = new CodeIteratorImpl(body); + final CodeIterator it20 = new CodeIteratorLongComment(it10, debug); + final CodeIterator it30 = new CodeIteratorShortComment(it20, debug); + final CodeIterator it40 = new CodeIteratorInnerComment(it30); + final CodeIterator it50 = new CodeIteratorSub(it40, subs, this, memory); + final CodeIterator it60 = new CodeIteratorFunction(it50, this, memory, functionsSet, debug); + final CodeIterator it70 = new CodeIteratorIf(it60, this, memory, debug); + final CodeIterator it80 = new CodeIteratorLegacyDefine(it70, this, memory, functionsSet, debug); + final CodeIterator it90 = new CodeIteratorWhile(it80, this, memory, debug); + final CodeIterator it100 = new CodeIteratorForeach(it90, this, memory, debug); + final CodeIterator it110 = new CodeIteratorAffectation(it100, this, memory, debug); + + final CodeIterator it = it110; + return it; + } + + public void executeLines(TMemory memory, List<StringLocated> body, TFunctionType ftype) + throws EaterExceptionLocated { + final CodeIterator it = buildCodeIterator(memory, body); + + StringLocated s = null; + try { + while ((s = it.peek()) != null) { + executeOneLineSafe(memory, s, ftype); + it.next(); + } + } catch (EaterException e) { + throw e.withLocation(s); + } + + } + + private void executeLinesInternal(TMemory memory, List<StringLocated> body, TFunctionType ftype) + throws EaterExceptionLocated, EaterException { + final CodeIterator it = buildCodeIterator(memory, body); + + StringLocated s = null; + while ((s = it.peek()) != null) { + executeOneLineSafe(memory, s, ftype); + it.next(); + } + + } + + private void executeOneLineSafe(TMemory memory, StringLocated s, TFunctionType ftype) + throws EaterException, EaterExceptionLocated { + try { + this.debug.add(s); + executeOneLineNotSafe(memory, s, ftype); + } catch (Exception e) { + if (e instanceof EaterException) + throw (EaterException) e; + if (e instanceof EaterExceptionLocated) + throw (EaterExceptionLocated) e; + e.printStackTrace(); + throw EaterException.located("Fatal parsing error", s); + } + } + + private void executeOneLineNotSafe(TMemory memory, StringLocated s, TFunctionType ftype) + throws EaterException, EaterExceptionLocated { + final TLineType type = s.getType(); + + if (type == TLineType.INCLUDESUB) { + this.executeIncludesub(memory, s); + return; + } else if (type == TLineType.INCLUDE) { + this.executeInclude(memory, s); + return; + } else if (type == TLineType.INCLUDE_DEF) { + this.executeIncludeDef(memory, s); + return; + } else if (type == TLineType.IMPORT) { + this.executeImport(memory, s); + return; + } + + if (type == TLineType.DUMP_MEMORY) { + this.executeDumpMemory(memory, s.getTrimmed()); + return; + } else if (type == TLineType.ASSERT) { + this.executeAssert(memory, s.getTrimmed()); + return; + } else if (type == TLineType.UNDEF) { + this.executeUndef(memory, s); + return; + } else if (ftype != TFunctionType.RETURN && type == TLineType.PLAIN) { + this.addPlain(memory, s); + return; + } else if (ftype == TFunctionType.RETURN && type == TLineType.RETURN) { + // Actually, ignore because we are in a if + return; + } else if (type == TLineType.AFFECTATION_DEFINE) { + this.executeAffectationDefine(memory, s); + return; + } else if (ftype == null && type == TLineType.END_FUNCTION) { + CommandExecutionResult.error("error endfunc"); + return; + } else if (type == TLineType.LOG) { + this.executeLog(memory, s); + return; + } else { + throw EaterException.located("Parsing Error", s); + } + } + + private void addPlain(TMemory memory, StringLocated s) throws EaterException, EaterExceptionLocated { + StringLocated tmp = applyFunctionsAndVariablesInternal(memory, s); + if (tmp != null) { + if (pendingAdd != null) { + tmp = new StringLocated(pendingAdd + tmp.getString(), tmp.getLocation()); + pendingAdd = null; + } + resultList.add(tmp); + } + } + + private void executeAffectationDefine(TMemory memory, StringLocated s) + throws EaterException, EaterExceptionLocated { + new EaterAffectationDefine(s).analyze(this, memory); + } + + private void executeDumpMemory(TMemory memory, StringLocated s) throws EaterException { + final EaterDumpMemory condition = new EaterDumpMemory(s); + condition.analyze(this, memory); + } + + private void executeAssert(TMemory memory, StringLocated s) throws EaterException, EaterExceptionLocated { + final EaterAssert condition = new EaterAssert(s); + condition.analyze(this, memory); + } + + private void executeUndef(TMemory memory, StringLocated s) throws EaterException { + final EaterUndef undef = new EaterUndef(s); + undef.analyze(this, memory); + } + + private StringLocated applyFunctionsAndVariablesInternal(TMemory memory, StringLocated located) + throws EaterException, EaterExceptionLocated { + if (memory.isEmpty() && functionsSet.size() == 0) { + return located; + } + final String result = applyFunctionsAndVariables(memory, located.getLocation(), located.getString()); + if (result == null) { + return null; + } + return new StringLocated(result, located.getLocation()); + } + + private String pendingAdd = null; + + public String applyFunctionsAndVariables(TMemory memory, LineLocation location, String str) + throws EaterException, EaterExceptionLocated { + // https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore%E2%80%93Horspool_algorithm + // https://stackoverflow.com/questions/1326682/java-replacing-multiple-different-substring-in-a-string-at-once-or-in-the-most + // https://en.wikipedia.org/wiki/String-searching_algorithm + // https://www.quora.com/What-is-the-most-efficient-algorithm-to-replace-all-occurrences-of-a-pattern-P-in-a-string-with-a-pattern-P + // https://en.wikipedia.org/wiki/Trie + if (memory.isEmpty() && functionsSet.size() == 0) { + return str; + } + final StringBuilder result = new StringBuilder(); + for (int i = 0; i < str.length(); i++) { + final char c = str.charAt(i); + final String presentFunction = getFunctionNameAt(str, i); + if (presentFunction != null) { + final String sub = str.substring(i); + final EaterFunctionCall call = new EaterFunctionCall(new StringLocated(sub, location), + isLegacyDefine(presentFunction), isUnquoted(presentFunction)); + call.analyze(this, memory); + final TFunction function = functionsSet + .getFunctionSmart(new TFunctionSignature(presentFunction, call.getValues().size())); + if (function == null) { + throw EaterException.located("Function not found " + presentFunction, + new StringLocated(str, location)); + } + if (function.getFunctionType() == TFunctionType.VOID) { + this.pendingAdd = result.toString(); + executeVoid3(location, memory, sub, function); + return null; + } + if (function.getFunctionType() == TFunctionType.LEGACY_DEFINELONG) { + this.pendingAdd = str.substring(0, i); + executeVoid3(location, memory, sub, function); + return null; + } + assert function.getFunctionType() == TFunctionType.RETURN + || function.getFunctionType() == TFunctionType.LEGACY_DEFINE; + final TValue functionReturn = function.executeReturn(this, memory, location, call.getValues()); + result.append(functionReturn.toString()); + i += call.getCurrentPosition() - 1; + } else if (new VariableManager(this, memory, location).getVarnameAt(str, i) != null) { + i = new VariableManager(this, memory, location).replaceVariables(str, i, result); + } else { + result.append(c); + } + } + return result.toString(); + } + + private void executeVoid3(LineLocation location, TMemory memory, String s, TFunction function) + throws EaterException, EaterExceptionLocated { + function.executeVoid(this, memory, location, s); + } + + private void executeImport(TMemory memory, StringLocated s) throws EaterException, EaterExceptionLocated { + final EaterImport _import = new EaterImport(s.getTrimmed()); + _import.analyze(this, memory); + + try { + final File file = FileSystem.getInstance() + .getFile(applyFunctionsAndVariables(memory, s.getLocation(), _import.getLocation())); + if (file.exists() && file.isDirectory() == false) { + importedFiles.add(file); + return; + } + } catch (IOException e) { + e.printStackTrace(); + throw EaterException.located("Cannot import " + e.getMessage(), s); + } + + throw EaterException.located("Cannot import", s); + } + + private void executeLog(TMemory memory, StringLocated s) throws EaterException, EaterExceptionLocated { + final EaterLog log = new EaterLog(s.getTrimmed()); + log.analyze(this, memory); + } + + private void executeIncludesub(TMemory memory, StringLocated s) throws EaterException, EaterExceptionLocated { + ImportedFiles saveImportedFiles = null; + try { + final EaterIncludesub include = new EaterIncludesub(s.getTrimmed()); + include.analyze(this, memory); + final String location = include.getLocation(); + final int idx = location.indexOf('!'); + Sub sub = null; + if (idx != -1) { + final String filename = location.substring(0, idx); + final String blocname = location.substring(idx + 1); + try { + final FileWithSuffix f2 = importedFiles.getFile(filename, null); + if (f2.fileOk()) { + saveImportedFiles = this.importedFiles; + this.importedFiles = this.importedFiles.withCurrentDir(f2.getParentFile()); + final Reader reader = f2.getReader(charset); + ReadLine readerline = ReadLineReader.create(reader, location, s.getLocation()); + readerline = new UncommentReadLine(readerline); + sub = Sub.fromFile(readerline, blocname, this, memory); + } + } catch (IOException e) { + e.printStackTrace(); + throw EaterException.located("cannot include " + e, s); + } + } + if (sub == null) { + sub = subs.get(location); + } + if (sub == null) { + throw EaterException.located("cannot include " + location, s); + } + executeLinesInternal(memory, sub.lines(), null); + } finally { + if (saveImportedFiles != null) { + this.importedFiles = saveImportedFiles; + } + } + } + + private void executeIncludeDef(TMemory memory, StringLocated s) throws EaterException, EaterExceptionLocated { + final EaterIncludeDef include = new EaterIncludeDef(s.getTrimmed()); + include.analyze(this, memory); + final String definitionName = include.getLocation(); + final List<String> definition = definitionsContainer.getDefinition(definitionName); + ReadLine reader2 = new ReadLineList(definition, s.getLocation()); + + try { + final List<StringLocated> body = new ArrayList<StringLocated>(); + do { + final StringLocated sl = reader2.readLine(); + if (sl == null) { + executeLinesInternal(memory, body, null); + return; + } + body.add(sl); + } while (true); + } catch (IOException e) { + e.printStackTrace(); + throw EaterException.located("" + e, s); + } + } + + private void executeInclude(TMemory memory, StringLocated s) throws EaterException, EaterExceptionLocated { + final EaterInclude include = new EaterInclude(s.getTrimmed()); + include.analyze(this, memory); + String location = include.getLocation(); + final PreprocessorIncludeStrategy strategy = include.getPreprocessorIncludeStrategy(); + final int idx = location.lastIndexOf('!'); + String suf = null; + if (idx != -1) { + suf = location.substring(idx + 1); + location = location.substring(0, idx); + } + + ReadLine reader2 = null; + ImportedFiles saveImportedFiles = null; + try { + if (location.startsWith("http://") || location.startsWith("https://")) { + final URL url = new URL(location); + reader2 = PreprocessorUtils.getReaderIncludeUrl2(url, s, suf, charset); + + } + if (location.startsWith("<") && location.endsWith(">")) { + reader2 = PreprocessorUtils.getReaderStdlibInclude(s, location.substring(1, location.length() - 1)); + } else { + final FileWithSuffix f2 = importedFiles.getFile(location, suf); + if (f2.fileOk()) { + if (strategy == PreprocessorIncludeStrategy.DEFAULT && filesUsedCurrent.contains(f2)) { + return; + } + if (strategy == PreprocessorIncludeStrategy.ONCE && filesUsedCurrent.contains(f2)) { + throw EaterException.located("This file has already been included", s); + } + + if (StartDiagramExtractReader.containsStartDiagram(f2, s, charset)) { + reader2 = StartDiagramExtractReader.build(f2, s, charset); + } else { + final Reader reader = f2.getReader(charset); + if (reader == null) { + throw EaterException.located("Cannot include file", s); + } + reader2 = ReadLineReader.create(reader, location, s.getLocation()); + } + saveImportedFiles = this.importedFiles; + this.importedFiles = this.importedFiles.withCurrentDir(f2.getParentFile()); + assert reader2 != null; + filesUsedCurrent.add(f2); + } + } + if (reader2 != null) { + try { + final List<StringLocated> body = new ArrayList<StringLocated>(); + do { + final StringLocated sl = reader2.readLine(); + if (sl == null) { + executeLines(memory, body, null); + return; + } + body.add(sl); + } while (true); + } finally { + if (saveImportedFiles != null) { + this.importedFiles = saveImportedFiles; + } + } + } + } catch (IOException e) { + e.printStackTrace(); + throw EaterException.located("cannot include " + e, s); + } + + throw EaterException.located("cannot include " + location, s); + } + + public boolean isLegacyDefine(String functionName) { + for (Map.Entry<TFunctionSignature, TFunction> ent : functionsSet.functions().entrySet()) { + if (ent.getKey().getFunctionName().equals(functionName) && ent.getValue().getFunctionType().isLegacy()) { + return true; + } + } + return false; + } + + public boolean isUnquoted(String functionName) { + for (Map.Entry<TFunctionSignature, TFunction> ent : functionsSet.functions().entrySet()) { + if (ent.getKey().getFunctionName().equals(functionName) && ent.getValue().isUnquoted()) { + return true; + } + } + return false; + } + + public boolean doesFunctionExist(String functionName) { + for (Map.Entry<TFunctionSignature, TFunction> ent : functionsSet.functions().entrySet()) { + if (ent.getKey().getFunctionName().equals(functionName)) { + return true; + } + } + return false; + } + + private String getFunctionNameAt(String s, int pos) { + if (pos > 0 && TLineType.isLetterOrUnderscoreOrDigit(s.charAt(pos - 1)) + && VariableManager.justAfterBackslashN(s, pos) == false) { + return null; + } + final String fname = functionsSet.getLonguestMatchStartingIn(s.substring(pos)); + if (fname.length() == 0) { + return null; + } + return fname.substring(0, fname.length() - 1); + } + + public List<StringLocated> getResultList() { + return resultList; + } + + public List<StringLocated> getDebug() { + return debug; + } + + public String extractFromResultList(int n1) { + final StringBuilder sb = new StringBuilder(); + while (resultList.size() > n1) { + sb.append(resultList.get(n1).getString()); + resultList.remove(n1); + if (resultList.size() > n1) { + sb.append("\\n"); + } + } + return sb.toString(); + } + + public void appendEndOfLine(String endOfLine) { + if (endOfLine.length() > 0) { + final int idx = resultList.size() - 1; + StringLocated last = resultList.get(idx); + last = last.append(endOfLine); + resultList.set(idx, last); + } + } + + public TFunction getFunctionSmart(TFunctionSignature signature) { + return functionsSet.getFunctionSmart(signature); + } + +} |