blob: dd6ac555af2247b7185abb05f96d9570fc4fb363 [file] [log] [blame]
/* JavaCC grammar for the SLING-5449 content repository
* initialization language
* See https://javacc.github.io/javacc/ for the most recent docs,
* including links to grammar examples
* See http://www.engr.mun.ca/~theo/JavaCC-FAQ/javacc-faq-moz.htm for a FAQ from 2011
* See also http://eriklievaart.com/web/javacc1.html (from 2014) for additional information
*/
options
{
STATIC=false;
LOOKAHEAD=3;
//FORCE_LA_CHECK=true;
//DEBUG_PARSER=true;
//DEBUG_LOOKAHEAD=true;
}
PARSER_BEGIN(RepoInitParserImpl)
package org.apache.sling.repoinit.parser.impl;
import java.util.List;
import java.util.ArrayList;
import org.apache.sling.repoinit.parser.operations.*;
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
public class RepoInitParserImpl
{
}
PARSER_END(RepoInitParserImpl)
SKIP :
{
" "
| "\r"
| "\t"
| < COMMENT: "#" (~["\n"])* "\n" >
}
TOKEN:
{
< SET: "set" >
| < ACL: "ACL" >
| < ACE: "ACE" >
| < ON: "on" >
| < TO: "to" >
| < FROM: "from" >
| < REMOVE: "remove" >
| < ALLOW: "allow" >
| < DENY: "deny" >
| < FOR: "for" >
| < CREATE: "create" >
| < DELETE: "delete" >
| < DISABLE: "disable" >
| < ENSURE: "ensure" >
| < SERVICE: "service" >
| < ADD: "add" >
| < MIXIN: "mixin" >
| < PATH: "path" >
| < NODE: "node" >
| < NODES: "nodes" >
| < END: "end" >
| < REPOSITORY: "repository" >
| < PRINCIPAL: "principal" >
| < USER: "user" >
| < GROUP: "group" >
| < NODETYPES: "nodetypes" >
| < REGISTER: "register" >
| < NAMESPACE: "namespace" >
| < PRIVILEGE: "privilege" >
| < WITH: "with" >
| < ABSTRACT: "abstract" >
| < PASSWORD: "password" >
| < START_TEXTBLOCK: "<<===" > : TEXTBLOCK
| < LPAREN: "(" >
| < RPAREN: ")" >
| < LCURLY: "{" >
| < RCURLY: "}" >
| < MULTI: "[]" >
| < COMMA: "," >
| < DQUOTE: "\"" >
| < COLON: ":" >
| < STAR: "*" >
| < EQUALS: "=" >
| < RESTRICTION: "restriction" >
| < ACL_OPTIONS: "ACLOptions" >
| < PROPERTIES: "properties" >
| < SETDEF: "default" >
| < FORCED: "forced" >
/* The order of these fuzzy statements is important (first match wins?) */
| < NAMESPACED_ITEM: (["a"-"z"] | ["A"-"Z"])+ ":" (["a"-"z"] | ["A"-"Z"])+ >
| < PATH_STRING: "/" (["a"-"z"] | ["A"-"Z"] | ["0"-"9"] | ["-"] | ["_"] | ["."] | ["@"] | [":"] | ["+"] | ["/"]) * >
| < PATH_REPOSITORY: ":repository" >
| < STRING: (["a"-"z"] | ["A"-"Z"] | ["0"-"9"] | ["-"] | ["_"] | ["."] | ["/"] | [":"] | ["*"]) + >
| < EOL: "\n" >
}
/* Found out about this syntax later - would make STRING etc. simpler */
TOKEN: {
<QUOTED:
<DQUOTE>
(
"\\" ~[] // escaped chars
|
~["\\","\""] //any char but not backslash nor quote
)*
<DQUOTE>
>
}
<TEXTBLOCK> SKIP :
{
"\r"
}
<TEXTBLOCK> TOKEN :
{
< TEXT : ~[] >
| < END_TEXTBLOCK: "===>>" > : DEFAULT
}
List<Operation> parse() :
{}
{
{ final List<Operation> result = new ArrayList<Operation>(); }
(
serviceUserStatement(result)
| setAclPaths(result)
| setAclPrincipals(result)
| setAclPrincipalBased(result)
| ensureAclPrincipalBased(result)
| setAclRepository(result)
| deleteAclPaths(result)
| deleteAclPrincipals(result)
| deleteAclPrincipalBased(result)
| removeAcePaths(result)
| removeAcePrincipals(result)
| removeAcePrincipalBased(result)
| createPathStatement(result)
| ensureNodesStatement(result)
| addMixins(result)
| removeMixins(result)
| registerNamespaceStatement(result)
| registerNodetypesStatement(result)
| registerPrivilegeStatement(result)
| createGroupStatement(result)
| deleteGroupStatement(result)
| createUserStatement(result)
| deleteUserStatement(result)
| disableUserStatement(result)
| addToGroupStatement(result)
| removeFromGroupStatement(result)
| setPropertiesStatement(result)
| blankLine()
) *
<EOF>
{ return result; }
}
void blankLine() :
{}
{
<EOL>
}
List<String> principalsList() :
{
Token t = null;
List<String> principals = new ArrayList<String>();
}
{
t = quotableString() { principals.add(t.image); }
( <COMMA> t = quotableString() { principals.add(t.image); } )*
{ return principals; }
}
WithPathOptions withPathStatement() :
{
Token path = null;
Token forced = null;
}
{
/* accept relative (string) or absolute path */
<WITH> (forced=<FORCED>)? <PATH> ( path=<STRING> | path=<PATH_STRING> )
{ return new WithPathOptions(path.image, forced != null); }
}
void serviceUserStatement(List<Operation> result) :
{
Token t;
List<String> principals;
WithPathOptions wpopt = null;
}
{
(t=<CREATE> | t=<DELETE>)
<SERVICE> <USER>
principals = principalsList()
( wpopt = withPathStatement() ) ?
(<EOL> | <EOF>)
{
for(String principal : principals) {
if(CREATE == t.kind) {
result.add(new CreateServiceUser(principal, wpopt));
} else {
result.add(new DeleteServiceUser(principal));
}
}
}
}
List<String> namespacedItemsList() :
{
Token t = null;
List<String> priv = new ArrayList<String>();
}
{
t = <NAMESPACED_ITEM> { priv.add(t.image); }
( <COMMA> t = <NAMESPACED_ITEM> { priv.add(t.image); } )*
{ return priv; }
}
List<String> privilegesList() :
{
Token t = null;
List<String> priv = new ArrayList<String>();
}
{
( t=<NAMESPACED_ITEM> | t=<STRING> ) { priv.add(t.image); }
( <COMMA> ( t=<NAMESPACED_ITEM> | t=<STRING> ) { priv.add(t.image); } )*
{ return priv; }
}
String usernameList() :
{
List<String> names = new ArrayList<String>();
Token t = null;
}
{
( t = quotableString() ) { names.add(t.image); }
// disable lists for now, not supported downstream
// ( <COMMA> t = <STRING> { names.add(t.image); }) *
{
return String.join(",", names);
}
}
String pathExpression() :
{
Token t = null;
Token tf = null;
String usernames = null;
final StringBuilder sb = new StringBuilder();
}
{
(
tf = <STRING> <LPAREN> usernames = usernameList() <RPAREN> ( t = <PATH_STRING> ) ?
| ( t = <PATH_STRING>
| t = <PATH_REPOSITORY> )
)
{
if(tf != null) {
sb.append(':').append(tf.image).append(':').append(usernames).append('#');
}
if(t != null) {
sb.append(t.image);
}
}
{ return sb.toString(); }
}
List<String> pathsList() :
{
String pathExpr = null;
List<String> paths = new ArrayList<String>();
}
{
( pathExpr = pathExpression() ) { paths.add(pathExpr); }
( <COMMA> ( pathExpr = pathExpression() ) { paths.add(pathExpr); } )*
{ return paths; }
}
void createPathStatement(List<Operation> result) :
{
CreatePath cp = null;
String defaultPrimaryType = null;
Token t1 = null;
Token t2 = null;
List<String> t3 = null;
List<PropertyLine> lines = new ArrayList<PropertyLine>();
}
{
<CREATE> <PATH>
( <LPAREN> t1 = <NAMESPACED_ITEM> <RPAREN> { defaultPrimaryType = t1.image; } ) ?
( t1 = <PATH_STRING> ( <LPAREN> t2 = <NAMESPACED_ITEM> <RPAREN> ) ?
( <LPAREN> <MIXIN> t3 = namespacedItemsList() <RPAREN>) ?
( <LPAREN> t2 = <NAMESPACED_ITEM> <MIXIN> t3 = namespacedItemsList() <RPAREN>) ?
{
if(cp == null) {
cp = new CreatePath(defaultPrimaryType);
}
cp.addSegment(t1.image, t2 == null ? null : t2.image, t3);
t2 = null;
t3 = null;
}
) +
( <WITH> <PROPERTIES> <EOL>
( propertyLine(lines) | blankLine() ) +
{
cp.setPropertyLines(lines);
}
<END>
)?
(<EOL> | <EOF>)
{ if(cp != null) result.add(cp); }
}
// similar to createPath but with stricter semantics of also changing types of existing nodes
void ensureNodesStatement(List<Operation> result) :
{
EnsureNodes en = null;
String defaultPrimaryType = null;
Token t1 = null;
Token t2 = null;
List<String> t3 = null;
List<PropertyLine> lines = new ArrayList<PropertyLine>();
}
{
<ENSURE> <NODES>
( <LPAREN> t1 = <NAMESPACED_ITEM> <RPAREN> { defaultPrimaryType = t1.image; } ) ?
( t1 = <PATH_STRING> ( <LPAREN> t2 = <NAMESPACED_ITEM> <RPAREN> ) ?
( <LPAREN> <MIXIN> t3 = namespacedItemsList() <RPAREN>) ?
( <LPAREN> t2 = <NAMESPACED_ITEM> <MIXIN> t3 = namespacedItemsList() <RPAREN>) ?
{
if(en == null) {
en = new EnsureNodes(defaultPrimaryType);
}
en.addSegment(t1.image, t2 == null ? null : t2.image, t3);
t2 = null;
t3 = null;
}
) +
( <WITH> <PROPERTIES> <EOL>
( propertyLine(lines) | blankLine() ) +
{
en.setPropertyLines(lines);
}
<END>
)?
(<EOL> | <EOF>)
{ if(en != null) result.add(en); }
}
void addMixins(List<Operation> result) :
{
List<String> mixins = null;
List<String> paths = null;
}
{
<ADD> <MIXIN>
mixins = namespacedItemsList()
<TO>
paths = pathsList()
{
result.add(new AddMixins(mixins, paths));
}
}
void removeMixins(List<Operation> result) :
{
List<String> mixins = null;
List<String> paths = null;
}
{
<REMOVE> <MIXIN>
mixins = namespacedItemsList()
<FROM>
paths = pathsList()
{
result.add(new RemoveMixins(mixins, paths));
}
}
void setAclPaths(List<Operation> result) :
{
List<String> paths;
List<AclLine> lines = new ArrayList<AclLine>();
List<String> aclOptions;
}
{
<SET> <ACL> <ON> paths = pathsList() aclOptions=aclOptions() <EOL>
( removeStarLine(lines) | userPrivilegesLine(lines, true) | blankLine() ) +
<END>
( <EOL> | <EOF> )
{
result.add(new SetAclPaths(paths, lines, aclOptions));
}
}
void removeStarLine(List<AclLine> lines) :
{
List<String> tmp = null;
AclLine line = new AclLine(AclLine.Action.REMOVE_ALL);
}
{
<REMOVE> <STAR>
(
<FOR> tmp = principalsList() { line.setProperty(AclLine.PROP_PRINCIPALS, tmp); }
| <ON> tmp = pathsList() { line.setProperty(AclLine.PROP_PATHS, tmp); }
)
<EOL>
{
lines.add(line);
}
}
AclLine privilegesLineOperation(boolean supportsRemoveAction) :
{
}
{
(
<REMOVE> {
if (supportsRemoveAction) {
return new AclLine(AclLine.Action.REMOVE);
} else {
throw new ParseException("REMOVE action not supported with 'remove acl' statements.");
}}
| ( <ALLOW> { return new AclLine(AclLine.Action.ALLOW); } )
| ( <DENY> { return new AclLine(AclLine.Action.DENY); } )
)
}
void userPrivilegesLine(List<AclLine> lines, boolean supportsRemoveAction) :
{
AclLine line;
List<String> tmp;
List<RestrictionClause> restrictions;
}
{
line = privilegesLineOperation(supportsRemoveAction)
tmp = privilegesList() { line.setProperty(AclLine.PROP_PRIVILEGES, tmp); }
<FOR>
tmp = principalsList() { line.setProperty(AclLine.PROP_PRINCIPALS, tmp); }
restrictions = restrictions() { line.setRestrictions(restrictions); }
<EOL>
{
lines.add(line);
}
}
/**
* A single restriction value
*/
void restrictionValue(List<String> values) :
{
Token t;
}
{
<COMMA> ( t=<STAR> | t=<NAMESPACED_ITEM> | t=<PATH_STRING> | t=<STRING> )
{
values.add(t.image);
}
}
/**
* A list of restriction values
*/
List<String> restrictionValues() :
{
List<String> values = new ArrayList<String>();
}
{
( restrictionValue(values) ) *
{
return values;
}
}
/**
* A single restriction
*/
void restriction(List<RestrictionClause> restrictions) :
{
Token restrictionProp;
List<String> values;
}
{
<RESTRICTION>
<LPAREN> restrictionProp=<NAMESPACED_ITEM> values=restrictionValues() <RPAREN>
{
restrictions.add(new RestrictionClause(restrictionProp.image,values));
}
}
/**
* A list of restrictions
*/
List<RestrictionClause> restrictions() :
{
List<RestrictionClause> restrictions = new ArrayList<RestrictionClause>();
}
{
( restriction(restrictions) )*
{
return restrictions;
}
}
void pathPrivilegesLine(List<AclLine> lines, boolean supportsRemoveAction) :
{
AclLine line;
List<String> tmp;
List<RestrictionClause> restrictions;
}
{
line = privilegesLineOperation(supportsRemoveAction)
tmp = namespacedItemsList() { line.setProperty(AclLine.PROP_PRIVILEGES, tmp); }
<ON> tmp = pathsList() { line.setProperty(AclLine.PROP_PATHS, tmp); }
( <NODETYPES> tmp = namespacedItemsList() { line.setProperty(AclLine.PROP_NODETYPES, tmp); }) ?
restrictions = restrictions() { line.setRestrictions(restrictions); }
<EOL>
{
lines.add(line);
}
}
void aclOption(List<String> options) :
{
Token t;
}
{
(t=<NAMESPACED_ITEM> | t=<STRING>)
{
options.add(t.image);
}
}
List<String> aclOptions() :
{
List<String> aclOptionList = new ArrayList<String>();
Token t;
}
{
( <LPAREN> <ACL_OPTIONS> <EQUALS> aclOption(aclOptionList) ( <COMMA> aclOption(aclOptionList) )* <RPAREN> )?
{
return aclOptionList;
}
}
void setAclRepository(List<Operation> result) :
{
List<AclLine> lines = new ArrayList<AclLine>();
List<String> principals;
List<String> privileges;
List<String> aclOptions;
AclLine line = null;
}
{
<SET> <REPOSITORY> <ACL> <FOR> principals = principalsList() aclOptions=aclOptions() <EOL>
(
( <REMOVE> <STAR> )
{
line = new AclLine(AclLine.Action.REMOVE_ALL);
lines.add(line);
}
| ( line = privilegesLineOperation(true) privileges = namespacedItemsList() )
{
line.setProperty(AclLine.PROP_PRIVILEGES, privileges);
lines.add(line);
}
| ( blankLine() )
)+
<END>
( <EOL> | <EOF> )
{
result.add(new SetAclPrincipals(principals, lines, aclOptions));
}
}
void setAclPrincipals(List<Operation> result) :
{
List <String> principals;
List<AclLine> lines = new ArrayList<AclLine>();
List<String> aclOptions;
}
{
<SET> <ACL> <FOR> principals = principalsList() aclOptions=aclOptions() <EOL>
( removeStarLine(lines) | pathPrivilegesLine(lines, true) | blankLine() ) +
<END>
( <EOL> | <EOF> )
{
result.add(new SetAclPrincipals(principals, lines, aclOptions));
}
}
void setAclPrincipalBased(List<Operation> result) :
{
List <String> principals;
List<AclLine> lines = new ArrayList<AclLine>();
List<String> aclOptions;
}
{
<SET> <PRINCIPAL> <ACL> <FOR> principals = principalsList() aclOptions=aclOptions() <EOL>
( removeStarLine(lines) | pathPrivilegesLine(lines, true) | blankLine() ) +
<END>
( <EOL> | <EOF> )
{
result.add(new SetAclPrincipalBased(principals, lines, aclOptions));
}
}
void ensureAclPrincipalBased(List<Operation> result) :
{
List <String> principals;
List<AclLine> lines = new ArrayList<AclLine>();
List<String> aclOptions;
}
{
<ENSURE> <PRINCIPAL> <ACL> <FOR> principals = principalsList() aclOptions=aclOptions() <EOL>
( removeStarLine(lines) | pathPrivilegesLine(lines, true) | blankLine() ) +
<END>
( <EOL> | <EOF> )
{
result.add(new EnsureAclPrincipalBased(principals, lines, aclOptions));
}
}
void removeAcePaths(List<Operation> result) :
{
List<String> paths;
List<AclLine> lines = new ArrayList<AclLine>();
}
{
<REMOVE> <ACE> <ON> paths = pathsList()<EOL>
( userPrivilegesLine(lines, false) | blankLine() ) +
<END>
( <EOL> | <EOF> )
{
result.add(new RemoveAcePaths(paths, lines));
}
}
void removeAcePrincipals(List<Operation> result) :
{
List <String> principals;
List<AclLine> lines = new ArrayList<AclLine>();
}
{
<REMOVE> <ACE> <FOR> principals = principalsList()<EOL>
( pathPrivilegesLine(lines, false) | blankLine() ) +
<END>
( <EOL> | <EOF> )
{
result.add(new RemoveAcePrincipals(principals, lines));
}
}
void removeAcePrincipalBased(List<Operation> result) :
{
List <String> principals;
List<AclLine> lines = new ArrayList<AclLine>();
}
{
<REMOVE> <PRINCIPAL> <ACE> <FOR> principals = principalsList()<EOL>
( pathPrivilegesLine(lines, false) | blankLine() ) +
<END>
( <EOL> | <EOF> )
{
result.add(new RemoveAcePrincipalBased(principals, lines));
}
}
void deleteAclPaths(List<Operation> result) :
{
List<String> paths;
}
{
<DELETE> <ACL> <ON> paths = pathsList()
( <EOL> | <EOF> )
{
result.add(new DeleteAclPaths(paths));
}
}
void deleteAclPrincipals(List<Operation> result) :
{
List <String> principals;
}
{
<DELETE> <ACL> <FOR> principals = principalsList()
( <EOL> | <EOF> )
{
result.add(new DeleteAclPrincipals(principals));
}
}
void deleteAclPrincipalBased(List<Operation> result) :
{
List <String> principals;
}
{
<DELETE> <PRINCIPAL> <ACL> <FOR> principals = principalsList()
( <EOL> | <EOF> )
{
result.add(new DeleteAclPrincipalBased(principals));
}
}
void registerNamespaceStatement(List<Operation> result) :
{
Token prefix = null;
Token uri;
}
{
<REGISTER> <NAMESPACE>
<LPAREN> prefix = <STRING> <RPAREN>
uri = quotableString()
{
result.add(new RegisterNamespace(prefix.image, uri.image));
}
(<EOL> | <EOF>)
}
void registerPrivilegeStatement(List<Operation> result) :
{
Token privilege;
boolean isAbstract = false;
List<String> aggregates = new ArrayList<String>();
}
{
<REGISTER> ((<ABSTRACT>) {isAbstract = true;})? <PRIVILEGE> (privilege = <STRING> | privilege = <NAMESPACED_ITEM>) (<WITH> aggregates = privilegesList())?
{
result.add(new RegisterPrivilege(privilege.image, isAbstract, aggregates));
}
(<EOL> | <EOF>)
}
void textBlock(StringBuilder b) :
{
Token t;
}
{
<START_TEXTBLOCK>
( t = <TEXT> { b.append(t.image); } )*
<END_TEXTBLOCK>
}
void registerNodetypesStatement(List<Operation> result) :
{
StringBuilder b = new StringBuilder();
}
{
<REGISTER> <NODETYPES> <EOL>
textBlock(b)
(<EOL> | <EOF>)
{
result.add(new RegisterNodetypes(b.toString()));
}
}
void createGroupStatement(List<Operation> result) :
{
Token group = null;
Token encoding = null;
WithPathOptions wpopt = null;
}
{
<CREATE> <GROUP>
( group = quotableString() )
( wpopt = withPathStatement() ) ?
{
result.add(new CreateGroup(group.image, wpopt));
}
}
void deleteGroupStatement(List<Operation> result) :
{
Token group = null;
}
{
<DELETE> <GROUP>
( group = quotableString() )
{
result.add(new DeleteGroup(group.image));
}
}
void createUserStatement(List<Operation> result) :
{
Token user = null;
Token encoding = null;
Token password = null;
WithPathOptions wpopt = null;
}
{
<CREATE> <USER>
( user = <STRING> )
( wpopt = withPathStatement() ) ?
( <WITH> <PASSWORD> ( <LCURLY> encoding = <STRING> <RCURLY> )? password = <STRING> )?
{
result.add(new CreateUser(user.image,
(encoding == null ? null : encoding.image),
(password == null ? null : password.image),
wpopt
));
}
}
void deleteUserStatement(List<Operation> result) :
{
Token user = null;
}
{
<DELETE> <USER>
( user = <STRING> )
{
result.add(new DeleteUser(user.image));
}
}
Token quotedString() :
{
Token t = null;
}
{
( t = <QUOTED> )
{
// Remove start/end quotes + escaping backslash for double quotes
final String unescaped = t.image.trim()
.replaceAll("^\"|\"$", "")
.replaceAll("\\\\\"", "\"")
.replaceAll("\\\\\\\\", "\\\\")
;
return new Token(t.kind, unescaped);
}
}
void disableUserStatement(List<Operation> result) :
{
Token user = null;
Token msg = null;
Token isServiceUser = null;
}
{
<DISABLE> ( isServiceUser = <SERVICE> )? <USER>
( user = <STRING> )
( <COLON> msg = quotedString() )
{
DisableServiceUser dsu = new DisableServiceUser(user.image, msg.image);
dsu.setServiceUser(isServiceUser != null);
result.add(dsu);
}
}
void addToGroupStatement(List<Operation> result) :
{
List <String> members;
Token t;
Token group = null;
}
{
<ADD>
members = principalsList()
<TO> <GROUP>
group = quotableString()
{
result.add(new AddGroupMembers(members, group.image));
}
}
void removeFromGroupStatement(List<Operation> result) :
{
List <String> members;
Token t;
Token group = null;
}
{
<REMOVE>
members = principalsList()
<FROM> <GROUP>
group = quotableString()
{
result.add(new RemoveGroupMembers(members, group.image));
}
}
Token quotableString() :
{
Token t = null;
}
{
(t = <STRING> | t = quotedString() ) { return t; }
}
Token propertyValue() :
{
Token t = null;
}
{
(t = <STRING> | t = quotedString() | t = <PATH_STRING> | t = <NAMESPACED_ITEM> ) { return t; }
}
List<String> propertyValuesList() :
{
Token t = null;
List<String> values = new ArrayList<String>();
}
{
(( t = propertyValue() ) { values.add(t.image); })?
( <COMMA> ( t = propertyValue() ) { values.add(t.image); } )*
{ return values; }
}
void propertyLine(List<PropertyLine> lines) :
{
Token name = null;
Token type = null;
List<String> values;
Token t = null;
boolean isDefault = false;
}
{
(t = <SET> | t = <SETDEF> {isDefault = true;} )
( name = <STRING> | name = <NAMESPACED_ITEM>)
( <LCURLY> ( type = <STRING> ) <RCURLY> | <LCURLY> ( type = <STRING> ) <MULTI> <RCURLY> )?
<TO> ( values = propertyValuesList() )
<EOL>
{
lines.add(new PropertyLine(name.image, type == null ? null : type.image, values, isDefault));
}
}
void setPropertiesStatement(List<Operation> result) :
{
List<PropertyLine> lines = new ArrayList<PropertyLine>();
List<String> paths;
}
{
<SET> <PROPERTIES> <ON> ( paths = pathsList() ) <EOL>
( propertyLine(lines) | blankLine() ) +
<END>
( <EOL> | <EOF> )
{
result.add(new SetProperties(paths, lines));
}
}