diff --git a/cfml.parsing/src/main/antlr4/cfml/CFSCRIPTParser.g4 b/cfml.parsing/src/main/antlr4/cfml/CFSCRIPTParser.g4
index cd5cbe6..6f6139a 100644
--- a/cfml.parsing/src/main/antlr4/cfml/CFSCRIPTParser.g4
+++ b/cfml.parsing/src/main/antlr4/cfml/CFSCRIPTParser.g4
@@ -595,7 +595,6 @@ functionCall
;
simpleFunctionCall
:(identifier | specialWord) LEFTPAREN argumentList RIGHTPAREN
- body=compoundStatement?
;
qualifiedFunctionCall
:(identifier | reservedWord) LEFTPAREN argumentList RIGHTPAREN
@@ -783,8 +782,12 @@ implicitStructKeyExpression
| reservedWord
;
+// `new component { ... }` defines and instantiates in one expression. The body is an
+// ordinary componentDeclaration, reused rather than restated so attributes, directives
+// and members all behave the same as in a named component.
newComponentExpression
: NEW componentPath LEFTPAREN argumentList RIGHTPAREN
+ | NEW componentDeclaration
;
// Lucee types the path being instantiated: new java:java.io.File(p), new cfml:foo.Bar().
diff --git a/cfml.parsing/src/main/java/cfml/parsing/cfscript/CFAnonymousComponentExpression.java b/cfml.parsing/src/main/java/cfml/parsing/cfscript/CFAnonymousComponentExpression.java
new file mode 100644
index 0000000..e446cc1
--- /dev/null
+++ b/cfml.parsing/src/main/java/cfml/parsing/cfscript/CFAnonymousComponentExpression.java
@@ -0,0 +1,56 @@
+package cfml.parsing.cfscript;
+
+import java.util.List;
+
+import org.antlr.v4.runtime.Token;
+
+import cfml.parsing.cfscript.script.CFCompDeclStatement;
+import cfml.parsing.cfscript.script.CFScriptStatement;
+import cfml.parsing.util.ArrayBuilder;
+
+/**
+ * An inline component: new component javaSettings='...' { ... }, which declares and
+ * instantiates in one expression.
+ *
+ * Modelled the way {@link CFAnonymousFunctionExpression} models function(){} -- a
+ * declaration statement hanging off an expression -- so a walk that already descends into an
+ * anonymous function reaches an inline component's members the same way.
+ */
+public class CFAnonymousComponentExpression extends CFExpression {
+
+ private static final long serialVersionUID = 1L;
+
+ private CFCompDeclStatement componentDeclaration;
+
+ public CFAnonymousComponentExpression(Token _t, CFCompDeclStatement _componentDeclaration) {
+ super(_t);
+ componentDeclaration = _componentDeclaration;
+ if (componentDeclaration != null) {
+ componentDeclaration.setParent(this);
+ }
+ }
+
+ @Override
+ public byte getType() {
+ return CFExpression.NESTED;
+ }
+
+ public CFCompDeclStatement getComponentDeclaration() {
+ return componentDeclaration;
+ }
+
+ @Override
+ public String Decompile(int indent) {
+ return "new " + componentDeclaration.Decompile(0);
+ }
+
+ @Override
+ public List decomposeExpression() {
+ return ArrayBuilder.createCFExpression();
+ }
+
+ @Override
+ public List decomposeScript() {
+ return ArrayBuilder.createCFScriptStatement(componentDeclaration);
+ }
+}
diff --git a/cfml.parsing/src/main/java/cfml/parsing/cfscript/walker/CFExpressionVisitor.java b/cfml.parsing/src/main/java/cfml/parsing/cfscript/walker/CFExpressionVisitor.java
index 657cf7c..835d820 100644
--- a/cfml.parsing/src/main/java/cfml/parsing/cfscript/walker/CFExpressionVisitor.java
+++ b/cfml.parsing/src/main/java/cfml/parsing/cfscript/walker/CFExpressionVisitor.java
@@ -17,6 +17,7 @@
import cfml.CFSCRIPTParser.CfmlFunctionContext;
import cfml.CFSCRIPTParser.CompareExpressionContext;
import cfml.CFSCRIPTParser.ComponentAttributeContext;
+import cfml.CFSCRIPTParser.ComponentDeclarationContext;
import cfml.CFSCRIPTParser.ComponentGutsContext;
import cfml.CFSCRIPTParser.ComponentPathContext;
import cfml.CFSCRIPTParser.ConditionContext;
@@ -57,6 +58,7 @@
import cfml.CFSCRIPTParser.UnaryExpressionContext;
import cfml.CFSCRIPTParserBaseVisitor;
import cfml.parsing.cfscript.ArgumentsVector;
+import cfml.parsing.cfscript.CFAnonymousComponentExpression;
import cfml.parsing.cfscript.CFAnonymousFunctionExpression;
import cfml.parsing.cfscript.CFArrayExpression;
import cfml.parsing.cfscript.CFAssignmentExpression;
@@ -78,6 +80,7 @@
import cfml.parsing.cfscript.CFTernaryExpression;
import cfml.parsing.cfscript.CFUnaryExpression;
import cfml.parsing.cfscript.CFVarDeclExpression;
+import cfml.parsing.cfscript.script.CFCompDeclStatement;
import cfml.parsing.cfscript.script.CFFuncDeclStatement;
import cfml.parsing.cfscript.script.CFReturnStatement;
@@ -429,6 +432,13 @@ private CFIdentifier makeIdentifier(CFExpression visit) {
@Override
public CFExpression visitNewComponentExpression(NewComponentExpressionContext ctx) {
+ if (ctx.componentDeclaration() != null) {
+ // `new component { ... }` -- the declaration is built by the statement visitor, the way
+ // an anonymous function's is, and wrapped so it can sit in an expression.
+ CFCompDeclStatement declaration = (CFCompDeclStatement) getCFScriptStatementVisitor()
+ .visitComponentDeclaration(ctx.componentDeclaration());
+ return new CFAnonymousComponentExpression(ctx.NEW().getSymbol(), declaration);
+ }
ArgumentsVector args = new ArgumentsVector();
if (ctx.getChildCount() > 4) {
for (ArgumentContext argCtx : ctx.argumentList().argument()) {
diff --git a/cfml.parsing/src/test/resources/cfml/tests/components/inline_component_49.cfc b/cfml.parsing/src/test/resources/cfml/tests/components/inline_component_49.cfc
new file mode 100644
index 0000000..559dfa6
--- /dev/null
+++ b/cfml.parsing/src/test/resources/cfml/tests/components/inline_component_49.cfc
@@ -0,0 +1,16 @@
+component {
+ function connect() {
+ // Declares and instantiates in one expression -- the shape Lucee uses to pull a
+ // Maven dependency in via javaSettings.
+ var msal = new component javaSettings='{"maven":["com.microsoft.azure:msal4j:1.23.1"]}' {
+ function buildApp() {
+ return 1;
+ }
+ };
+ // Without attributes, and the ordinary path forms alongside it.
+ var bare = new component { function f() { return 2; } };
+ var named = new models.User();
+ var javaFile = new java:java.io.File(path);
+ return msal;
+ }
+}
diff --git a/cfml.parsing/src/test/resources/cfml/tests/components/inline_component_49.expected.txt b/cfml.parsing/src/test/resources/cfml/tests/components/inline_component_49.expected.txt
new file mode 100644
index 0000000..be6f621
--- /dev/null
+++ b/cfml.parsing/src/test/resources/cfml/tests/components/inline_component_49.expected.txt
@@ -0,0 +1,345 @@
+/*===TOKENS===*/
+COMPONENT
+'{' <{>
+Hidden:NEWLINE <>
+FUNCTION
+IDENTIFIER
+'(' <(>
+')' <)>
+'{' <{>
+Hidden:NEWLINE <>
+Hidden:LINE_COMMENT / Declares and instantiates in one expression -- the shape Lucee uses to pull a>
+Hidden:LINE_COMMENT / Maven dependency in via javaSettings.>
+VAR
+IDENTIFIER
+'=' <=>
+NEW
+COMPONENT
+IDENTIFIER
+'=' <=>
+OPEN_STRING <'>
+STRING_LITERAL <{"maven":["com.microsoft.azure:msal4j:1.23.1"]}>
+CLOSE_STRING <'>
+'{' <{>
+Hidden:NEWLINE <>
+FUNCTION
+IDENTIFIER
+'(' <(>
+')' <)>
+'{' <{>
+Hidden:NEWLINE <>
+RETURN
+INTEGER_LITERAL <1>
+';' <;>
+Hidden:NEWLINE <>
+'}' <}>
+Hidden:NEWLINE <>
+'}' <}>
+';' <;>
+Hidden:NEWLINE <>
+Hidden:LINE_COMMENT / Without attributes, and the ordinary path forms alongside it.>
+VAR
+IDENTIFIER
+'=' <=>
+NEW
+COMPONENT
+'{' <{>
+FUNCTION
+IDENTIFIER
+'(' <(>
+')' <)>
+'{' <{>
+RETURN
+INTEGER_LITERAL <2>
+';' <;>
+'}' <}>
+'}' <}>
+';' <;>
+Hidden:NEWLINE <>
+VAR
+IDENTIFIER
+'=' <=>
+NEW
+IDENTIFIER
+'.' <.>
+IDENTIFIER
+'(' <(>
+')' <)>
+';' <;>
+Hidden:NEWLINE <>
+VAR
+IDENTIFIER
+'=' <=>
+NEW
+IDENTIFIER
+':' <:>
+IDENTIFIER
+'.' <.>
+IDENTIFIER
+'.' <.>
+FILE
+'(' <(>
+IDENTIFIER
+')' <)>
+';' <;>
+Hidden:NEWLINE <>
+RETURN
+IDENTIFIER
+';' <;>
+Hidden:NEWLINE <>
+'}' <}>
+Hidden:NEWLINE <>
+'}' <}>
+Hidden:NEWLINE <>
+/*===TREE===*/
+(scriptBlock
+ (componentDeclaration
+ component
+ (componentGuts
+ {
+ (element
+ (functionDeclaration
+ function
+ (identifier connect)
+ (
+ parameterList
+ )
+ (compoundStatement
+ {
+ (statement
+ (localAssignmentExpression
+ var
+ (startExpression
+ (baseExpression (unaryExpression (memberExpression (identifier msal))))
+ )
+ =
+ (startExpression
+ (baseExpression
+ (unaryExpression
+ (memberExpression
+ (newComponentExpression
+ new
+ (componentDeclaration
+ component
+ (componentAttribute
+ (identifier javaSettings)
+ =
+ (startExpression
+ (baseExpression
+ (unaryExpression
+ (primaryExpression
+ (literalExpression
+ (stringLiteral
+ '
+ (stringLiteralPart {"maven":["com.microsoft.azure:msal4j:1.23.1"]})
+ '
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ (componentGuts
+ {
+ (element
+ (functionDeclaration
+ function
+ (identifier buildApp)
+ (
+ parameterList
+ )
+ (compoundStatement
+ {
+ (statement
+ (returnStatement
+ return
+ (anExpression
+ (startExpression
+ (baseExpression (unaryExpression (primaryExpression (literalExpression 1))))
+ )
+ )
+ )
+ (endOfStatement ;)
+ )
+ }
+ )
+ )
+ )
+ }
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ (endOfStatement ;)
+ )
+ (statement
+ (localAssignmentExpression
+ var
+ (startExpression
+ (baseExpression (unaryExpression (memberExpression (identifier bare))))
+ )
+ =
+ (startExpression
+ (baseExpression
+ (unaryExpression
+ (memberExpression
+ (newComponentExpression
+ new
+ (componentDeclaration
+ component
+ (componentGuts
+ {
+ (element
+ (functionDeclaration
+ function
+ (identifier f)
+ (
+ parameterList
+ )
+ (compoundStatement
+ {
+ (statement
+ (returnStatement
+ return
+ (anExpression
+ (startExpression
+ (baseExpression (unaryExpression (primaryExpression (literalExpression 2))))
+ )
+ )
+ )
+ (endOfStatement ;)
+ )
+ }
+ )
+ )
+ )
+ }
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ (endOfStatement ;)
+ )
+ (statement
+ (localAssignmentExpression
+ var
+ (startExpression
+ (baseExpression (unaryExpression (memberExpression (identifier named))))
+ )
+ =
+ (startExpression
+ (baseExpression
+ (unaryExpression
+ (memberExpression
+ (newComponentExpression
+ new
+ (componentPath
+ (multipartIdentifier
+ (identifier models)
+ .
+ (identifierOrReservedWord (identifier User))
+ )
+ )
+ (
+ argumentList
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ (endOfStatement ;)
+ )
+ (statement
+ (localAssignmentExpression
+ var
+ (startExpression
+ (baseExpression (unaryExpression (memberExpression (identifier javaFile))))
+ )
+ =
+ (startExpression
+ (baseExpression
+ (unaryExpression
+ (memberExpression
+ (newComponentExpression
+ new
+ (componentPath
+ (identifier java)
+ :
+ (multipartIdentifier
+ (identifier java)
+ .
+ (identifierOrReservedWord (identifier io))
+ .
+ (identifierOrReservedWord (identifier (cfmlFunction File)))
+ )
+ )
+ (
+ (argumentList
+ (argument
+ (startExpression
+ (baseExpression (unaryExpression (memberExpression (identifier path))))
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ (endOfStatement ;)
+ )
+ (statement
+ (returnStatement
+ return
+ (anExpression
+ (startExpression
+ (baseExpression (unaryExpression (memberExpression (identifier msal))))
+ )
+ )
+ )
+ (endOfStatement ;)
+ )
+ }
+ )
+ )
+ )
+ }
+ )
+ )
+)
+/*======*/
+/*===DECOMPILE===*/
+component {
+ public function connect() {
+ var msal = new component javaSettings='{"maven":["com.microsoft.azure:msal4j:1.23.1"]}' {
+ public function buildApp() {
+ return 1;
+
+ }
+};
+ var bare = new component {
+ public function f() {
+ return 2;
+
+ }
+};
+ var named = new models.User();
+ var javaFile = new java:java.io.File(path);
+ return msal;
+
+ }
+}
+/*======*/
\ No newline at end of file