java/ch/wlkl/wsh/Parser.java

package ch.wlkl.wsh;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Parser extends Top {
    public static final Pattern eol = Pattern.compile("(\r\n)|\r|\n");
    public static final Pattern space = Pattern.compile("\\s+");
    public static final Pattern notSpace = Pattern.compile("\\S+");
    public static final Pattern number = Pattern.compile("[+-]?((\\d+(\\.\\d*)?)|(\\.\\d+))");
    public static final Pattern shName = Pattern.compile("[\\w&&[^\\d]][\\w]*");
    public static final Pattern string = Pattern.compile("\"([^\"]|\"\")*\"|'([^']|'')*'");
    public static final Pattern notDo = Pattern.compile("[^$]+");
    public static final Pattern shWord = Pattern.compile("[^${}=?:\\s]+");

    Read<String> in = null;
    boolean atEnd = true;
    int inIx, lineBeg, lineEnd, charIx;
    public String src, tok, key, val;
    String lines;
    Matcher match = space.matcher("");

    public Parser(Read<String> i) {
        reset(i);
    }

    public Parser(String i) {
        reset(i);
    }

    void reset () {
        in = null;
        lines = "";
        src = "";
        inIx =  charIx = -1;
        lineBeg = lineEnd = 0;
        atEnd = false;        
        tok = key = val = null;
    }
    
    public void reset(Read<String> i) {
        reset();
        in = i;
        in.open("r");
        switchLine();
    }

    public void reset(String ss) {
        reset();
        lines = ss;
        switchLine();
    }

    public boolean switchLine() {
        if (atEnd)
            return false;
        tok = null;
        if (lineEnd >= lines.length()) {
            if (in == null || null == (lines = in.read())) {
                atEnd = true;
                charIx = src.length();
                return false;
            } else
            inIx++;
            lineEnd = 0;
        } 
        match.usePattern(eol).reset(lines);
        if (match.find(lineBeg = lineEnd)) {
            src = lines.substring(lineBeg, match.start());
            lineEnd = match.end();
        } else {
            src = lines.substring(lineBeg);
            lineEnd = lines.length();
        }
        charIx = 0;
        match.reset(src);
        return true;
    }
    
    public boolean atEnd() {
        return atEnd; 
    }

    public boolean atEol() {
        return charIx >= src.length() || src.charAt(charIx) == '\n' || src.charAt(charIx) == '\r'; 
    }

    public boolean nl() {
        return atEol() ? switchLine() : false; 
    }

    public boolean next(Pattern pat) {
        match.region(charIx, src.length());
        match.usePattern(pat);
        if (match.lookingAt()) {
            tok = match.group();
            charIx = match.end();
            sTrc(null, "next(" + pat + ") found " + tok);
            return true;
        } else {
            tok = null;
            return false;
        }
    }

    public String look() {
        return src.substring(charIx);
    }

    public String look(int len) {
        return src.substring(charIx, charIx + len > src.length() ? src.length() : charIx + len);
    }

    public boolean lit(String lit) {
        if (charIx + lit.length() > src.length() || ! lit.equals(src.substring(charIx, charIx +lit.length()))) {
            tok = null;
            return false;
        } else {
            tok = lit;
            charIx += lit.length();
            return true;
        }
    }
    
    public String chars(String chars) {
        String res = "";
        for (int cx=0; cx<chars.length();cx++) {
            if (lit(chars.substring(cx, cx+1)))
                res += chars.charAt(cx);                    
        }
        return res;
    }
    

    public boolean comment() {
        int depth = 0;
        boolean found = false;
        while (true) {
            if (lit("$**")) {
                charIx = src.length(); // move to end of line, just before nl
            } else if (lit("$*+")) {
                switchLine(); // move to next line
            } else if (lit("$*(")) {
                depth++;
            } else if (depth == 0){
                return found;
            } else if (lit("$*)")) {
                depth--;
            } else if (lit("$")) {
                next(string); // skip over $ and possibly string
            } else if (next(notDo) || nl()) {
            } else {
                fail("source end (depth " + depth + ") in comment");
            }
            found = true;
        }
    }
    
    public boolean spaceComment() {
        boolean found = false;
        while (next(space) || comment())
            found = true;
        return found;
    }
    
    public boolean spaceNlComment() {
        boolean found = false;
        while (next(space) || nl() || comment())
            found = true;
        return found;
    }

    public boolean keyValue() {
        spaceComment();
        if ( ! next(notSpace))
            return false;
        if (tok.startsWith("-")) {
            key = tok.length() > 1 ? tok.substring(1,2) : "";
            val = tok.length() > 2 ? tok.substring(2) : "";
        } else {
            key = null;
            val = tok;
        }
        return true;
    }
    
    public void fail(String msg) {
        super.fail(msg + "\ntok " + tok + " charIx " + (lineBeg + charIx) + ": " + src.substring(charIx) +  "\nin line " + inIx + ": " + lines);
    }
    
    public String loc () {
        return String.valueOf(inIx) + "." + '.'+ (lineBeg + charIx);
    }

    public static int matchesEnd(CharSequence src, Pattern pat) {
        Matcher match = pat.matcher(src);
        return match.lookingAt() ? match.end() : 0;
    }
    
    public static int findEndStart(CharSequence src, Pattern pat) {
        Matcher match = pat.matcher(src);
        while (match.find()) {
            if (match.hitEnd())
                return match.start();
        }
        return src.length();
    }
    
    public static String skipScan(String src, Pattern ski, Pattern sca) {
        Matcher match = ski.matcher(src);
        if (match.lookingAt())
            match.region(match.end(), src.length());
        match.usePattern(sca);
        return match.lookingAt() ? match.group() : null;
    }
}