diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/AbstractVariableDeclaration.java b/src/main/java/com/github/sommeri/less4j/core/ast/AbstractVariableDeclaration.java index c8036b4d..cc1e0325 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/AbstractVariableDeclaration.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/AbstractVariableDeclaration.java @@ -37,6 +37,16 @@ public void setValue(Expression value) { this.value = value; } + @NotAstProperty + public boolean isCollector() { + return getVariable().isCollector(); + } + + @NotAstProperty + public void setCollector(boolean collector) { + getVariable().setCollector(collector); + } + @Override @NotAstProperty public List getChilds() { diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/ArgumentDeclaration.java b/src/main/java/com/github/sommeri/less4j/core/ast/ArgumentDeclaration.java index cc18d04d..45c358ad 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/ArgumentDeclaration.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/ArgumentDeclaration.java @@ -1,6 +1,5 @@ package com.github.sommeri.less4j.core.ast; -import com.github.sommeri.less4j.core.ast.annotations.NotAstProperty; import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; public class ArgumentDeclaration extends AbstractVariableDeclaration { @@ -17,16 +16,6 @@ public ArgumentDeclaration(Variable variable, Expression value) { this(variable.getUnderlyingStructure(), variable, value); } - @NotAstProperty - public boolean isCollector() { - return getVariable().isCollector(); - } - - @NotAstProperty - public void setCollector(boolean collector) { - getVariable().setCollector(collector); - } - @Override public ASTCssNodeType getType() { return ASTCssNodeType.ARGUMENT_DECLARATION; diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ExpressionManipulator.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ExpressionManipulator.java index 8c79859f..ec06d8ec 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ExpressionManipulator.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ExpressionManipulator.java @@ -6,7 +6,6 @@ import com.github.sommeri.less4j.core.ast.ASTCssNode; import com.github.sommeri.less4j.core.ast.ASTCssNodeType; import com.github.sommeri.less4j.core.ast.Expression; -import com.github.sommeri.less4j.core.ast.IdentifierExpression; import com.github.sommeri.less4j.core.ast.KeywordExpression; import com.github.sommeri.less4j.core.ast.ListExpression; import com.github.sommeri.less4j.core.ast.ListExpressionOperator; @@ -17,7 +16,7 @@ public class ExpressionManipulator { public Expression findRightmostListedExpression(Expression expression) { Expression result = expression; - while (result.getType() == ASTCssNodeType.LIST_EXPRESSION) { + while (result!=null && result.getType() == ASTCssNodeType.LIST_EXPRESSION) { ListExpression parentList = (ListExpression) result; result = ArraysUtils.last(parentList.getExpressions()); } @@ -25,6 +24,9 @@ public Expression findRightmostListedExpression(Expression expression) { } public ListExpression findRightmostSpaceSeparatedList(Expression expression) { + if (expression==null) + return null; + ListExpression result = null; Expression rightmost = expression; while (rightmost.getType() == ASTCssNodeType.LIST_EXPRESSION) { @@ -109,9 +111,6 @@ private boolean sameListOperator(ListExpression list, ListExpression sublist) { //FIXME: should probably deep clone allArguments and setup parent child relationships public Expression joinAll(List allArguments, ASTCssNode parent) { - if (allArguments.isEmpty()) - return new IdentifierExpression(parent.getUnderlyingStructure(), ""); - return new ListExpression(parent.getUnderlyingStructure(), allArguments, new ListExpressionOperator(parent.getUnderlyingStructure(), ListExpressionOperator.Operator.EMPTY_OPERATOR)); } diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/TypeFunctions.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/TypeFunctions.java index 03a07c02..da6eacf7 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/TypeFunctions.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/TypeFunctions.java @@ -25,6 +25,7 @@ public class TypeFunctions extends BuiltInFunctionsPack { protected static final String ISEM = "isem"; protected static final String ISURL = "isurl"; protected static final String ISUNIT = "isunit"; + protected static final String ISLIST = "islist"; private static Map FUNCTIONS = new HashMap(); static { @@ -38,6 +39,7 @@ public class TypeFunctions extends BuiltInFunctionsPack { FUNCTIONS.put(ISEM, new IsEm()); FUNCTIONS.put(ISURL, new IsUrl()); FUNCTIONS.put(ISUNIT, new IsUnit()); + FUNCTIONS.put(ISLIST, new IsList()); } public TypeFunctions(ProblemsHandler problemsHandler) { @@ -69,6 +71,15 @@ protected boolean checkType(Expression parameter) { } +class IsList extends AbstractTypeFunction { + + @Override + protected boolean checkType(Expression parameter) { + return parameter.getType() == ASTCssNodeType.LIST_EXPRESSION; + } + +} + class IsKeyword extends AbstractTypeFunction { @Override diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/BasicScope.java b/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/BasicScope.java index 2ea9078b..8abf791f 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/BasicScope.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/BasicScope.java @@ -25,7 +25,6 @@ public Expression getValue(String name) { Expression value = getLocalValue(name); if (value == null && hasParent()) { - //System.out.println("searching parent: " + getParent()); value = getParent().getValue(name); } diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/view/ScopeView.java b/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/view/ScopeView.java index 538f1cfc..fff661c9 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/view/ScopeView.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/view/ScopeView.java @@ -21,7 +21,6 @@ public void toIndependentWorkingCopy() { } public void toIndependentWorkingCopyAllParents() { - //System.out.println(" +++ saving: " + this); this.saveableLocalScope.save(); if (hasParent()) { diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/stages/EvaluatedMixinReferenceCall.java b/src/main/java/com/github/sommeri/less4j/core/compiler/stages/EvaluatedMixinReferenceCall.java index 0c83bc0d..c84e35f3 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/stages/EvaluatedMixinReferenceCall.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/stages/EvaluatedMixinReferenceCall.java @@ -1,12 +1,15 @@ package com.github.sommeri.less4j.core.compiler.stages; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import com.github.sommeri.less4j.core.ast.ASTCssNodeType; import com.github.sommeri.less4j.core.ast.Expression; +import com.github.sommeri.less4j.core.ast.ListExpression; import com.github.sommeri.less4j.core.ast.MixinReference; import com.github.sommeri.less4j.core.ast.Variable; import com.github.sommeri.less4j.core.compiler.expressions.ExpressionEvaluator; @@ -22,7 +25,8 @@ public EvaluatedMixinReferenceCall(MixinReference reference, ExpressionEvaluator this.reference = reference; for (Expression expression : reference.getPositionalParameters()) { - positionalParameters.add(evaluator.evaluate(expression)); + Expression value = evaluator.evaluate(expression); + addPositional(expression, value); } for (Entry entry : reference.getNamedParameters().entrySet()) { @@ -30,6 +34,30 @@ public EvaluatedMixinReferenceCall(MixinReference reference, ExpressionEvaluator } } + private void addPositional(Expression original, Expression evaluated) { + if (isEllipsisVariable(original)) { + positionalParameters.addAll(expandList(evaluated)); + } else { + positionalParameters.add(evaluated); + } + } + + private List expandList(Expression value) { + if (value.getType()!=ASTCssNodeType.LIST_EXPRESSION) { + return Arrays.asList(value); + } + ListExpression list = (ListExpression) value; + return list.getExpressions(); + } + + private boolean isEllipsisVariable(Expression expression) { + if (expression.getType()!=ASTCssNodeType.VARIABLE) + return false; + + Variable variable = (Variable) expression; + return variable.isCollector(); + } + public MixinReference getReference() { return reference; } diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/stages/MixinsRulesetsSolver.java b/src/main/java/com/github/sommeri/less4j/core/compiler/stages/MixinsRulesetsSolver.java index 9c724261..f43d84e1 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/stages/MixinsRulesetsSolver.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/stages/MixinsRulesetsSolver.java @@ -30,6 +30,7 @@ import com.github.sommeri.less4j.core.compiler.scopes.InScopeSnapshotRunner.ITask; import com.github.sommeri.less4j.core.compiler.scopes.ScopeFactory; import com.github.sommeri.less4j.core.compiler.scopes.view.ScopeView; +import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; import com.github.sommeri.less4j.core.problems.ProblemsHandler; import com.github.sommeri.less4j.core.problems.UnableToFinish; import com.github.sommeri.less4j.utils.ArraysUtils; @@ -108,12 +109,13 @@ public GeneralBody buildMixinReferenceReplacement(final EvaluatedMixinReferenceC final MixinReference reference = evaluatedReference.getReference(); if (Thread.currentThread().isInterrupted()) throw new UnableToFinish("Thread Interrupted", (ASTCssNode) null); - + final GeneralBody result = new GeneralBody(reference.getUnderlyingStructure()); if (mixins.isEmpty()) return result; - //candidate mixins with information about their default() function use are stored here + // candidate mixins with information about their default() function use are + // stored here final List compiledMixins = new ArrayList(); for (final FoundMixin fullMixin : mixins) { @@ -121,7 +123,8 @@ public GeneralBody buildMixinReferenceReplacement(final EvaluatedMixinReferenceC final IScope mixinScope = fullMixin.getScope(); final BodyCompilationData data = new BodyCompilationData(mixin); - // the following needs to run in snapshot because calculateMixinsWorkingScope modifies that scope + // the following needs to run in snapshot because + // calculateMixinsWorkingScope modifies that scope InScopeSnapshotRunner.runInLocalDataSnapshot(mixinScope.getParent(), new ITask() { @Override @@ -131,8 +134,10 @@ public void run() { data.setArguments(mixinArguments); mixinScope.getParent().add(mixinArguments); ScopeView mixinWorkingScope = scopeManipulation.joinIfIndependent(callerScope, mixinScope); - //it the mixin calls itself recursively, each copy should work on independent copy of local data - //that is matters mostly for scope placeholders - if both close placeholders in same copy error happen + // it the mixin calls itself recursively, each copy should work on + // independent copy of local data + // that is matters mostly for scope placeholders - if both close + // placeholders in same copy error happen mixinWorkingScope.toIndependentWorkingCopy(); data.setMixinWorkingScope(mixinWorkingScope); @@ -142,36 +147,41 @@ public void run() { compiledMixins.add(data); } - }); //end of InScopeSnapshotRunner.runInLocalDataSnapshot + }); // end of InScopeSnapshotRunner.runInLocalDataSnapshot } - // filter out mixins we do not want to use + // filter out mixins we do not want to use List mixinsToBeUsed = defaultGuardHelper.chooseMixinsToBeUsed(compiledMixins, reference); for (final BodyCompilationData data : mixinsToBeUsed) { final ScopeView mixinWorkingScope = data.getMixinWorkingScope(); - // compilation must run in another localDataSnapshot, because imported detached ruleset stored in + // compilation must run in another localDataSnapshot, because imported + // detached ruleset stored in // variables point to original scope - making snapshot above is not enough - // since they point to scope as defined during definition, they would not know parameters + // since they point to scope as defined during definition, they would not + // know parameters // of mixins that define them InScopeSnapshotRunner.runInLocalDataSnapshot(mixinWorkingScope.getParent(), new ITask() { @Override public void run() { BodyOwner mixin = data.getCompiledBodyOwner(); - // add arguments again - detached rulesets imported into this one - // via returned variables from sub-calls need would not see arguments otherwise - // bc they keep link to original copy and there is no other way how to access them + // add arguments again - detached rulesets imported into this one + // via returned variables from sub-calls need would not see arguments + // otherwise + // bc they keep link to original copy and there is no other way how to + // access them IScope arguments = data.getArguments(); mixinWorkingScope.getParent().add(arguments); Couple, IScope> compiled = resolveCalledBody(callerScope, mixin, mixinWorkingScope, ReturnMode.MIXINS_AND_VARIABLES); - // update mixin replacements and update scope with imported variables and mixins + // update mixin replacements and update scope with imported variables + // and mixins result.addMembers(compiled.getT()); callerScope.addToDataPlaceholder(compiled.getM()); } - }); //end of InScopeSnapshotRunner.runInLocalData........Snapshot + }); // end of InScopeSnapshotRunner.runInLocalData........Snapshot } @@ -191,7 +201,7 @@ public GeneralBody buildDetachedRulesetReplacement(DetachedRulesetReference refe callerScope.addToDataPlaceholder(compiled.getM()); callerScope.closeDataPlaceholder(); - //resolveImportance(reference, result); + // resolveImportance(reference, result); shiftComments(reference, result); return result; @@ -221,12 +231,12 @@ private void addImportantKeyword(Declaration declaration) { if (expressionManipulator.isImportant(expression)) return; - //FIXME !!!!!!!!!! correct underlying - or correct keyword!!! - KeywordExpression important = new KeywordExpression(expression.getUnderlyingStructure(), "!important", true); + HiddenTokenAwareTree underlying = expression != null ? expression.getUnderlyingStructure() : declaration.getUnderlyingStructure(); + KeywordExpression important = createImportantKeyword(underlying); ListExpression list = expressionManipulator.findRightmostSpaceSeparatedList(expression); if (list == null) { - list = new ListExpression(expression.getUnderlyingStructure(), ArraysUtils.asList(expression), new ListExpressionOperator(expression.getUnderlyingStructure(), ListExpressionOperator.Operator.EMPTY_OPERATOR)); + list = new ListExpression(underlying, ArraysUtils.asNonNullList(expression), new ListExpressionOperator(underlying, ListExpressionOperator.Operator.EMPTY_OPERATOR)); } list.addExpression(important); list.configureParentToAllChilds(); @@ -235,6 +245,10 @@ private void addImportantKeyword(Declaration declaration) { list.setParent(declaration); } + private KeywordExpression createImportantKeyword(HiddenTokenAwareTree underlyingStructure) { + return new KeywordExpression(underlyingStructure, "!important", true); + } + class ImportedScopeFilter implements ExpressionFilter { private final ExpressionEvaluator expressionEvaluator; diff --git a/src/main/java/com/github/sommeri/less4j/core/parser/ASTBuilderSwitch.java b/src/main/java/com/github/sommeri/less4j/core/parser/ASTBuilderSwitch.java index 18be9e84..4e295cd9 100644 --- a/src/main/java/com/github/sommeri/less4j/core/parser/ASTBuilderSwitch.java +++ b/src/main/java/com/github/sommeri/less4j/core/parser/ASTBuilderSwitch.java @@ -179,10 +179,6 @@ private Expression createListExpression(HiddenTokenAwareTree parent, LinkedList< HiddenTokenAwareTree operatorToken = iterator.next(); operatorToken.pushHiddenToSiblings(); ListExpressionOperator operator = createListOperator(operatorToken); - if (operator==null) { - System.out.println(operatorToken); - System.out.println(""); - } if (operator.getOperator()==ListExpressionOperator.Operator.EMPTY_OPERATOR) { space = operator; } else { diff --git a/src/test/resources/compile-basic-features/mixins/mixins-ellipsis.css b/src/test/resources/compile-basic-features/mixins/mixins-ellipsis.css new file mode 100644 index 00000000..c93eb34d --- /dev/null +++ b/src/test/resources/compile-basic-features/mixins/mixins-ellipsis.css @@ -0,0 +1,28 @@ +collapsed-list { + one-parameter: 1 2 3; +} +expanded-list { + three-parameters-1: 1; + three-parameters-2: 2; + three-parameters-3: 3; +} +expanded-comma-list { + three-parameters-1: 1; + three-parameters-2: 2; + three-parameters-3: 3; +} +expanded-not-list { + one-parameter: not-list; +} +pass-wrapper { + one-parameter: 1 2 3; +} +.tail-loop-here { + a: A; + tail: b B c C; + b: B; + tail: c C; + c: C; + tail: ; + the: end; +} \ No newline at end of file diff --git a/src/test/resources/compile-basic-features/mixins/mixins-ellipsis.less b/src/test/resources/compile-basic-features/mixins/mixins-ellipsis.less new file mode 100644 index 00000000..718de6a1 --- /dev/null +++ b/src/test/resources/compile-basic-features/mixins/mixins-ellipsis.less @@ -0,0 +1,41 @@ +@list: 1 2 3; +@comma-list: 1, 2, 3; +@not-list: not-list; + +collapsed-list {.mixin(@list)} // 1 argument +expanded-list {.mixin(@list...)} // 3 arguments +expanded-comma-list {.mixin(@comma-list...)} // 3 arguments +expanded-not-list {.mixin(@not-list...)} // 3 arguments +pass-wrapper {.wrapper(@list)} // 1 argument + + +.mixin(@a1) { + one-parameter: @a1; +} + +.mixin(@a1, @a2, @a3) { + three-parameters-1: @a1; + three-parameters-2: @a2; + three-parameters-3: @a3; +} + +.wrapper(@args...) { + .mixin(@args...); // 1 argument of `c` expands as 3 arguments +} + +// with tail +.tail-loop() { the: end; } +.tail-loop(@head, @tail...) { + @first: extract(@head, 1); + @second: extract(@head, 2); + + @{first}: @second; + tail: @tail; + .tail-loop(@tail...); +} + +.tail-loop-here { + @values : a A, b B, c C; + .tail-loop(@values...); +} + diff --git a/src/test/resources/minitests/debug1.css b/src/test/resources/minitests/debug1.css index 06278145..1e339801 100644 --- a/src/test/resources/minitests/debug1.css +++ b/src/test/resources/minitests/debug1.css @@ -1,16 +1,7 @@ -#argumentsArgument { - declaration1: b2; - declaration2: b1 b2; - declaration5: b1 b1 b2; - declaration6: b2; - declaration7: b1 b2; - declaration5: c1 c1 c2 c3; - declaration6: c2 c3; - declaration7: c1 c2; - declaration5: d1 d1 d2 d3 d4; - declaration6: d2 d3 d4; - declaration7: d1 d2; - declaration5: e1 e1 e2 e3 e4 e5; - declaration6: e2 e3 e4 e5; - declaration7: e1 e2; -} \ No newline at end of file +.class { + a: A; + b: B; + c: C; + tail: b B, c C; + tail: c C; +} diff --git a/src/test/resources/minitests/debug1.less b/src/test/resources/minitests/debug1.less index e30eb0a6..d9c83318 100644 --- a/src/test/resources/minitests/debug1.less +++ b/src/test/resources/minitests/debug1.less @@ -1,28 +1,15 @@ -@arguments: "blaaah"; +// with tail +.tail-loop() { the: end; } +.tail-loop(@head, @tail...) { + @first: extract(@head, 1); + @second: extract(@head, 2); -.argumentsArgument(@a: 0, @arguments) { - declaration1: @arguments + @{first}: @second; + tail: @tail; + .tail-loop(@tail...); } -.argumentsArgument(@a: 0, @b: 1) { - declaration2: @arguments -} - -.argumentsArgument(@a: 0, ...) { - declaration5: @a @arguments -} - -.argumentsArgument(@a: 0, @arguments...) { - declaration6: @arguments -} - -.argumentsArgument(@a: 0, @arguments: 1, ...) { - declaration7: @a @arguments -} - -#argumentsArgument { - .argumentsArgument(b1, b2); - .argumentsArgument(c1, c2, c3); - .argumentsArgument(d1, d2, d3, d4); - .argumentsArgument(e1, e2, e3, e4, e5); +.class { + @values : a A, b B, c C; + .tail-loop(@values...); } \ No newline at end of file