Package com.bazaarvoice.jless.parser

Source Code of com.bazaarvoice.jless.parser.Parser

/**
* Copyright 2010 Bazaarvoice, Inc.
*
* Licensed 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.
*
* @author J. Ryan Stinnett (ryan.stinnett@bazaarvoice.com)
*/

package com.bazaarvoice.jless.parser;

import com.bazaarvoice.jless.ast.node.ArgumentsNode;
import com.bazaarvoice.jless.ast.node.ExpressionGroupNode;
import com.bazaarvoice.jless.ast.node.ExpressionNode;
import com.bazaarvoice.jless.ast.node.ExpressionPhraseNode;
import com.bazaarvoice.jless.ast.node.FilterArgumentNode;
import com.bazaarvoice.jless.ast.node.FunctionNode;
import com.bazaarvoice.jless.ast.node.InternalNode;
import com.bazaarvoice.jless.ast.node.LineBreakNode;
import com.bazaarvoice.jless.ast.node.MediaQueryNode;
import com.bazaarvoice.jless.ast.node.MediaTypeNode;
import com.bazaarvoice.jless.ast.node.MediaTypeRestriction;
import com.bazaarvoice.jless.ast.node.Node;
import com.bazaarvoice.jless.ast.node.ParametersNode;
import com.bazaarvoice.jless.ast.node.PlaceholderNode;
import com.bazaarvoice.jless.ast.node.PropertyNode;
import com.bazaarvoice.jless.ast.node.RuleSetNode;
import com.bazaarvoice.jless.ast.node.ScopeNode;
import com.bazaarvoice.jless.ast.node.SelectorGroupNode;
import com.bazaarvoice.jless.ast.node.SelectorNode;
import com.bazaarvoice.jless.ast.node.SelectorSegmentNode;
import com.bazaarvoice.jless.ast.node.SimpleNode;
import com.bazaarvoice.jless.ast.node.SpacingNode;
import com.bazaarvoice.jless.ast.node.VariableDefinitionNode;
import com.bazaarvoice.jless.ast.node.VariableReferenceNode;
import com.bazaarvoice.jless.ast.node.WhiteSpaceCollectionNode;
import com.bazaarvoice.jless.ast.util.NodeTreeUtils;
import com.bazaarvoice.jless.exception.UndefinedMixinException;
import com.bazaarvoice.jless.exception.UndefinedVariableException;
import org.parboiled.BaseParser;
import org.parboiled.Context;
import org.parboiled.Rule;
import org.parboiled.annotations.MemoMismatches;
import org.parboiled.support.Var;

import java.util.List;

/**
* Initially transcribed into Parboiled from the
* <a href="http://github.com/cloudhead/less/blob/master/lib/less/engine/grammar">LESS Treetop grammar</a>
* that was created by Alexis Sellier as part of the LESS Ruby implementation.
*
* From there, the parser has been modified to more closely support Parboiled style and a modified set of requirements.
* Parsing differences from LESS Ruby include:
* <ul>
*   <li>Selector parsing was rewritten using the <a href="http://www.w3.org/TR/css3-selectors/#grammar">CSS 3 selector grammar</a></li>
*   <li>Numbers in attribute selectors must be quoted (as per the CSS spec)</li>
*   <li>Case-sensitivity was removed</li>
*   <li>Line breaks can be used in all places where spaces are accepted</li>
* </ul>
*
* This parser attempts to track line breaks so that the input and output files can have the same number of lines
* (which is helpful when referencing styles via browser tools like Firebug).
*
* This list only notes changes in the <em>parsing</em> stage. See {@link com.bazaarvoice.jless.LessProcessor} for details on any changes
* to the <em>translation</em> stage.
*
* @see com.bazaarvoice.jless.LessProcessor
*/
public class Parser extends BaseParser<Node> {

    private boolean _parserTranslationEnabled;

    public Parser() {
        this(true);
    }

    public Parser(Boolean parserTranslationEnabled) {
        _parserTranslationEnabled = parserTranslationEnabled;
    }

    protected boolean isParserTranslationEnabled() {
        return _parserTranslationEnabled;
    }

    // ********** Document **********

    public Rule Document() {
        return Sequence(Scope(), EOI);
    }

    /**
     * This is the high-level rule at some scope (either the root document or within a rule set / mixin).
     * Future: Imports
     */
    Rule Scope() {
        return Sequence(
                push(new ScopeNode()),
                ZeroOrMore(
                        FirstOf(
                                Declaration(),
                                MediaQuery(),
                                RuleSet(),
                                MixinReference(),
                                Sequence(push(new WhiteSpaceCollectionNode()), Sp1Nodes())
                        ),
                        peek(1).addChild(pop())
                )
        );
    }

    // ********** CSS Rule Sets & Mixins **********

    /**
     * "@media" Ws0 "only "? MediaType
     */
    Rule MediaQuery() {
        return Sequence(
            push(new MediaQueryNode()),
            MediaQueryDefinition(), peek(1).addChild(pop()), peek().addChild(new SimpleNode(" ")),
            Ws0(),
            Optional(OnlyIndicator()),
            OneOrMore(MediaType()), Ws0(),
            '{', Scope(), '}',
            peek(1).addChild(pop()),
            Ws0Nodes()
        );
    }

    /**
     * media type name Optional( and "(" Css property ")")
     */
    Rule MediaType() {
        return Sequence(
            push(new MediaTypeNode()),
            MediaTypeName(),
            peek(1).addChild(pop()),
            FirstOf(
                Sequence(
                    MediaTypeRestriction(),
                    peek(1).addChild(pop())
                ),
                Optional(Ws0(), push(new SpacingNode(" ")), peek(1).addChild(pop()))
            ),
            peek(1).addChild(pop())
        );
    }

    /**
    * simple media type name
    */
    Rule MediaTypeName() {
        return Sequence(
              OneOrMore(NameCharacter()),
              push(new SimpleNode(match()))
        );
    }

    /**
     * Sequence ( Whitespace "and" Whitespace "(" Css Property ")" )
     */
    Rule MediaTypeRestriction() {
        return Sequence(
            push(new MediaTypeRestriction()),
            Ws0(), "and", Ws0(), '(',
            Sequence(
                    Ident(), peek().addChild(new SimpleNode(match())),
                    ':', peek().addChild(new SimpleNode(":")),
                    Optional(Ws0()),
                    peek().addChild(new SpacingNode(" ")),
                    ExpressionPhrase()
            ),
            ')',
            peek(1).addChild(pop())
        );
    }

    /**
     * "only" Whitespace
     */
    Rule OnlyIndicator() {
        return Sequence(
                "only",
                peek().addChild(new SimpleNode(match())), peek().addChild(new SimpleNode(" ")),
                Ws0()
        );
    }

    /**
     * (Selectors '{' Ws0 / Class Ws0 Parameters Ws0 '{' Ws0) Scope Ws0 '}' Ws0
     *
     * Ex: div, .class, body > p {...}
     *
     * Future: Hidden
     */
    Rule RuleSet() {
        return Sequence(
                FirstOf(
                        // Standard CSS rule set
                        Sequence(
                                SelectorGroup(), push(new RuleSetNode(pop())),
                                '{',
                                Scope(), peek(1).addChild(pop()), Ws0()
                        ),
                        // Mixin rule set, with possible arguments
                        Sequence(
                                ClassSelectorGroup(), push(new RuleSetNode(pop())), Ws0(),
                                Parameters(), Ws0(),
                                '{',
                                Scope(), peek(1).addChild(pop()), peek(1).addChild(pop()), Ws0()
                        )
                ),
                '}', Ws0Nodes()
        );
    }

    /**
     * '(' Parameter Ws0 (',' Ws0 Parameter)* ')'
     */
    Rule Parameters() {
        return Sequence(
                '(',
                Parameter(), push(new ParametersNode(pop())), Ws0(),
                ZeroOrMore(
                        ',', Ws0(),
                        Parameter(), peek(1).addChild(pop())
                ),
                ')',
                push(new ScopeNode(pop()))
        );
    }

    /**
     * Variable Ws0 ':' Ws0 ExpressionPhrase
     */
    Rule Parameter() {
        return Sequence(
                Variable(), push(new VariableDefinitionNode(match())), peek().setVisible(!isParserTranslationEnabled()), Ws0(),
                ':', Ws0(),
                ExpressionPhrase(), peek(1).addChild(new ExpressionGroupNode(pop()))
        );
    }

    /**
     * SelectorGroup ';' Ws0
     * / Class Arguments ';' Ws0
     */
    @MemoMismatches
    Rule MixinReference() {
        Var<String> name = new Var<String>();
        return FirstOf(
                // No arguments, reference an existing rule set's properties
                Sequence(
                        SelectorGroup(), ';',
                        resolveMixinReference(NodeTreeUtils.getFirstChild((InternalNode) pop(), SelectorNode.class).toString(), null),
                        Ws0Nodes()
                ),
                // Call a mixin, passing along some arguments
                Sequence(
                        Class(), name.set(match()), Arguments(), Ws0(), ';',
                        resolveMixinReference(name.get(), (ArgumentsNode) pop()),
                        Ws0Nodes()
                )
        );
    }

    // ********** CSS Selectors **********

    /**
     * Selector Ws0 (',' Ws0 Selector)* Ws0
     */
    @MemoMismatches
    Rule SelectorGroup() {
        return Sequence(
                Selector(), push(new SelectorGroupNode(pop())), Ws0Nodes(),
                ZeroOrMore(
                        ',', Ws0Nodes(),
                        Selector(), peek(1).addChild(pop())
                ),
                Ws0Nodes()
        );
    }

    /**
     * Special case rule that builds a selector for only a single class
     */
    Rule ClassSelectorGroup() {
        return Sequence(
                Class(),
                push(new SelectorGroupNode(new SelectorNode(new SelectorSegmentNode("", match()))))
        );
    }

    /**
     * SimpleSelector (Combinator SimpleSelector)*
     */
    Rule Selector() {
        Var<SelectorSegmentNode> selectorSegmentNode = new Var<SelectorSegmentNode>();
        return Sequence(
                push(new SelectorNode()),
                // First selector segment may have a combinator (with nested rule sets)
                Optional(SymbolCombinator()),
                selectorSegmentNode.set(new SelectorSegmentNode(match())),
                SimpleSelector(selectorSegmentNode),
                selectorSegmentNode.get().setSimpleSelector(match()),
                peek().addChild(selectorSegmentNode.getAndClear()),
                // Additional selector segments must have a combinator
                ZeroOrMore(
                        Combinator(),
                        selectorSegmentNode.set(new SelectorSegmentNode(match())),
                        SimpleSelector(selectorSegmentNode),
                        selectorSegmentNode.get().setSimpleSelector(match()),
                        peek().addChild(selectorSegmentNode.getAndClear())
                )
        );
    }

    /**
     * Ws0 [+>~] Ws0 / Ws1
     */
    Rule Combinator() {
        return FirstOf(
                Sequence(Ws0(), SymbolCombinator()),
                DescendantCombinator()
        );
    }

    Rule SymbolCombinator() {
        return Sequence(AnyOf("+>~"), Ws0());
    }

    Rule DescendantCombinator() {
        return Ws1();
    }

    Rule SimpleSelector(Var<SelectorSegmentNode> selectorSegmentNode) {
        return FirstOf(
                Sequence(
                        FirstOf(
                                ID(), Class(), Attribute(), Negation(), ElementName(),
                                Sequence(
                                        FirstOf(
                                                UniversalHtml(),
                                                Universal()
                                        ),
                                        selectorSegmentNode.get().setUniversal(true)
                                ),
                                Sequence(
                                        Pseudo(),
                                        selectorSegmentNode.get().setSubElementSelector(true)
                                )
                        ),
                        ZeroOrMore(FirstOf(ID(), Class(), Attribute(), Negation(), Pseudo()))
                ),
                "@media",
                "@font-face"
        );
    }

    Rule Attribute() {
        return Sequence(
                '[', Ws0(),
                Ident(), Ws0(),
                Optional(
                        Optional(AnyOf("~|^$*")),
                        '=', Ws0(),
                        FirstOf(Ident(), String()), Ws0()
                ),
                ']'
        );
    }

    Rule Pseudo() {
        return Sequence(':', Optional(':' ), FirstOf(FunctionalPseudo(), Ident()));
    }

    Rule FunctionalPseudo() {
        return Sequence(Ident(), '(', Ws0(), PseudoExpression(), ')');
    }

    Rule PseudoExpression() {
        return OneOrMore(FirstOf(AnyOf("+-"), Dimension(), Digit(), String(), Ident()), Ws0());
    }

    Rule Negation() {
        return Sequence(":not(", Ws0(), NegationArgument(), Ws0(), ')');
    }

    Rule NegationArgument() {
        return FirstOf(ElementName(), Universal(), ID(), Class(), Attribute(), Pseudo());
    }

    // ********** Variables & Expressions **********

    /**
     * (Ident / Variable) Ws0 ':' Ws0 ExpressionPhrase (Ws0 ',' Ws0 ExpressionPhrase)* Sp0 (';' / Ws0 &'}')
     * / Ident Ws0 ':' Ws0 ';'
     *
     * Ex: @my-var: 12px; height: 100%;
     */
    Rule Declaration() {
        return FirstOf(
                Sequence(
                        FirstOf(
                                Sequence(PropertyName(), push(new PropertyNode(match()))),
                                Sequence(Variable(), push(new VariableDefinitionNode(match())), peek().setVisible(!isParserTranslationEnabled()))
                        ), Ws0(),
                        ':', Ws0(),
                        push(new ExpressionGroupNode()),
                        ExpressionPhrase(), peek(1).addChild(pop()),
                        ZeroOrMore(
                                Ws0(), ',', Ws0Nodes(),
                                ExpressionPhrase(), peek(1).addChild(pop())
                        ),
                        Sp0(), FirstOf(';', Sequence(Ws0(), Test('}' ))),
                        peek(1).addChild(pop())
                ),
                // Empty rules are ignored
                Sequence(
                        Ident(), push(new PlaceholderNode()), Ws0(),
                        ':', Ws0(),
                        ';'
                )
        );
    }

    Rule Variable() {
        return Sequence('@', Ident());
    }

    /**
     * Expression (Operator Expression)+ Future: Operations
     * / Expression (Ws1 Expression)* Important?
     */
    Rule ExpressionPhrase() {
        // Space-separated expressions
        return Sequence(
                Expression(), push(new ExpressionPhraseNode(pop())),
                ZeroOrMore(Ws1(), Expression(), peek(1).addChild(pop())),
                Optional(Ws0(), Important(), peek(1).addChild(pop()))
        );
    }

    /**
     * '(' Ws0 ExpressionPhrase Ws0 ')' Future: Operations
     * / Entity
     */
    Rule Expression() {
        return Sequence(Value(), push(new ExpressionNode(pop())));
    }

    /**
     * '!' Ws0 'important'
     */
    Rule Important() {
        return Sequence('!', Ws0(), "important", push(new SimpleNode("!important")));
    }

    // ********** Browser Hack Workarounds **********

    Rule PropertyName() {
        return Sequence(Optional(AnyOf("*_")), Ident());
    }

    Rule UniversalHtml() {
        return Sequence(Universal(), Ws0(), "html");
    }

    // ********** CSS Entities **********

    Rule ElementName() {
        return Ident();
    }

    Rule Universal() {
        return Ch('*' );
    }

    Rule ID() {
        return Sequence('#', Name());
    }

    @MemoMismatches
    Rule Class() {
        return Sequence('.', Ident());
    }

    // ********** Functions & Arguments **********

    /**
     * Ident Arguments
     */
    Rule Function() {
        return Sequence(
                Ident(), push(new FunctionNode(match())),
                Arguments(), peek(1).addChild(pop())
        );
    }

    /**
     * '(' Ws0 ExpressionPhrase Ws0 (',' Ws0 ExpressionPhrase Ws0)* ')' / '(' Ws0 ')'
     */
    Rule Arguments() {
        return FirstOf(
                Sequence(
                        Ws0(), '(', Ws0(),
                        ExpressionPhrase(), push(new ArgumentsNode(new ExpressionGroupNode(pop()))),
                        Ws0(),
                        ZeroOrMore(
                                ',', Ws0(),
                                ExpressionPhrase(), peek(1).addChild(new ExpressionGroupNode(pop())),
                                Ws0()
                        ),
                        ')'
                ),
                Sequence(Ws0(), '(', Ws0(), ')', push(new ArgumentsNode()))
        );
    }

    /**
     * Ex: progid:DXImageTransform.Microsoft.gradient(startColorstr=@lightColor, endColorstr=@darkColor)
     */
    Rule FilterFunction() {
        return Sequence(
                Sequence(
                        "progid:",
                        OneOrMore(FirstOf('.', Ident()))
                ),
                push(new FunctionNode(match())),
                FilterArguments(),
                peek(1).addChild(pop())
        );
    }

    /**
     * '(' Ws0 FilterArgument Ws0 (',' Ws0 FilterArgument Ws0)* ')' / '(' Ws0 ')'
     */
    Rule FilterArguments() {
        return FirstOf(
                Sequence(
                        '(', Ws0(),
                        FilterArgument(), push(new ArgumentsNode(pop())),
                        Ws0(),
                        ZeroOrMore(
                                ',', Ws0(),
                                FilterArgument(), peek(1).addChild(pop()),
                                Ws0()
                        ),
                        ')'
                ),
                Sequence('(', Ws0(), ')', push(new ArgumentsNode()))
        );
    }

    /**
     * Ex: startColorstr=@lightColor
     */
    Rule FilterArgument() {
        return Sequence(
                Ident(), push(new FilterArgumentNode(match())), Ws0(),
                '=', Ws0(),
                Value(), peek(1).addChild(pop())
        );
    }

    // ********** LESS Entities **********

    /**
     * Any token used as a value in an expression
     * Future: Accessors
     */
    Rule Value() {
        return FirstOf(
                Keyword(),
                Literal(),
                Function(),
                VariableReference(),
                URL(),
                Font(),
                AlphaFilter(),
                ExpressionFunction(),
                FilterFunction()
        );
    }

    @MemoMismatches
    Rule VariableReference() {
        return Sequence(
                Variable(),
                pushVariableReference(match())
        );
    }

    Rule MediaQueryDefinition() {
        return Sequence(
                "@media",
                push(new SimpleNode(match()))
        );
    }

    /**
     * 'url(' (String / [-_%$/.&=:;#+?Alphanumeric]+) ')'
     */
    Rule URL() {
        return Sequence(
                Sequence(
                        "url(", Ws0(),
                        FirstOf(
                                String(),
                                OneOrMore(FirstOf(AnyOf("-_%$/.&=:;#+?"), Alphanumeric()))
                        ), Ws0(),
                        ')'
                ),
                push(new SimpleNode(match()))
        );
    }

    /**
     * 'alpha(' Ws0 'opacity' Ws0 '=' Ws0 Digit1 Ws0 ')'
     */
    Rule AlphaFilter() {
        return Sequence(
                Sequence(
                        "alpha(", Ws0(),
                        "opacity", Ws0(),
                        '=', Ws0(),
                        Digit1(), Ws0(),
                        ')'
                ),
                push(new SimpleNode(match()))
        );
    }

    /**
     * 'expression(' (!(')' Ws0 [;}]) .)* ');'
     */
    Rule ExpressionFunction() {
        return Sequence(
                Sequence(
                        "expression(",
                        ZeroOrMore(
                                TestNot(
                                        ')',
                                        Ws0(),
                                        AnyOf(";}")
                                ),
                                ANY
                        ),
                        ')'
                ),
                push(new SimpleNode(match()))
        );
    }

    /**
     * Ident &Delimiter
     *
     * Ex: blue, small, normal
     */
    Rule Keyword() {
        return Sequence(
                Sequence(Ident(), Test(Delimiter())),
                push(new SimpleNode(match()))
        );
    }

    /**
     * Tokens that don't need to evaluated
     */
    Rule Literal() {
        return Sequence(
                FirstOf(Color(), MultiDimension(), Dimension(), String()),
                push(new SimpleNode(match()))
        );
    }

    /**
     * Alpha [-Alphanumeric]* &Delimiter / String
     */
    Rule Font() {
        return Sequence(
                FirstOf(
                        Sequence(Alpha(), ZeroOrMore(FirstOf('-', Alphanumeric())), Test(Delimiter())),
                        String()
                ),
                push(new SimpleNode(match()))
        );
    }

    /**
     * Ex: 'hello' / "hello"
     */
    Rule String() {
        return FirstOf(
                Sequence('\'', ZeroOrMore(TestNot('\'' ), ANY), '\'' ),
                Sequence('"', ZeroOrMore(TestNot('"' ), ANY), '"' )
        );
    }

    /**
     * Some CSS properties allow multiple dimensions separated by '/'
     *
     * (Dimension / Ident) '/' Dimension
     */
    Rule MultiDimension() {
        return Sequence(
                FirstOf(
                        Dimension(),
                        Ident()
                ),
                '/',
                Dimension()
        );
    }

    /**
     * Number Unit
     */
    @MemoMismatches
    Rule Dimension() {
        return Sequence(Number(), Unit());
    }

    /**
     * '-'? Digit* '.' Digit+ / '-'? Digit+
     */
    Rule Number() {
        return FirstOf(
                Sequence(Optional('-'), Digit0(), '.', Digit1()),
                Sequence(Optional('-'), Digit1())
        );
    }

    Rule Unit() {
        return Optional(FirstOf('%', Ident()));
    }

    /**
     * '#' RGB
     */
    Rule Color() {
        return Sequence('#', RGB());
    }

    /**
     * Ex: 0099dd / 09d
     */
    Rule RGB() {
        return FirstOf(
                Sequence(Hex(), Hex(), Hex(), Hex(), Hex(), Hex()),
                Sequence(Hex(), Hex(), Hex())
        );
    }

    // ********** Comments **********

    Rule Comment() {
        return FirstOf(MultipleLineComment(), SingleLineComment());
    }

    /**
     * '//' (!LB .)* LB Ws0
     */
    Rule SingleLineComment() {
        return Sequence(
                "//", ZeroOrMore(TestNot(AnyOf("\r\n")), ANY),
                FirstOf('\n', "\r\n"), Ws0()
        );
    }

    /**
     * '/*' (!'*\/' .)* '*\/' Ws0
     */
    Rule MultipleLineComment() {
        return Sequence(
                "/*", ZeroOrMore(TestNot("*/"), ANY),
                "*/", Ws0()
        );
    }

    // ********** Characters & Simple Character Groups **********

    Rule Alphanumeric() {
        return FirstOf(Alpha(), Digit());
    }

    Rule Alpha() {
        return FirstOf(CharRange('a', 'z' ), CharRange('A', 'Z' ));
    }

    Rule Digit0() {
        return ZeroOrMore(Digit());
    }

    Rule Digit1() {
        return OneOrMore(Digit());
    }

    Rule Digit() {
        return CharRange('0', '9' );
    }

    Rule Hex() {
        return FirstOf(CharRange('a', 'f' ), CharRange('A', 'F' ), Digit());
    }

    Rule Ws0() {
        return ZeroOrMore(Whitespace());
    }

    Rule Ws0Nodes() {
        return ZeroOrMore(WhitespaceNode());
    }

    Rule Ws1() {
        return OneOrMore(Whitespace());
    }

    Rule Whitespace() {
        return AnyOf(" \r\n\t\f");
    }

    Rule WhitespaceNode() {
        return FirstOf(
                Sequence(OneOrMore(AnyOf(" \t")), peek().addChild(new SpacingNode(match()))),
                Sequence(FirstOf('\n', "\r\n", '\r', '\f' ), peek().addChild(new LineBreakNode(1)))
        );
    }

    Rule Sp0() {
        return ZeroOrMore(Spacing());
    }

    Rule Sp1() {
        return OneOrMore(Spacing());
    }

    Rule Sp1Nodes() {
        return OneOrMore(SpacingNode());
    }

    Rule Spacing() {
        return FirstOf(Whitespace(), Comment());
    }

    Rule SpacingNode() {
        return FirstOf(
                WhitespaceNode(),
                Sequence(Comment(), peek().addChild(new SpacingNode(match())))
        );
    }

    Rule Delimiter() {
        return FirstOf(AnyOf(";,!})"), Whitespace());
    }

    Rule NameStart() {
        return FirstOf('_', Alpha(), NonAscii(), Escape());
    }

    Rule NameCharacter() {
        return FirstOf(AnyOf("-_"), Alphanumeric(), NonAscii(), Escape());
    }

    /**
     * nonascii [^\0-\177]
     */
    Rule NonAscii() {
        return Sequence(TestNot(CharRange((char) 0, (char) 127)), ANY);
    }

    /**
     * '\' [0-9a-fA-F]{1,6} wc?
     */
    Rule Unicode() {
        return Sequence(
                '\\',
                Hex(), Optional(Hex(), Optional(Hex(), Optional(Hex(), Optional(Hex(), Optional(Hex()))))),
                Optional(Whitespace())
        );
    }

    /**
     * unicode | '\' [^\x0-\x1F]
     */
    Rule Escape() {
        return Sequence(
                Test('\\'), // for performance
                FirstOf(
                        Unicode(),
                        Sequence('\\', Sequence(TestNot(CharRange((char) 0, (char) 31)), ANY))
                )
        );
    }

    @MemoMismatches
    Rule Ident() {
        return Sequence(Optional('-'), NameStart(), ZeroOrMore(NameCharacter()));
    }

    Rule Name() {
        return OneOrMore(NameCharacter());
    }

    // ********** Translation Actions **********

    /**
     * Locates the referenced mixin in one of the scope nodes on the stack. If found, the mixin's
     * scope is cloned and placed onto the stack in place of the mixin reference. Additionally,
     * any arguments are applied to the mixin's scope.
     */
    boolean resolveMixinReference(String name, ArgumentsNode arguments) {
        if (!isParserTranslationEnabled()) {
            return push(new PlaceholderNode(new SimpleNode(name)));
        }

        // Walk down the stack, looking for a scope node that knows about a given rule set
        for (Node node : getContext().getValueStack()) {
            if (!(node instanceof ScopeNode)) {
                continue;
            }

            ScopeNode scope = (ScopeNode) node;
            RuleSetNode ruleSet = scope.getRuleSet(name);

            if (ruleSet == null) {
                continue;
            }

            // Get the scope of the rule set we located and call it as a mixin
            ScopeNode ruleSetScope = NodeTreeUtils.getFirstChild(ruleSet, ScopeNode.class).callMixin(name, arguments);

            return push(ruleSetScope);
        }

        // Record error location
        throw new UndefinedMixinException(name);
    }

    /**
     * Looks for a variable definition that matches the reference in the scope nodes on the stack.
     * If found, a reference node that can repeat this lookup later is placed on the stack, not the
     * current value itself. This is done because the value may change if the variable reference is
     * inside a mixin.
     */
    boolean pushVariableReference(String name) {
        if (!isParserTranslationEnabled()) {
            return push(new SimpleNode(name));
        }

        // Walk down the stack, looking for a scope node that knows about a given variable
        for (Node node : getContext().getValueStack()) {
            if (!(node instanceof ScopeNode)) {
                continue;
            }

            // Ensure that the variable exists
            ScopeNode scope = (ScopeNode) node;
            if (!scope.isVariableDefined(name)) {
                continue;
            }

            return push(new VariableReferenceNode(name));
        }

        // Record error location
        throw new UndefinedVariableException(name);
    }

    // ********** Debugging **********

    boolean debug(Context context) {
        return true;
    }
}
TOP

Related Classes of com.bazaarvoice.jless.parser.Parser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.