/*
* xtc - The eXTensible Compiler
* Copyright (C) 2007 Robert Grimm
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*/
package xtc.parser;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import xtc.tree.Visitor;
import xtc.type.AST;
import xtc.type.Type;
import xtc.type.Wildcard;
import xtc.util.Runtime;
/**
* Visitor to add lists as semantic values.
*
* @author Robert Grimm
* @version $Revision: 1.9 $
*/
public class ListMaker extends Visitor {
/** The marker for synthetic variables. */
public static final String MARKER = "l";
/** The runtime. */
protected final Runtime runtime;
/** The analyzer. */
protected final Analyzer analyzer;
/** The type operations. */
protected final AST ast;
/** The current production's element type. */
protected Type element;
/** The list of elements. */
protected List<Element> elements;
/**
* Create a new list maker.
*
* @param runtime The runtime.
* @param analyzer The analyzer.
* @param ast The type operations.
*/
public ListMaker(Runtime runtime, Analyzer analyzer, AST ast) {
this.runtime = runtime;
this.analyzer = analyzer;
this.ast = ast;
elements = new ArrayList<Element>();
}
/** Visit the specified module. */
public void visit(Module m) {
// Initialize the per-grammar state.
analyzer.register(this);
analyzer.init(m);
elements.clear();
// Process the productions.
for (Production p : m.productions) {
if (AST.isList(p.type)) {
// Initialize the element type.
if (runtime.test("optionVariant") &&
AST.isDynamicNode(AST.getArgument(p.type))) {
element = Wildcard.TYPE;
} else {
element = null;
}
// Actually process the production.
analyzer.process(p);
// Patch the production's type.
if ((null != element) && ! element.isError()) {
p.type = AST.listOf(ast.concretize(element, AST.NULL_NODE));
}
}
}
}
/** Visit the specified full production. */
public void visit(FullProduction p) {
dispatch(p.choice);
}
/** Visit the specified choice. */
public void visit(OrderedChoice c) {
for (Sequence alt : c.alternatives) dispatch(alt);
}
/** Visit the specified sequence. */
public void visit(Sequence s) {
// Remember the current number of elements.
final int base = elements.size();
// Process the elements of the sequence.
for (Iterator<Element> iter = s.elements.iterator(); iter.hasNext(); ) {
Element e = iter.next();
if ((! iter.hasNext()) && (e instanceof OrderedChoice)) {
// Continue with the trailing choice.
dispatch(e);
} else {
// Add the current element to the list of traversed elements.
elements.add(e);
}
}
// If we have no trailing choice and the elements do not set the
// semantic value, we need to add a semantic value.
if ((! s.hasTrailingChoice()) && (! Analyzer.setsValue(elements, false))) {
List<Binding> bindings = new ArrayList<Binding>();
// Iterate over the elements and collect all bindable elements.
for (int i=0; i<elements.size(); i++) {
Element e = elements.get(i);
if (e instanceof Binding) {
bindings.add((Binding)e);
} else if (analyzer.isBindable(e)) {
Binding b = new Binding(analyzer.variable(MARKER), e);
elements.set(i, b);
bindings.add(b);
}
}
// Update the element type.
if ((null != element) && ! element.isError()) {
for (Binding b : bindings) {
Type t = analyzer.type(b.element);
if (AST.isList(t)) t = AST.getArgument(t);
Type u = ast.unify(element, t, true);
if (u.isError()) {
runtime.error("unable to determine consistent list element type", s);
runtime.errConsole().loc(s).p(": error: 1st type is '");
ast.print(element, runtime.errConsole(), false, true, null);
runtime.errConsole().pln("'");
runtime.errConsole().loc(s).p(": error: 2nd type is '");
ast.print(t, runtime.errConsole(), false, true, null);
runtime.errConsole().pln("'").flush();
}
element = u;
}
}
// Ensure the alternative has a semantic value.
if (bindings.isEmpty()) {
// An empty list value.
s.add(EmptyListValue.VALUE);
} else if ((1 == bindings.size()) &&
AST.isList(analyzer.type(bindings.get(0).element))) {
// The only binding already has a list value. Pass it through.
Binding b = bindings.get(0);
if (Analyzer.isSynthetic(b.name)) {
// Rename the binding.
b.name = CodeGenerator.VALUE;
} else {
// Preserve the user-specified variable name. Note that the
// bindings name cannot be yyValue due to the
// Analyzer.setValue() test above.
s.add(new BindingValue(b));
}
} else {
// Check whether the last binding has a list value.
Binding last = bindings.get(bindings.size()-1);
if (AST.isList(analyzer.type(last.element))) {
bindings.remove(bindings.size()-1);
s.add(new ProperListValue(analyzer.current().type, bindings, last));
} else {
s.add(new ProperListValue(analyzer.current().type, bindings, null));
}
}
}
// Patch back any added binding.
int size = s.size();
if (s.hasTrailingChoice() ||
((0 != size) && (s.get(size-1) instanceof ValueElement))) {
// Ignore trailing choices and value elements.
size--;
}
for (int i=0; i<size; i++) {
Element e = elements.get(base + i);
if (s.get(i) != e) s.elements.set(i, e);
}
// Remove any elements added by processing the sequence.
if (0 == base) {
elements.clear();
} else {
elements.subList(base, elements.size()).clear();
}
}
}