/* * xtc - The eXTensible Compiler * Copyright (C) 2007 New York University * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. */ package xtc.typical; import java.util.ArrayList; import java.util.Hashtable; import xtc.tree.Node; import xtc.util.Runtime; import xtc.util.SymbolTable; import xtc.util.Pair; import xtc.util.Function; /** * The base class of all Typical-generated type checkers. * * @author Laune Harris, Anh Le * @version $Revision: 1.145 $ */ public abstract class Analyzer { /** The runtime. */ protected final Runtime runtime; /** The symbol table. */ protected final SymbolTable gamma; /** The property to check if enter a scope. */ protected final String ENTERSCOPE = "enterScope" ; /** The property of check if exit a scope. */ protected final String EXITSCOPE = "exitScope"; /** * The property to store the times of not entering a new scope because * the new scope is also the current scope. */ protected final String MAGICNUMBER = "magicNumber"; /** The hash table */ protected Hashtable<Object, Object> hashTable; /** The analyzer. */ protected Function.F1<?, Node> analyzer; /** The tree root. */ protected Node root; /** The visited node path.*/ protected final ArrayList<Node> matching_nodes = new ArrayList<Node>(); /** The list of names of the nodes that trigger scope changes. */ protected final ArrayList<String> processScopeNodes = new ArrayList<String>(); /** Interface for pattern matches. */ public static interface Match<T> extends Function.F0<T> { /*empty*/ } /** Interface for ancestor Matches. */ public static interface NodeMatch extends Function.F1<Boolean, Node> { /* empty */ } /** Interface for require expressions. */ public static interface Require<T> extends Function.F0<T> { /*empty*/ } /** Interface for let expressions. */ public static interface Let<T> extends Function.F0<T> { /*empty*/ } /** Interface for guard expressions. */ public static interface Guard<T> extends Function.F0<T> { /*empty*/ } /** Get the names of the nodes that trigger scope changes. */ protected abstract void getScopeNodes(); /** The Typical load function to load predefined values and data types. */ protected final Function.F3<Void,String,String,Object> load = new Function.F3<Void,String,String,Object>() { public Void apply(String s, String ns, Object t) { if (null == s || null == ns) return null; gamma.current().define(SymbolTable.toNameSpace(s, ns), t); return null; } }; /** The ancestor function.*/ protected final Function.F1<Node, NodeMatch> ancestor = new Function.F1<Node,NodeMatch>() { public final Node apply(NodeMatch pattern) { for (int i = matching_nodes.size() - 1; i >=0; i--) { final Node node = matching_nodes.get(i); if (pattern.apply(node)) return node; } return null; } }; /** The parent function.*/ protected final Function.F1<Node, NodeMatch> parent = new Function.F1<Node,NodeMatch>() { public final Node apply(NodeMatch pattern) { final Node node = matching_nodes.get(matching_nodes.size() -1); if (pattern.equals(node)) return node; return null; } }; /** A Typical function to lookup a node, without error messages. */ protected final Function.F2<Object, Node, Function.F1<?, Node>> lookup2 = new Function.F2<Object, Node, Function.F1<?, Node>>() { public final Object apply(Node n, Function.F1<?, Node> getNameSpace) { Tuple.T3<Name<?>,String,String> tup = cast(getNameSpace.apply(n)); assert (null != tup) : "Unable to map namespace for node " + (null == n ? "Null" : n.getName()); checkEnterScope(n); final Object res = gamma.lookup(tup.get1().mangle(tup.get2())); if (null == res) showMessage("error", "Undefined: " + tup.get1().mangle(tup.get2()), n); checkExitScope(n); return res; } }; /** A Typical function to lookup a node, with an error message. */ protected final Function.F4<Object, Node, String, String, Function.F1<?, Node>> lookup4 = new Function.F4<Object, Node, String, String, Function.F1<?, Node>>() { public final Object apply(Node n, String tag, String err, Function.F1<?, Node> getNameSpace) { Tuple.T3<Name<?>,String,String> tup = cast(getNameSpace.apply(n)); assert (null != tup) : "Unable to map namespace for node " + (null == n ? "Null" : n.getName()); checkEnterScope(n); final Object res = gamma.lookup(tup.get1().mangle(tup.get2())); if (null == res) showMessage(tag, err, n); checkExitScope(n); return res; } }; /** A Typical function to lookup a node locally, without error messages. */ protected final Function.F2<Object, Node, Function.F1<?, Node>> lookupLocally2 = new Function.F2<Object, Node, Function.F1<?, Node>>() { public final Object apply(Node n, Function.F1<?, Node> getNameSpace) { Tuple.T3<Name<?>,String,String> tup = cast(getNameSpace.apply(n)); assert (null != tup) : "Unable to map namespace for node " + (null == n ? "Null" : n.getName()); checkEnterScope(n); final Object res = gamma.lookup(tup.get1().mangle(tup.get2())); if (null == res) showMessage("error", "Undefined in the current scope: " + tup.get1().mangle(tup.get2()), n); checkExitScope(n); return res; } }; /** A Typical function to lookup a node locally, with an error message. */ protected final Function.F4<Object, Node, String, String, Function.F1<?, Node>> lookupLocally4 = new Function.F4<Object, Node, String, String, Function.F1<?, Node>>() { public final Object apply(Node n, String tag, String err, Function.F1<?, Node> getNameSpace) { Tuple.T3<Name<?>,String,String> tup = cast(getNameSpace.apply(n)); assert (null != tup) : "Unable to map namespace for node " + (null == n ? "Null" : n.getName()); checkEnterScope(n); final Object res = gamma.lookup(tup.get1().mangle(tup.get2())); if (null == res) showMessage(tag,err,n); checkExitScope(n); return res; } }; /** A Typical function to define a node, without error messages. */ protected final Function.F3<Void, Node, Object, Function.F1<?, Node>> define3 = new Function.F3<Void, Node, Object, Function.F1<?, Node>>() { public final Void apply(Node n, Object t, Function.F1<?, Node> getNameSpace) { Tuple.T3<Name<?>,String,String> tup = cast(getNameSpace.apply(n)); assert (null != tup) : "define : no namespace for " + (null == n ? "Null" : n.getName()); checkEnterScope(n); gamma.current().define(tup.get1().mangle(tup.get2()), t); checkExitScope(n); return null; } }; /** A Typical function to define a node, with an error message. */ protected final Function.F5<Void, Node, Object, String, String, Function.F1<?, Node>> define5 = new Function.F5<Void, Node, Object, String, String, Function.F1<?, Node>>() { public final Void apply(Node n, Object t, String tag, String err, Function.F1<?, Node> getNameSpace) { Tuple.T3<Name<?>,String,String> tup = cast(getNameSpace.apply(n)); assert (null != tup) : "define : no namespace for " + (null == n ? "Null" : n.getName()); checkEnterScope(n); final String newName = tup.get1().mangle(tup.get2()); if (gamma.current().isDefined(newName)) { showMessage(tag,err,n); } gamma.current().define(newName, t); checkExitScope(n); return null; } }; /** A Typical function to redefine a node. */ protected final Function.F3<Void, Node, Object, Function.F1<?, Node>> redefine = new Function.F3<Void, Node, Object, Function.F1<?, Node>>() { public final Void apply(Node n, Object t, Function.F1<?, Node> getNameSpace) { Tuple.T3<Name<?>,String,String> tup = cast(getNameSpace.apply(n)); assert (null != tup) : "redefine : no namespace for " + (null == n ? "Null" : n.getName()); checkEnterScope(n); String newName = tup.get1().mangle(tup.get2()); gamma.current().define(newName, t); checkExitScope(n); return null; } }; /** A typical function for checking if a node has been defined */ protected final Function.F2<Boolean, Node, Function.F1<?,Node>> isDefined = new Function.F2<Boolean, Node, Function.F1<?,Node>>() { public final Boolean apply(Node n, Function.F1<?,Node> getNameSpace) { Tuple.T3<Name<?>,String,String> tup = cast(getNameSpace.apply(n)); assert (null != tup) : "is_define : no namespace for " + (null == n ? "Null" : n.getName()); checkEnterScope(n); final boolean res = gamma.isDefined(tup.get1().mangle(tup.get2())); checkExitScope(n); return res; } }; /** A typical function for checking if a node has been locally defined */ protected final Function.F2<Boolean, Node, Function.F1<?,Node>> isDefinedLocally = new Function.F2<Boolean, Node, Function.F1<?,Node>>() { public final Boolean apply(Node n, Function.F1<?,Node> getNameSpace) { Tuple.T3<Name<?>,String,String> tup = cast(getNameSpace.apply(n)); assert (null != tup) : "is_define_locally : no namespace for " + (null == n ? "Null" : n.getName()); checkEnterScope(n); boolean res = gamma.current().isDefinedLocally(tup.get1().mangle(tup.get2())); checkExitScope(n); return res; } }; /** The Typical function to print a symbol. */ protected final Function.F1<Boolean, String> show_symbols = new Function.F1<Boolean, String>() { public Boolean apply(String s) { if ("local".equals(s)) { gamma.current().dump(runtime.console()); runtime.console().flush(); } else if ("all".equals(s)) { gamma.root().dump(runtime.console()); runtime.console().flush(); } else { runtime.error("local or all required to use show_symbol function"); } return Boolean.TRUE; } }; /** The Typical function to get a new name. */ protected final Function.F1<String, String> freshName = new Function.F1<String, String>() { public String apply(String s){ return gamma.freshName(s); } }; /** * The Typical function to check if a value if bottom. * Return <code>true</code> if not bottom, otherwise bottom * (It will be removed once guard is done) */ protected final Function.F1<Boolean, Object> notBottom = new Function.F1<Boolean, Object>() { public Boolean apply(Object t) { return null == t ? null : Boolean.TRUE; } }; /** * Create a new TypeChecker base. * * @param runt The runtime. */ public Analyzer(Runtime runt) { runtime = runt; gamma = new SymbolTable(); hashTable = new Hashtable<Object, Object>(); getScopeNodes(); } /** * Check a node and enter a scope. * * @param n The node to check. */ protected void checkEnterScope(Node n) { if (null != n && n.hasProperty(ENTERSCOPE)) { final String scopeName = (String)n.getProperty(ENTERSCOPE); if (!scopeName.equals(gamma.current().getName())){ gamma.enter(scopeName); } else { // magicNumber is the number of times not entering a new scope // at this node because the new scope is the current scope if (!n.hasProperty(MAGICNUMBER)) { n.setProperty(MAGICNUMBER, 1); } else { final Integer num = (Integer)n.getProperty(MAGICNUMBER); n.setProperty(MAGICNUMBER, num + 1); } } } } /** * Check a node and exit scope. * * @param n The node to check. */ protected void checkExitScope(Node n){ if (null != n && n.hasProperty(EXITSCOPE)) { if (!n.hasProperty(MAGICNUMBER)) gamma.exit(); else { final Integer num = (Integer)n.getProperty(MAGICNUMBER); if (1 == num) n.removeProperty(MAGICNUMBER); else n.setProperty(MAGICNUMBER, num - 1); } } if (null != n && n.hasProperty("deleteScope")) { gamma.delete((String)n.getProperty("deleteScope")); } } /** * Print a error message. * * @param s The msg. * @param n The location. * @return null */ protected Object error(String s, Node n) { if (null == n) { n = matching_nodes.get(matching_nodes.size() -1); } runtime.error(s, n); return null; } /** * Print a warning message. * * @param s The msg. * @param n The location. * @return null */ protected Object warning(String s, Node n) { if (null == n) { n = matching_nodes.get(matching_nodes.size() -1); } runtime.warning(s, n); return null; } /** * Print a error message. * * @param tag The message class. * @param msg The message. * @param o The node generating the message. */ protected void showMessage(String tag, String msg, Object o){ Node n = (Node)o; if (null == n) { if ("error".equals(tag)) { if (matching_nodes.size() - 1 < 0) { runtime.error(msg); } else { Node nod = matching_nodes.get(matching_nodes.size() - 1); runtime.error(msg,nod); } } else { if (matching_nodes.size() -1 < 0) { runtime.warning(msg); } else { Node nod = matching_nodes.get(matching_nodes.size() - 1); runtime.warning(msg,nod); } } } else { if ("error".equals(tag)) { runtime.error(msg,n); } else { runtime.warning(msg,n); } } } /** * Process scope. * * @param n The node to process. * @param getScope The generated getScope function. */ protected void processScope(Node n, Function.F1<?, Node> getScope) { // the function getScope(Node n) will be created // after visiting the scope declaration Scope scop = cast(getScope.apply(n)); if (null == scop) { throw new AssertionError("unable to get scope for : " + n.getName()); } // get the scope name ScopeKind<?> scopename = scop.getTuple().get1(); String str; if (scopename.isNamed()) { // Cast to object first to avoid inconvertible types error with Java 5. Name<?> strname = ((ScopeKind.Named)(Object)scopename).getTuple().get1(); if (strname.isSimpleName()) { // Cast to object first to avoid inconvertible types error with Java 5. str = ((Name.SimpleName)(Object)strname).getTuple().get1(); } else { str = "QualifiedName"; } } else if (scopename.isAnonymous()) { // Cast to object first to avoid inconvertible types error with Java 5. str = gamma. freshName(((ScopeKind.Anonymous)(Object)scopename).getTuple().get1()); } else { // Cast to object first to avoid inconvertible types error with Java 5. str = gamma. freshName(((ScopeKind.Temporary)(Object)scopename).getTuple().get1()); } Pair<Node> nodes = scop.getTuple().get2(); ArrayList<Node> nodeList = (ArrayList<Node>)nodes.list(); int index; int prob = -1; // check and remove set scope property in those nodes for (index = 0; index < nodeList.size(); index++) { Node node = nodeList.get(index); if ( node == null) { continue; } if (!node.hasProperty(ENTERSCOPE)) { node.setProperty(ENTERSCOPE,str); node.setProperty(EXITSCOPE,true); prob = index; } } if ((prob!=-1) && (scopename.isTemporary())) { Node node = nodeList.get(prob); node.setProperty("deleteScope",str); } } /** * Run this typechecker on the tree rooted at n. * * @param n The tree root. * @return The Symbol table of the ast. */ public SymbolTable run(Node n) { if (null == analyzer) { runtime.error("Analyzer is null"); runtime.exit(); } if (null == n) { runtime.error("Tree root is null"); runtime.exit(); } else { root = n; } matching_nodes.add(n); analyzer.apply(n); return gamma; } /** * Returns the (possibly annotated and modified) AST root. * * @return The root of the ast. */ public Node getASTRoot() { return root; } /** * Utility function to print a nicely formatted ast for debugging. * * @param n The ast root. */ protected void printAST(Node n){ runtime.console().pln().format(n).pln().flush(); } /** * Convert the specified object to a string. * * @param o The object. * @return The corresponding string. */ public static final String toString(Object o) { return null == o ? "?" : o.toString(); } /** * Test for equality between two objects. * * @param o1 The first object. * @param o2 The second object. * @return <code>true</code> if both are equal, false otherwise. */ public static Boolean equal(Object o1, Object o2) { return null == o1 ? null == o2 : o1.equals(o2); } /** * Test for equality between two objects. * * @param o1 The first object. * @param o2 The second object. * @return <code>true</code> if the o1 and o2 not equal, false otherwise. */ protected static Boolean not_equal(Object o1, Object o2) { return null == o1 ? null != o2 : (! o1.equals(o2)); } /** * Ignore the specified value. * * @param o The value to ignore. */ protected static final void discard(Object o) { // Nothing to do. } /** * Cast an object to type T. * * @param arg The object to cast. * @return The object cast to T */ @SuppressWarnings("unchecked") public static final <T> T cast(Object arg) { return (T)arg; } }