001/*
002 * Copyright (C) 2009-2011 Mathias Doenitz
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.parboiled.support;
018
019import org.parboiled.common.Factory;
020import org.parboiled.common.Reference;
021
022import java.util.LinkedList;
023
024import static org.parboiled.common.Preconditions.checkArgNotNull;
025
026/**
027 * <p>This class provides a "local variable"-like construct for action expressions in parser rule methods.
028 * {@literal Var<T>} objects wrap an internal value of an arbitrary (reference) type, can have an initial value,
029 * allow read/write access to their values and can be passed around as parameters to nested rule methods.
030 * Each rule invocation (i.e. rule matching attempt) receives its own {@literal Var<T>} scope (which is automatically
031 * initialized with the initial value), so actions in recursive rules work just like expected.</p>
032 * <p>{@literal Var<T>} objects generally behave just like local variables with one exception:<br/>
033 * When rule method A() passes a Var defined in its scope to another rule method B() as a parameter and an action
034 * in rule method B() writes to this Var all actions in rule method A() running after B() will "see" this newly written
035 * value (since values in {@literal Var<T>} objects are passed by reference)</p>
036 *
037 * @param <T> the type wrapped by this Var
038 */
039public class Var<T> extends Reference<T> {
040
041    private Factory<T> initialValueFactory;
042    private LinkedList<T> stack;
043    private int level;
044    private String name;
045
046    /**
047     * Initializes a new Var with a null initial value.
048     */
049    public Var() {
050        this((T) null);
051    }
052
053    /**
054     * Initializes a new Var with the given initial value.
055     *
056     * @param value the value
057     */
058    public Var(final T value) {
059        super(value);
060        initialValueFactory = new Factory<T>() {
061            public T create() {
062                return value;
063            }
064        };
065    }
066
067    /**
068     * Initializes a new Var. The given factory will be used to create the initial value for each "execution frame"
069     * of the enclosing rule.
070     *
071     * @param initialValueFactory the factory used to create the initial value for a rule execution frame
072     */
073    public Var(Factory<T> initialValueFactory) {
074        this.initialValueFactory = checkArgNotNull(initialValueFactory, "initialValueFactory");
075    }
076
077    /**
078     * Gets the name of this Var.
079     *
080     * @return the name
081     */
082    public String getName() {
083        return name;
084    }
085
086    /**
087     * Sets the name of this Var.
088     *
089     * @param name the name
090     */
091    public void setName(String name) {
092        this.name = name;
093    }
094
095    /**
096     * Returns the current frame level of this variable, the very first level corresponding to zero.
097     *
098     * @return the current level
099     */
100    public int getLevel() {
101        return level;
102    }
103
104    /**
105     * Provides a new frame for the variable.
106     * Potentially existing previous frames are saved.
107     * Normally you do not have to call this method manually as parboiled provides for automatic Var frame management.
108     *
109     * @return true
110     */
111    public boolean enterFrame() {
112        if (level++ > 0) {
113            if (stack == null) stack = new LinkedList<T>();
114            stack.add(get());
115        }
116        return set(initialValueFactory.create());
117    }
118
119    /**
120     * Exits a frame previously entered with {@link #enterFrame()}.
121     * Normally you do not have to call this method manually as parboiled provides for automatic Var frame management.
122     *
123     * @return true
124     */
125    public boolean exitFrame() {
126        if (--level > 0) {
127            set(stack.removeLast());
128        }
129        return true;
130    }
131
132    @Override
133    public String toString() {
134        return name != null ? name : super.toString();
135    }
136
137}