java/ch/wlkl/wsm/Formatter.java
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package ch.wlkl.wsm;
import static ch.wlkl.env.Env.*;
import ch.wlkl.wsm.Syntax.SynLex;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author walter
*/
interface Formatter {
/**
* format ele
*
* @param ele the Formattable to format
* @param fmt the Format to use
*/
void format(Format fmt, Formattable ele);
/**
* recursively format a subelement
*
* @param fx index of subelement
* @param ele the Formattable to format
*/
void format(int fx, Formattable ele);
/**
* format a subelement
*
* @param fx index of subelement
* @param txt the text for the subelement
*/
void format(int fx, String txt);
/**
* format a subelement without data
*
* @param fx index of subelement
*/
void format(int fx);
void formatOk(Format fmt, Formattable ele);
Formatter reset();
void toOut();
CharSequence text();
static Formatter make() {
return new FormatterOut();
}
static class FormatterOut implements Formatter {
StringBuilder t = new StringBuilder();
int ind = 0;
final int fl0len;
final String[] indAll = new String[20];
final String delims;
final String squash;
Format format = null;
String[][] code = null; // the code for all elements
String[] coEl = null; // the code for the current eleIx
boolean chcOk = false;
int lex = -1, lexNx = -1, // index of lex work in code
eleIx = -1, // the index of the current element (in work[0])
preIx = -1;// the index of the previous element (in work[0])
boolean lexOk = true; // true if output ok for lex
int overCx = -1;
int overInd = -1;
Formattable subEle = null;
String subStr = null;
public FormatterOut() {
this(0, null, null, null);
}
public FormatterOut(int l, String i, String d, String s) {
fl0len = l < 5 ? 50 : l;
i = i == null ? " " : i;
delims = d == null ? " \t\n" : d;
squash = delims + (s == null ? ",.:;+*?()[]{}<>" : s);
indAll[0] = "\n";
for (int y = 1; y < indAll.length; y++) {
indAll[y] = indAll[y - 1] + i;
}
}
public Formatter reset() {
t.setLength(ind = 0);
return this;
}
/**
* format ele with format fmt - dy if not applicable
*
* @param ele the Formattable to format
* @param fmt the Format to apply
*/
public void formatOk(Format fmt, Formattable ele) {
chcOk = true;
format(fmt, ele);
if (!chcOk) {
dy("cannot formatOk ele=", ele, ", fmt=", fmt);
}
}
/**
* recursively format a subelement to t
*
* @param fx index of subelement
* @param ele the Formattable to format
*/
@Override
public void format(int fx, Formattable ele) {
final Formattable oldEle = subEle;
final String oldStr = subStr;
subEle = ele;
subStr = null;
formatIx(fx);
subEle = oldEle;
subStr = oldStr;
}
/**
* recursively format a subelement to t
*
* @param fx index of subelement
* @param txt the text for subelement
*/
@Override
public void format(int fx, String txt) {
final Formattable oldEle = subEle;
final String oldStr = subStr;
subEle = null;
subStr = txt;
formatIx(fx);
subEle = oldEle;
subStr = oldStr;
}
/**
* format fx without data
*
* @param fx index of subelement
*/
@Override
public void format(int fx) {
final Formattable oldEle = subEle;
final String oldStr = subStr;
subEle = null;
subStr = null;
formatIx(fx);
subEle = oldEle;
subStr = oldStr;
}
/**
* output a newline
*/
public void nl() {
while (t.length() > 0 && t.charAt(t.length() - 1) == ' ') {
t.setLength(t.length() - 1);
}
t.append(nlStr(ind));
}
/**
* return newline with intendation
*
* @param i indentation level
* @return
*/
public String nlStr(int i) {
return indAll[i];
}
/**
* append a String, if necessary after a space (to avoid token joining)
*
* @param txt
* @return
*/
public FormatterOut app(String txt) {
if (!txt.equals("") && squash.indexOf(txt.charAt(0)) < 0 && t.length() != 0 && squash.indexOf(t.charAt(t.length() - 1)) < 0) {
t.append(' ');
}
t.append(txt);
return this;
}
/**
* check whether a part of output is a proper line (neither too long,
* nor containing \n)
*
* @param b begin index
* @param e end index
* @return true if a proper line
*/
public boolean checkInline(int b, int e) {
if (b >= t.length()) {
return true;
}
if (t.charAt(b) == '\n') {
for (b++; b < e && t.charAt(++b) == ' '; b++) {
}
}
if (e - b > fl0len) {
return false;
}
int nx = t.indexOf("\n", b);
return nx >= e || nx < b;
}
/**
* checkInline from b to end
*
* @param b begin index
* @return
*/
public boolean checkInline(int b) {
return checkInline(b, t.length());
}
/**
* check the line lengths in region of output
*
* @param b begin index
* @param e end index
* @return true if all lines are not longer then fl0len
*/
public boolean checkLinelen(int b, int e) {
int linC = 0, linM = 0, ovrC = 0, ovrS = 0, nx;
do {
linC++;
if (t.charAt(b) == '\n') {
for (b++; b < e && t.charAt(b) == ' '; b++) {
}
}
nx = t.indexOf("\n", b);
int a = (nx <= e && nx >= b ? nx : e) - b;
if (linM < a) {
linM = a;
}
if (a > fl0len) {
ovrC++;
ovrS += a - fl0len;
}
b = nx;
} while (nx > 0 && nx < e);
// say("linC="+linC + " linM="+linM + " ovrC=" + ovrC + " ovrS=" + ovrS);
return ovrC == 0;
}
/**
* check the line lengths in a end region of output
*
* @param b begin index
* @return true if all lines are not longer then fl0len
*/
public boolean checkLinelen(int b) {
return checkLinelen(b, t.length());
}
public CharSequence text() {
return t;
};
public void toOut() {
for (String li : t.toString().split("\n")) {
out(li);
}
}
/**
* format ele with format fmt
*
* @param ele the Formattable to format
* @param fmt the Format to apply
*/
@Override
public void format(Format fmt, Formattable ele) {
final Format oldFormat = format;
final String[][] oldCode = code;
final String[] oldCoEl = coEl;
final int oldEleIx = eleIx, oldPreIx = preIx;
debug("enter format fmt=", fmt, ", ele=", ele);
format = fmt;
code = format.code();
coEl = null;
eleIx = preIx = -99;
assert code != null && code.length >= 2 : assFail("code fmt=", fmt, ", ele=", ele);
formatChcLex(ele);
format = oldFormat; // restore state variables for recursive calls
code = oldCode;
coEl = oldCoEl;
eleIx = oldEleIx;
preIx = oldPreIx;
debug("exit format fmt=", fmt, ", ele=", ele);
}
/**
* format ele, try different chc and increasing lex variants
*
* @param ele
*/
public void formatChcLex(Formattable ele) {
if (!chcOk) {
return;
}
final int oldLen = t.length();
final int oldInd = ind;
final int oldLex = lex, oldLexNx = lexNx;
final boolean oldLexOk = lexOk;
final int oldOverInd = overInd, oldOverCx = overCx;
int ff = 0;
lex = 0;
while (true) { // try different chc and lex
chcOk = true;
lexNx = 9;
lexOk = true;
overCx = -99;
overInd = -99;
formatIx(0);
int ffNx = format.format(this, ff, ele);
if (!chcOk && ff > 1) { // try next choice
ff = ffNx;
lex = 0;
} else {
formatIx(code.length - 1); // last eleIx is unconditional postfix
if (overInd >= 0) {
ruleOver();
}
ind = oldInd;
if (lexNx >= 9 || (lexOk && checkLinelen(oldLen))) {
break;
}
lex = lexNx; // try next lex
}
t.setLength(oldLen);
}
lex = oldLex;
lexNx = oldLexNx;
lexOk = oldLexOk;
overInd = oldOverInd;
overCx = oldOverCx;
}
/**
* append code for elementIx ix, possibly including subEle (at $ clause)
*/
public void formatIx(int ix) {
preIx = eleIx;
eleIx = ix;
if (eleIx >= code.length)
outPa("");
coEl = code[eleIx];
int y = appCode(0);
assert y >= coEl.length : assFail("not at end ix=", ix, ", coEl=", coEl);
}
/**
* append code in coEl from index x
*
* @param x: current index in coEl
* @return next index in coEl
*/
public int appCode(int x) {
for (; x < coEl.length; x++) { // // handle each clause in abst
String s = coEl[x];
char c = s.charAt(0);
if (c == ':') {
x = appLex(x, -1) - 1;
} else if (c == '|') {
if (s.charAt(1) == '(') {
x = appPre(x);
} else {
return x;
}
} else if (c == '$') {
int eleStart = t.length();
if (subEle != null) {
format.format(this, eleIx, subEle);
} else if (subStr != null) {
app(subStr);
}
x = appLex(x + 1, eleStart) - 1;
} else if (c == '%' || c == '\"') {
app(s.substring(1)); // append literal
} else {
er("bad code " + c, x);
}
}
return x;
}
/**
* append pre alternatives, i.e. clauses |(d |d ... |)
*
* @param x: current index in coEl
* @return next index in coEl
*/
public int appPre(int x) {
assert coEl[x].substring(0, 2).equals("|(") : assFail("bad pre x=", x, ", coEl=", coEl[x]);
int v = Integer.valueOf(coEl[x].substring(2));
while (preIx >= v) {
x = lexUntil(x + 1, "|");
if (coEl[x].equals("|)")) {
return x;
}
v = Integer.valueOf(coEl[x].substring(1));
}
x = appCode(x + 1);
return lexUntil(x, "|)");
}
/**
* append pre alternatives i.e. ${ | ... $} clauses
*
* @param x: current index in coEl
* @return next index in coEl
*/
public int lexUntil(int x, String u) {
for (; x < coEl.length; x++) {
if (coEl[x].charAt(0) != '|') {
} else if (coEl[x].equals(u)) {
return x;
} else if (coEl[x].length() <= 1) {
} else if (coEl[x].substring(0, 2).equals("|(")) {
x = lexUntil(x + 1, "|)");
} else if (u.equals("|")) {
return x;
}
}
assert false: assFail(u, " not found in ", coEl);
return x;
}
/**
* append lex alternatives (: clauses)
*
* @param x: current index in coEl
* @return next index in coEl after all alternatives
*/
public int appLex(int x, int eleStart) {
int v;
for (;; x++) {
if (x >= coEl.length || coEl[x].charAt(0) != ':') {
return x;
} else if (lex < (v = Integer.valueOf(coEl[x].substring(1, 2)))) {
break;
}
}
if (lexNx > v) {
lexNx = v;
}
String s = coEl[x];
for (int y = 2; y < s.length(); y++) {
char c = s.charAt(y);
if (c == ' ') {
t.append(c);
} else if (c == 'n') {
nl();
} else if (c == '>') {
ind++;
} else if (c == '<') {
ind--;
} else if (c == 'i') {
assert eleStart >= 0 : assFail("i not after $");
lexOk &= checkInline(eleStart); // after $i check inline
} else if (c == 'o') {
ruleOver();
} else {
assert false: assFail("bad lex " + c, s, y);
}
}
for (; x < coEl.length && coEl[x].charAt(0) == ':'; x++) {
}
return x;
}
/**
* lex 'o' inserts nl at last o if necessary
*/
public void ruleOver() {
int laNL = t.lastIndexOf("\n");
for (laNL++; laNL < t.length() && t.charAt(laNL) == ' '; laNL++) {
};
int ex = overCx;
do { // move overCx before spaces
overCx--;
} while (overCx >= 0 && t.charAt(overCx) == ' ');
if (laNL + 10 < overCx && t.length() - laNL > fl0len) { // insert NL if laNL faraway and laNL to overCX is not very small
while (ex < t.length() && t.charAt(ex) == ' ') {
ex++;
}
t.replace(overCx + 1, ex, nlStr(overInd));
}
overCx = t.length(); // remember current state for next over
overInd = ind;
}
void er(String e, int x) {
dy(e, " cx=" + x + ": " + (x >= coEl.length ? " @end" : coEl[x]) + " coEl=", coEl);
}
}
/**
* create code from String
*
* @param s source of format
* @return formatted code
*/
public static String[][] toCode(String s, int es) {
ArrayList<String[]> c = new ArrayList();
ArrayList<String> ele = new ArrayList();
int sx = 0;
while (true) {
ele.clear();
sx = toCode(s, sx, ele);
c.add(ele.toArray(new String[ele.size()]));
if (sx >= s.length() || s.charAt(sx) != ',') {
break;
}
sx++;
}
for (int i = c.size(); i <= es; i++) {
c.add(new String[]{"$"});
}
if (c.size() < es + 2) {
c.add(new String[0]);
}
String[][] abs = c.toArray(new String[c.size()][]);
if (sx < s.length() && s.charAt(sx) == ':') {
sx = mergeLex(s, sx + 1, abs);
}
assert sx >= s.length() :assFail("not at end", s, sx);
return abs;
}
/**
* handle source for one eleIx (until , or :)
*
* @param s source String
* @param x start index in s
* @param ele to add the generated code clauses
* @return index for next character in s
*/
public static int toCode(String s, int x, List<String> ele) {
int bar = -1;
int barEx = -1;
int y;
while (true) {
char c = ' ';
for (; x < s.length() && ' ' == (c = s.charAt(x)); x++) {
}
if (" ,:".indexOf(c) >= 0) { // at end
if (bar >= 0) {
dy("end in or", s, x);
}
return x;
}
y = s.indexOf(' ', x + (c == '\'' ? 2 : 1)); // find word, possibly with escape '
if (y < x) {
y = s.length();
}
String w = s.substring(x, y);
if (c == '|') {
if (w.equals("|")) {
if (bar >= 0) {
ele.set(barEx, "|999");
}
bar = -1;
ele.add("|)");
} else {
if (barEx < 0) {
dy("no preceeding | at " + x + ": " + s.substring(x) + " in " + s);
}
ele.set(barEx, bar < 0 ? "|(" + w.substring(1) : w);
int v = Integer.valueOf(w.substring(1));
if (v <= bar) {
dy("| not increasing");
}
bar = v;
ele.add("|??");
}
barEx = ele.size() - 1;
} else if (c == '$') {
ele.add("$");
y = x;
} else {
ele.add("%" + (c == '\'' ? w.substring(1) : w));
}
x = y + 1;
}
}
/**
* merge lex syntax with existing code (remove lexx syntax from existing
* code and merge lex from s
*/
public static int mergeLex(String s, int x, String[][] abs) {
ArrayList<String> ele = new ArrayList(), lex = new ArrayList();
for (int ax = 0; ax < abs.length; ax++) {
ele.clear();
x = mergeLex(s, x, abs[ax], ele);
// mergeLex(abs[ax], lex, ele);
abs[ax] = ele.toArray(new String[ele.size()]);
if (x >= s.length()) {
} else if (s.charAt(x) == ',') {
x++;
} else {
dy("comma expexted not " + s.charAt(x) + " @" + x + " in " + s);
}
}
if (x < s.length()) {
dy("too many ,", s, x);
}
debug(3, "mergeLex end " + s + ", abs=", abs);
return x;
}
/**
* merge lex syntax with existing code for one elementIx (remove lexx syntax
* from existing code and merge lex from s
*/
public static int mergeLex(String s, int x, String[] abs, List<String> ele) {
int ly = 0;
int y;
char c;
for (int ay = 0;; ay++) {
if (ay < abs.length && "$%|".indexOf(abs[ay].charAt(0)) < 0) {
continue;
}
y = x;
while (true) { // add lex alternatives
c = y < s.length() ? s.charAt(y) : ',';
if (",|%$".indexOf(c) >= 0) {
if (y > x) {
ele.add(":9" + s.substring(x, y));
}
x = y;
break;
} else if ("0123456789".indexOf(c) >= 0) {
ele.add(":" + c + s.substring(x, y));
x = ++y;
} else {
y++;
}
}
if (ay >= abs.length) {
if (c != ',') {
dy("end of abs ", a2r(abs) + " overflow " + c + " in lex", s, y);
}
return x;
} else {
ele.add(abs[ay]);
if (abs[ay].charAt(0) == c) {
x++;
} else if (c != ',') {
dy("abs[" + ay + "] = " + abs[ay] + " in " + a2r(abs) + " mismatches " + c + " in lex", s, x);
}
}
}
}
/**
* replace existing (abstract) syntax until :, mergeLex afterwards
*/
public static int mergeSyn(String s, int x, String[][] abs) {
// toOut("mergeLex begin " + s + " abs " + a2s(abs));
ArrayList<String> ele = new ArrayList();
// replace existing syntax until :
for (int ax = 0; ax < abs.length && s.charAt(x) != ':'; ax++) {
if (s.charAt(x) != ',') {
ele.clear();
x = toCode(s, x, ele);
abs[ax] = ele.toArray(new String[ele.size()]);
}
if (s.charAt(x) == ',') {
x++;
}
}
if (s.charAt(x) != ':') {
dy("colon : expected", s, x);
}
return mergeLex(s, x + 1, abs);
}
}