package mumbler.truffle.parser;
import static mumbler.truffle.parser.MumblerReadException.throwReaderException;
import java.util.HashMap;
import java.util.Map;
import mumbler.truffle.syntax.ListSyntax;
import mumbler.truffle.syntax.SymbolSyntax;
import mumbler.truffle.type.MumblerList;
import mumbler.truffle.type.MumblerSymbol;
import com.oracle.truffle.api.frame.FrameDescriptor;
/**
* This class walks through the syntax objects to define all the namespaces
* and the identifiers within. Special forms are also verified to be structured
* correctly.
* <p>
* After the scan, a lambda's namespace can be fetched to find symbols'
* appropriate {@link FrameDescriptor}.
* <p>
* The following special form properties are checked
* <ol>
* <li> <code>define</code> calls only contain 2 arguments.
* <li> The first argument to <code>define</code> is a symbol.
* <li> <code>lambda</code> calls have at least 3 arguments.
* <li> The first argument to <code>lambda</code> must be a list of symbols.
* <li> <code>if</calls> have exactly 3 arguments.
* <li> <code>quote</code> quote calls have exactly 1 argument.
* </ol>
*/
public class Analyzer extends SexpListener {
private final Map<ListSyntax, Namespace> namespaces;
private Namespace currentNamespace;
public Analyzer(Namespace topNamespace) {
this.namespaces = new HashMap<>();
this.namespaces.put(null, topNamespace);
this.currentNamespace = topNamespace;
}
public Map<ListSyntax, Namespace> getNamespaceMap() {
return this.namespaces;
}
public Namespace getNamespace(ListSyntax syntax) {
return this.namespaces.get(syntax);
}
@Override
public void onDefine(ListSyntax syntax) {
MumblerList<? extends Syntax<?>> list = syntax.getValue();
if (list.size() != 3) {
throwReaderException("define takes 2 arguments", syntax,
this.currentNamespace);
}
if (!(list.cdr().car() instanceof SymbolSyntax)) {
throwReaderException("define first argument must be a symbol",
syntax, this.currentNamespace);
}
MumblerSymbol sym = (MumblerSymbol) list.cdr().car().getValue();
this.currentNamespace.addIdentifier(sym.name);
}
@Override
public void onLambda(ListSyntax syntax) {
MumblerList<? extends Syntax<?>> list = syntax.getValue();
if (list.size() < 3) {
throwReaderException("lambda takes at least 3 arguments",
syntax, this.currentNamespace);
}
if (!(list.cdr().car() instanceof ListSyntax)) {
throwReaderException("lambda second argument must be a list",
syntax, this.currentNamespace);;
}
this.currentNamespace = new Namespace(syntax.getName(), this.currentNamespace);
this.namespaces.put(syntax, this.currentNamespace);
this.addLambdaArguments((ListSyntax) list.cdr().car());
}
@Override
public void onLambdaExit(ListSyntax syntax) {
this.currentNamespace = this.currentNamespace.getParent();
}
@Override
public void onIf(ListSyntax syntax) {
if (syntax.getValue().size() != 4) {
throwReaderException("if must have 3 arguments: test, then & else",
syntax, this.currentNamespace);
}
}
@Override
public void onQuote(ListSyntax syntax) {
if (syntax.getValue().size() != 2) {
throwReaderException("quote has only one argument", syntax,
this.currentNamespace);
}
}
private void addLambdaArguments(ListSyntax argsListSyntax) {
for (Syntax<?> syntax: argsListSyntax.getValue()) {
if (syntax instanceof SymbolSyntax) {
this.currentNamespace.addIdentifier(
((SymbolSyntax) syntax).getValue().name);
} else {
throwReaderException("lambda argument must be a symbol",
argsListSyntax, this.currentNamespace);
}
}
}
}