diff --git a/cfml.dictionary/src/main/java/cfml/dictionary/DictionaryManager.java b/cfml.dictionary/src/main/java/cfml/dictionary/DictionaryManager.java index 8befb8c..e86d13c 100644 --- a/cfml.dictionary/src/main/java/cfml/dictionary/DictionaryManager.java +++ b/cfml.dictionary/src/main/java/cfml/dictionary/DictionaryManager.java @@ -82,6 +82,16 @@ public class DictionaryManager { private static boolean initialized; + static { + // Trigger the default dictionary load (built-in classpath resources only, via the + // no-arg DictionaryPreferences defaults - no environment/filesystem dependence) as part + // of class initialization, not just on first explicit call. This lets AOT tooling (e.g. + // GraalVM native-image's --initialize-at-build-time) bake the parsed dictionaries into + // the build-time heap snapshot instead of re-parsing them on every process start. + // initDictionaries() is otherwise unchanged and remains safe/idempotent to call directly. + initDictionaries(); + } + private DictionaryManager(DictionaryPreferences prefs) { fPrefs = prefs; init(); diff --git a/cfml.parsing/src/main/java/cfml/parsing/CFMLParser.java b/cfml.parsing/src/main/java/cfml/parsing/CFMLParser.java index 2c19c33..4f5cd32 100644 --- a/cfml.parsing/src/main/java/cfml/parsing/CFMLParser.java +++ b/cfml.parsing/src/main/java/cfml/parsing/CFMLParser.java @@ -58,7 +58,16 @@ public class CFMLParser { CFScriptStatementVisitor scriptVisitor = new CFScriptStatementVisitor(); CFSCRIPTLexer lexer = null; CFSCRIPTParser parser = null; - + + // Callers (e.g. CFLint scanning a file's / tags one at a time) frequently + // re-parse textually-identical short expressions many times over - common boilerplate like + // "var result = StructNew()" can repeat dozens of times in one file. Cache the parse tree by + // source text and re-run the (cheap) visitor on a hit, skipping the expensive lex/parse/ATN + // simulation. We deliberately do NOT cache/share the resulting CFExpression itself - it has + // mutable state (CFParsedStatement.setParent()) that callers rely on per-use, so every hit + // gets a fresh visit() over the cached (read-only) parse tree instead. + private final Map exprTreeCache = new HashMap<>(); + public void clearDFA() { if (parser != null) parser.getInterpreter().clearDFA(); @@ -124,7 +133,12 @@ public CFExpression parseCFMLExpression(String _infix, ANTLRErrorListener errorR if (errorReporter == null) { errorReporter = this.errorReporter; } - + + final CfmlExpressionContext cachedTree = exprTreeCache.get(_infix); + if (cachedTree != null) { + return expressionVisitor.visit(cachedTree); + } + final CharStream input = CharStreams.fromString(_infix); if (lexer == null) { lexer = new CFSCRIPTLexer(input); @@ -170,6 +184,7 @@ public CFExpression parseCFMLExpression(String _infix, ANTLRErrorListener errorR } } if (expressionContext != null) { + exprTreeCache.put(_infix, expressionContext); return expressionVisitor.visit(expressionContext); } else return null;