// // AWL.cs // // // This is a very simple C# parser for // "Andrew's Weaving Language (AWL)" // as described in the March/April issue of // IEEE Computer Graphics & Applications. // AWL is a generalization of the weaving language found // in Painter, sold by Procreate. // This file is being made public for educational and // casual use only. Absolutely no guarantees are made // as to correctness or reliability. // // To use the parser, create an instance of the AWL object, // and then call ProcessString with a string containing your // AWL expression, which is a list of tokens separated // by white space. That returns a string, which is itself // a white-space separated list of tokens that // are the result of evaluating the input string. // // The program works by maintaining a stack. As words // are read in from the user's command, the stack is // pushed and popped accordingly. In a well-formed // expression, there will be one element left on top of // the stack, and that's what we return. The only odd // thing about this stack is that elements can be lists. // So if we have the sequence 1 2 3, then that would all // occupy one node on the stack, rather than three separate // positions. In general, if we see a number from the user, // and the top of the stack is a number or a list, then // we append that number to the list on top of the stack. // Commands cause the stack to be modified. Commands that // take several arguments usually pop them off the stack, // operate, and push their results back on top. // // This code was designed for easy reading and modifying, // and not for efficiency or elegance. It started out simple // and I wrote extra bits and pieces as I needed them. // I know that there are lots of ways to improve this parser. // I think everything here works properly, but I wrote this // code just to test the ideas. It's not bullet-proof, and // I haven't exhaustively tested it. // // The biggest omission here is error checking. I don't // do a lot of it, and what I do check is often minimal. // It's easy to make this code crash. If you pass // ProcessString an invalid string, it may survive and // return an empty string, but more likely it will crash // and cause a run-time error. // // A huge improvement would be to add type-checking code // to the arguments. Presently, if I expect an integer or // a list of numbers or a string or whatever to be on top // of the stack, then I treat the top of the stack as though // it was one of those things without confirming that it is. // If the user enters a badly-formed expression, this will // probably cause an error, or even a crash. So maintaining // some kind of record of the type of each element in the // stack would be very useful. Probably the easiest way would // be to simply maintain a parallel "type stack" that is // updated simultaneously with the normal stack, and simply // tells us the type of the corresponding entries. // // If you do use this code, or modify it, I would ask // that you please leave this comment block intact, and // add your comments below. Please don't write to me // about maintainance, bugs, etc. since I am not actively // supporting this code. // // Have fun! // -Andrew Glassner // andrew_glassner@yahoo.com // // code copyright (c) 2003 Andrew Glassner // // basic programming: 12 January 2003 // additional comments: 3 August 2003 // /* Summary of commands in AWL, Andrew's Weaving Language. See the column "Andrew Glassner's Notebook" in the January 2003 issue of IEEE Computer Graphics and Applications for details and examples. The S column provides the symbolic shortcut, if available. If the R column has a Y, the command reshapes its inputs. \A and \B are vectors (lists of numbers). \c and \d are single numbers. \A_i refers to the i'th element of \A. Special values are \A_0 for the first element of \A, and \A_L for the last element of \A. command | S | R | summary --------------------+-----+---+---------------------------------- \A \d extend | + | | repeat or clip \A to \d elements \A \d repeat | * | | repeat \A a total of \d times \A reverse | @ | | reverse the elements of \A \A \d rotatel | << | | rotate \A left by \d steps \A \d rotater | >> | | rotate \A right by \d steps \A \d nth | | | take every \d'th element of \A \A palindrome | | | | \A followed by a near-reversal --------------------+-----+---+---------------------------------- \A \B down | > | | \A, descending run from \A_L to \B_0, \B \A \B \c downloop | >l | | like down but include \c runs as well \A \B downup | >u | Y | alternating down and up runs \A \B \c downuploop | >ul | Y | like downup but include \c runs as well \A \B up | < | | \A, ascending run from \A_L to \B_0, \B \A \B \c uploop | > | | make \c repeats of \A rotating each by \d more \A \c \d twilll | t<< | | like twillr but rotate left \A \B template | : | | create a sub-articulation using \B as a template --------------------+-----+---+---------------------------------- clear | | | erase the stack concat | , | | concatanate the top two stack elements dup | | | pop the top of stack and push it back twice pop | | | discard top of stack push | / | | consider all since last command a single sequence \c \d domain | | | set the domain to [\c,\d] swap | | | exchange top two stack elements veclen | | | push length of list on top of stack vmax | | | push largest element in sequence on top of stack vmin | | | push smallest element in sequence on top of stack --------------------+-----+---+---------------------------------- */ // // using System; using System.Collections; namespace Weaver { /// /// Summary description for AWL. /// public class AWL { ArrayList aList; // the list of tokens int PatternLen; // for up, down, etc. public AWL() // constructor { aList = new ArrayList(); InitStack(); } // this is the main routine // hand it the string you want to process, // and it returns a tokenized string of digits public String ProcessString(String str) { InitStack(); str.Trim(); String [] words = str.Split(' '); for (int i=0; i=0; i--) { if (aList[i] != null) { newa.Insert(0, aList[i]); } } aList = newa; } // take an ArrayList off the top of the stack private Object PopArrayList() { Object myobj; RemoveNulls(); if (aList.Count == 0) { return(null); } // the following testing isn't necessary thanks to RemoveNulls(), but it's // there for a backstop in case somehow the top element is a null somehow anyway do { myobj = aList[0]; aList.RemoveAt(0); } while ((myobj == null) && (aList.Count > 0)); return(myobj); } // find n such that (na)%b = 0 private int FindReps(int a, int b) { int n = 1; do { if (((n*a)%b) == 0) { return(n); } n++; } while(n<10000); // sanity check for runaways return(1); } // simple string compare: true iff they're the same private bool Tis(String a, String b) { if (String.Compare(a, b, true) == 0) return(true); return(false); } // replace all runs of spaces with a single space public String OneSpace(String str) { if (str == null) return(null); String newstr = str.Trim(); do { newstr = newstr.Replace(" ", " "); } while (newstr.IndexOf(" ") > 0); return(newstr); } // return an array at the top of the stack as a list of words private String[] PopStringArray() { String v = (String)PopArrayList(); v = OneSpace(v); String[] w = v.Split(' '); return(w); } // return the string at the top of the stack private String PopString() { String s = (String)PopArrayList(); return(s); } // return the integer at the top of the stack private int PopInt() { String s = (String)PopArrayList(); int i = Int32.Parse(s); return(i); } // this does the "normalization" step I mention in the column. // It pops the top two entries in the stack, both of which // are presumed to be StringArrays (that is, lists of numbers). // Then the shorter one is extended by repetition until it is // as long as the longer one, and then they are both pushed // back onto the stack. I sometimes also called this "reshaping". private void NormalizeTopTwoOnStack() { String [] Rw = PopStringArray(); String [] Lw = PopStringArray(); int maxlen = Math.Max(Rw.Length, Lw.Length); String newR = null; String newL = null; for (int i=0; i>") == true) { DoTwillR(); cmd = push = true; } if (Tis(token, "|") == true) { DoPalindrome(); cmd = push = true; } if (Tis(token, ">") == true) { DoDown(); cmd = push = true; } if (Tis(token, ">l") == true) { DoDownLoop(); cmd = push = true; } if (Tis(token, ">u") == true) { DoDownUp(); cmd = push = true; } if (Tis(token, ">ul") == true) { DoDownUpLoop(); cmd = push = true; } if (Tis(token, "<") == true) { DoUp(); cmd = push = true; } if (Tis(token, ">") == true) { DoTwillR(); cmd = push = true; } if (Tis(token, "t<<") == true) { DoTwillL(); cmd = push = true; } if (Tis(token, "/") == true) { DoPush(); cmd = true; } if (cmd == true) { if (push == true) DoPush(); return; } // get the top of the stack - but don't use PopArrayList, as that ignores nulls, // and pushing new operands on the stack may go into a null entry, since that's // what's on top after an operation. Then append the new token to the end of // what was on top, and push the result back on. Object myobj = null; if (aList.Count > 0) { myobj = aList[0]; aList.RemoveAt(0); } String olda = (String)myobj; String newa = olda + " " + token; PushArrayList(newa); } // The routines from here on implement AWL commands. Many are stand-alone, and // just directly implement their function. Some of the more complicated commands // share code. For example, the various forms of block, which are the first ones // to appear below, call the routine "doGeneralBlock" with different sets of // parameters. All the tricky stuff for getting blocks to work just right // appears just once in "doGeneralBlock", making life a lot easier. Similarly, // up/down commands call "doGeneralDown" and "doGeneralUpDown". Most of the // commands are straightforward implementations of their description in the column, // summarized above. private void DoBlock() { NormalizeTopTwoOnStack(); String [] Rw = PopStringArray(); String [] Lw = PopStringArray(); doGeneralBlock(Lw, Rw, false); } private void DoBlockPal() { NormalizeTopTwoOnStack(); String [] Rw = PopStringArray(); String [] Lw = PopStringArray(); doGeneralBlock(Lw, Rw, true); } void DoIBlock() { String [] W = PopStringArray(); String [] Lw = new String[W.Length/2]; String [] Rw = new String[W.Length/2]; for (int i=0; i 0); v = OneSpace(v); String[] w = v.Split(' '); String [] Lw = new String[w.Length]; String [] Rw = new String[w.Length]; char [] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; for (int i=0; i= 0) { Lw[i] = w[i].Substring(0, firstNum); int count = Int32.Parse(w[i].Substring(firstNum)); count = count/2; Rw[i] = count.ToString(); } } doGeneralBlock(Lw, Rw, true); } private void doGeneralBlock(String [] Lw, String [] Rw, bool makePalindrome) { String newString = null; int i, j, reps; for (i=0; i 0) { reps = Int32.Parse(Rw[i]); for (j=0; j= PatternLen) newVal -= PatternLen; } newa = newa + " " + newVal; } } if (goingDown) { newVal = lw-1; while (newVal != rw) { newa = newa + " " + newVal; newVal = newVal-1; if (newVal < 0) newVal = PatternLen-1; } } else { newVal = lw+1; while (newVal != rw) { newa = newa + " " + newVal; newVal = newVal+1; if (newVal >= PatternLen) newVal = 0; } } newa = newa + " " + rw; goingDown = !goingDown; } newa = OneSpace(newa); PushArrayList(newa); } private void DoRamp() { String [] Rw = PopStringArray(); String [] Lw = PopStringArray(); int rs = Int32.Parse(Rw[0]); int le = Int32.Parse(Lw[Lw.Length-1]); // going from le to rs if (le < rs) { generalUp(0, Lw, Rw); } else { generalDown(0, Lw, Rw); } } private void DoRampLoop() { int numLoops = PopInt(); String [] Rw = PopStringArray(); String [] Lw = PopStringArray(); int rs = Int32.Parse(Rw[0]); int le = Int32.Parse(Lw[Lw.Length-1]); // going from le to rs if (le < rs) { generalUp(numLoops, Lw, Rw); } else { generalDown(numLoops, Lw, Rw); } } private void DoUp() { String [] Rw = PopStringArray(); String [] Lw = PopStringArray(); generalUp(0, Lw, Rw); } private void DoUpLoop() { int numLoops = PopInt(); String [] Rw = PopStringArray(); String [] Lw = PopStringArray(); generalUp(numLoops, Lw, Rw); } private void generalUp(int numLoops, String [] Lw, String [] Rw) { int lend = Int32.Parse(Lw[Lw.Length-1]); int rend = Int32.Parse(Rw[0]); int i, j, r; String newa = null; for (i=0; i= PatternLen) i = 0; } } i = lend+1; while (i != rend) { newa = newa + " " + i; if (++i >= PatternLen) i = 0; } for (i=0; i b0) dir = -1; j += dir; while (j != b0) { newa += " " + j; j += dir; } } // put in the palindrome j = 0; while (j <= i) { newa += " " + Rw[j]; j++; }; j -= 2; while (j >= 0) { newa += " " + Rw[j]; j--; } // and count up or down if we're not done if (i < Lw.Length-1) { nexta = Int32.Parse(Lw[i+1]); if (nexta != b0) { j = b0; dir = 1; if (j > nexta) dir = -1; j += dir; while (j != nexta) { newa += " " + j; j += dir; } } } } newa = OneSpace(newa); PushArrayList(newa); } private void DoTwillR() { int repCount = PopInt(); int shiftCount = PopInt(); String [] w = PopStringArray(); String newa = ""; int i, j, k, index; j = 0; for (i=0; i= w.Length) index -= w.Length; newa = newa + " " + w[index]; } j += shiftCount; } newa = OneSpace(newa); PushArrayList(newa); } private void DoTwillL() { int repCount = PopInt(); int shiftCount = PopInt(); String [] w = PopStringArray(); String newa = ""; int i, j, k, index; j = 0; for (i=0; i= w.Length) index -= w.Length; newa = newa + " " + w[index]; } j += shiftCount; } newa = OneSpace(newa); PushArrayList(newa); } private void DoReverse() { String [] w = PopStringArray(); String newa = ""; for (int i=0; i vmax) vmax = v; } String newa = vmax+" "; newa = OneSpace(newa); PushArrayList(newa); } private void DoVmin() { String [] w = PopStringArray(); int vmin = Int32.Parse(w[0]); for (int i=0; i= Lw.Length) index -= Lw.Length; newa = newa + " " + Lw[index]; } newa = OneSpace(newa); PushArrayList(newa); } private void DoRotateL() { int steps = PopInt(); String [] Lw = PopStringArray(); String newa = ""; for (int i=0; i= Lw.Length) index -= Lw.Length; newa = newa + " " + Lw[index]; } newa = OneSpace(newa); PushArrayList(newa); } private void DoNth() { int n = PopInt(); String [] w = PopStringArray(); String newa = null; int i = 0; int j = 0; while (i < w.Length) { if (j == 0) { newa = newa + " " + w[i]; } i++; j++; if (j >= n-1) j = 0; } newa = OneSpace(newa); PushArrayList(newa); } private void DoInterleave() { NormalizeTopTwoOnStack(); String [] Rw = PopStringArray(); String [] Lw = PopStringArray(); String newa = null; for (int i=0; i0; i--) newa = newa + " " + w[i]; newa = OneSpace(newa); PushArrayList(newa); } private void DoConcat() { String Rs = PopString(); String Ls = PopString(); String newa = Ls + " " + Rs; newa = OneSpace(newa); PushArrayList(newa); } private void DoSwap() { String Rs = PopString(); String Ls = PopString(); PushArrayList(Ls); PushArrayList(Rs); } private void DoDomain() { int hi = PopInt(); int lo = PopInt(); PatternLen = 1+hi; } private void DoPermute() { // first, repeat L until it's an integer multiple of length of R String [] Rw = PopStringArray(); String [] Lw = PopStringArray(); // find how many copies i of L are needed so that i|L| is a multiple of |R| int numCopies = FindReps(Lw.Length, Rw.Length); int newLenofR = numCopies * Lw.Length; String newa = null; int i, rval; for (i=0; i