/**
* Copyright 2015 Santhosh Kumar Tekuri
*
* The JLibs authors license this file to you 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.
*/
package jlibs.nblr.codegen;
import jlibs.core.annotation.processing.Printer;
import jlibs.core.lang.ArrayUtil;
import jlibs.core.lang.ImpossibleException;
import jlibs.core.lang.StringUtil;
import jlibs.core.util.Range;
import jlibs.nblr.codegen.java.SyntaxClass;
import jlibs.nblr.matchers.Matcher;
import jlibs.nblr.rules.Node;
import jlibs.nblr.rules.Path;
import jlibs.nblr.rules.Routes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import static jlibs.core.annotation.processing.Printer.MINUS;
import static jlibs.core.annotation.processing.Printer.PLUS;
/**
* @author Santhosh Kumar T
*/
public class State{
public final RuleMethod ruleMethod;
public Node fromNode;
public final List<Decision> decisions = new ArrayList<Decision>();
public final RootIf rootIF = new RootIf(this);
public final List<IfBlock> ifBlocks = rootIF.children;
public State(RuleMethod ruleMethod, Node fromNode){
this.ruleMethod = ruleMethod;
this.fromNode = fromNode;
Routes routes = new Routes(ruleMethod.rule, fromNode, true);
if(routes.toString().endsWith("[)]<EOF>"))
System.out.print("");
for(int lookAhead: routes.lookAheads())
processLookAhead(routes.determinateRoutes(lookAhead));
// move loop without fallback to the beginning
for(Decision decision: decisions){
if(decision.usesFinishAll()){
ruleMethod.syntaxClass.addToFinishAll(decision.matchers[0]);
decisions.remove(decision);
decisions.add(0, decision);
break;
}
}
if(routes.indeterminateRoute!=null)
decisions.add(new Decision(this, routes.indeterminateRoute.route()[0]));
if(routes.routeStartingWithEOF!=null)
decisions.add(new Decision(this, routes.routeStartingWithEOF));
optimize();
populateIfBlocks();
}
private void processLookAhead(List<Path> routes){
processLookAhead(routes, 1);
}
private void processLookAhead(List<Path> routes, int depth){
List<List<Path>> groups = new ArrayList<List<Path>>();
Matcher matcher = null;
for(Path route: routes){
Path path = route.route()[depth-1];
Matcher curMatcher = path.matcher();
if(curMatcher==null)
curMatcher = eofMatcher;
if(matcher==null || !curMatcher.same(matcher)){
groups.add(new ArrayList<Path>());
matcher = curMatcher;
}
groups.get(groups.size()-1).add(route);
}
for(List<Path> group: groups){
Path route = group.get(0);
if(depth<routes.get(0).depth)
processLookAhead(group, depth+1);
if(depth==route.depth){
Decision decision = new Decision(this, route);
decisions.add(decision);
}
}
}
private boolean isCandidate(Decision decision){
int i = decisions.indexOf(decision)+1;
while(i<decisions.size()){
Decision d = decisions.get(i);
if(decision.matchers.length!=d.matchers.length)
return true;
if(!d.path.equals(decision.path)){
if(ArrayUtil.getLast(d.matchers)==null)
return false;
if(ArrayUtil.getLast(decision.matchers).clashesWith(ArrayUtil.getLast(d.matchers)))
return false;
}
i++;
}
return true;
}
private void optimize(){
List<List<Decision>> lists = new ArrayList<List<Decision>>();
for(int i=decisions.size()-1; i>=0; i--){
Decision decision = decisions.get(i);
if(decision.path.matcher()==null && isCandidate(decision)){
boolean listFound = false;
for(List<Decision> list: lists){
if(decision.path.equals(list.get(0).path)){
listFound = true;
list.add(decision);
break;
}
}
if(!listFound){
List<Decision> newList = new ArrayList<Decision>();
newList.add(decision);
lists.add(newList);
}
}
}
if(lists.size()==0)
return;
if(decisions.get(decisions.size()-1).matchers[0]==null){
Decision lastDecision = decisions.get(decisions.size()-1);
for(List<Decision> list: lists){
if(list.get(0).path.equals(lastDecision.path)){
lists.clear();
lists.add(list);
break;
}
}
}
List<Decision> preferredList = lists.get(0);
for(int i=1; i<lists.size(); i++){
List<Decision> list = lists.get(i);
if(list.size()>preferredList.size())
preferredList = list;
}
Decision preferredDecision = preferredList.get(0);
if(preferredList.size()==1 && preferredDecision.matchers.length<maxLookAhead())
return;
decisions.removeAll(preferredList);
decisions.add(preferredDecision);
preferredDecision.matchers = new Matcher[]{ null };
}
public void computeNextStates(ArrayList<Node> statesVisited, LinkedHashSet<Node> statesPending){
for(Decision decision: decisions)
decision.computeNextStates(statesVisited, statesPending);
}
private void populateIfBlocks(){
List<List<IfBlock>> lists = new ArrayList<List<IfBlock>>();
int lastLen = 0;
for(Decision decision: decisions){
IfBlock curIf = null;
if(lastLen!=decision.matchers.length){
lists.add(new ArrayList<IfBlock>());
lastLen = decision.matchers.length;
}
for(Matcher matcher: decision.matchers){
List<IfBlock> children = curIf==null ? lists.get(lists.size()-1) : curIf.children;
IfBlock found = null;
if(matcher!=null){
for(IfBlock child: children){
if(matcher.same(child.matcher)){
found = child;
break;
}
}
}
if(found==null){
found = new IfBlock(this);
found.matcher = matcher;
children.add(found);
found.parent = curIf;
}
curIf = found;
}
curIf.path = decision.path;
}
for(List<IfBlock> list: lists)
rootIF.children.addAll(list);
rootIF.computeCommon();
}
public boolean readCodePoint(){
for(Decision decision: decisions){
if(decision.readCodePoint())
return true;
}
return false;
}
public boolean readCharacter(){
for(Decision decision: decisions){
if(!decision.readCharacter())
return false;
}
return true;
}
public boolean matchesNewLine(){
for(Decision decision: decisions){
if(decision.matchesNewLine())
return true;
}
return false;
}
public String expected(){
StringBuilder builder = new StringBuilder();
for(Decision decision: decisions){
if(builder.length()>0)
builder.append(" OR ");
builder.append(decision.expected());
}
return builder.toString();
}
public boolean requiresContinue(State nextState){
for(Decision decision: decisions){
if(decision.requiresContinue(nextState))
return true;
}
return false;
}
public int maxLookAhead(){
int maxLookAhead = 0;
for(Decision decision: decisions)
maxLookAhead = Math.max(maxLookAhead, decision.matchers.length);
return maxLookAhead;
}
public boolean lookAheadRequired(){
return maxLookAhead()>1;
}
public String readMethod(){
if(!lookAheadRequired() && readCharacter() && !matchesNewLine())
return "position==limit ? marker : input[position]";
else
return "codePoint()";
}
public String breakStatement(){
return ruleMethod.requiresWhile() ? "break loop;" : "break;";
}
public void generate(Printer printer, State nextState){
printer.printlns(
"case "+fromNode.stateID+":",
PLUS
);
rootIF.generate(printer, nextState);
printer.printlns(
MINUS
);
}
public void generate1(Printer printer, State nextState){
printer.printlns(
"case "+fromNode.stateID+":",
PLUS
);
if(readCodePoint() && (!decisions.get(0).usesFinishAll() || lookAheadRequired())){
printer.printlns(
"if((ch="+readMethod()+")==EOC)"+(SyntaxClass.DEBUGGABLE ? "{" : ""),
PLUS
// "exiting(RULE_"+ruleMethod.rule.name.toUpperCase()+", "+fromNode.stateID+");"
);
if(SyntaxClass.DEBUGGABLE)
printer.println("handler.currentNode("+ruleMethod.rule.id+", "+fromNode.stateID+");");
printer.println(breakStatement());
printer.printlns(
// "return false;",
MINUS,
SyntaxClass.DEBUGGABLE ? "}" : null
);
}
boolean lookAheadReqd = lookAheadRequired();
int lastLookAhead = 0;
int elseAfterDecision = 1;
int lastDecisionAction = Decision.ADD_CONTINUE;
boolean closeLALengthCheck = false;
for(int i=0; i<decisions.size(); i++){
Decision decision = decisions.get(i);
int curLookAhead = decision.matchers.length;
if(curLookAhead>lastLookAhead){
elseAfterDecision = 1;
lastDecisionAction = Decision.ADD_CONTINUE;
if(decision.usesFinishAll())
elseAfterDecision = 2;
if(lookAheadReqd){
if(curLookAhead>1){
if(lastLookAhead<=1){
printer.println("addToLookAhead(ch);");
lastLookAhead = 1;
}
String prefix, condition;
if(curLookAhead==lastLookAhead+1){
prefix = "if";
condition = "ch!=EOF";
}else{
prefix = "while";
condition = "ch!=EOF && laLen<"+curLookAhead;
}
printer.printlns(
prefix+"("+condition+"){",
PLUS,
"if((ch=codePoint())==EOC)",
PLUS,
breakStatement(),
MINUS,
"addToLookAhead(ch);",
MINUS,
"}"
);
}
closeLALengthCheck = true;
if(curLookAhead>1){
printer.printlns(
"if(laLen=="+curLookAhead+"){",
PLUS
);
}
}
}
boolean closeBlock = false;
if(!lookAheadReqd && (elseAfterDecision<=0 || lastDecisionAction==Decision.GOTO_NEXT_CASE || lastDecisionAction==Decision.CALL_RULE_AND_NEXT_DECISION)){
printer.print("else ");
if(decision.matchers[0]==null){
closeBlock = true;
printer.printlns(
"{",
PLUS
);
}
}
if(decision.usesFinishAll())
decision.generate(printer, nextState);
else{
Decision prevDecision = i==0 ? null : decisions.get(i-1);
int common = common(prevDecision, decision);
for(int j=common; j<decision.matchers.length; j++)
decision.startMatcher(printer, j);
decision.addBody(printer, nextState);
Decision nextDecision = i==decisions.size()-1 ? null : decisions.get(i+1);
common = common(decision, nextDecision);
for(int j=common; j<decision.matchers.length; j++)
decision.endMatcher(printer, j);
}
if(closeBlock){
printer.printlns(
MINUS,
"}"
);
}
if(lookAheadReqd && closeLALengthCheck && (i+1==decisions.size() || decisions.get(i+1).matchers.length!=curLookAhead)){
closeLALengthCheck = false;
if(curLookAhead>1){
printer.printlns(
MINUS,
"}"
);
}
}
lastLookAhead = curLookAhead;
elseAfterDecision--;
lastDecisionAction = decision.returnAction(nextState);
}
Decision lastDecision = decisions.get(decisions.size()-1);
if(lastDecision.matchers[0]!=null){
if(!lookAheadReqd)
printer.print("else ");
printer.println("expected(ch, \""+ StringUtil.toLiteral(expected(), false)+"\");");
}
printer.printlns(
MINUS
);
}
private int common(Decision decision1, Decision decision2){
if(decision1==null || decision2==null)
return 0;
if(decision1.matchers.length!=decision2.matchers.length)
return 0;
if(decision1.usesFinishAll())
return 0;
for(int i=0; i<decision1.matchers.length; i++){
Matcher matcher1 = decision1.matchers[i];
Matcher matcher2 = decision2.matchers[i];
if(matcher1==null || matcher2==null)
return i;
if(!matcher1.same(matcher2))
return i;
}
throw new ImpossibleException();
}
protected static Matcher eofMatcher = new Matcher(){
@Override
public String toString(){ throw new UnsupportedOperationException(); }
@Override
protected String __javaCode(String variable){ throw new UnsupportedOperationException(); }
@Override
public List<Range> ranges(){
return Collections.emptyList();
}
};
}