Wednesday, October 21, 2009

The Doggerel Programming Language

The Doggerel programming language interprets lines of verse as code. The instructions are encoded in the meter, rhyme scheme, and the acrostic of each stanza. It has a single stack of strings and can define subroutines that are called by name.

How the meter, rhyme scheme, and acrostic are determined is implementation-dependent.

Instructions



  • PushString - meter is iambic with an odd number of feet. Pushes the acrostic of the stanza onto the stack. If the first line ends with an unstressed syllable, all lines that rhyme with the first line, including the first line, are removed from the acrostic. This enables pushing the empty string.

  • PushCharacter - meter is iambic with an even number of feet. Pushes a string containing the character named by the acrostic onto the stack. The names of characters are implementation-dependent. (For example, "one" causes "1" to be pushed on the stack.) If the first line ends with an unstressed syllable, all lines that rhyme with the first line, including the first line, are removed from the acrostic.

  • Pop - meter is trochaic with the number of feet an even multiple of 4. Removes elements from the stack. The first line in the stanza corresponds to the top of the stack, the 2nd line corresponds to the 2nd element in the stack, etc. All elements corresponding to lines rhyming with the first line, including the top element, are removed from the stack.

  • Float - meter is trochaic with the number of feet an even multiple of 4 plus 1. Moves elements to the top of the stack. The first line in the stanza corresponds to the top of the stack, the 2nd line corresponds to the 2nd element in the stack, etc. All elements corresponding to lines not rhyming with the first line are moved to the top of the stack, but otherwise retaining their relative positions.

  • Read - meter is trochaic with the number of feet an even multiple of 4 plus 2. Reads a line from standard input and pushes it onto the stack.

  • Write - meter is trochaic with the number of feet an even multiple of 4 plus 3. Writes elements on the stack to standard output. The first line in the stanza corresponds to the top of the stack, the 2nd line corresponds to the 2nd element in the stack, etc. All elements corresponding to lines rhyming with the first line, including the top element, are written to standard output, starting with the top element and ending with the deepest element.

  • Call - meter is anapestic with the number of feet an even multiple of 4. Calls previously defined subroutines by name. The first line in the stanza corresponds to the top of the stack, the 2nd line corresponds to the 2nd element in the stack, etc. If the top of the stack is not the empty string, it calls the subroutine defined by element corresponding to the first subsequent line that rhymes with the first line. If no such subroutine is defined, it calls the one corresponding to the 3rd subsequent line, and then the 5th, 7th, etc if they are not defined. If the top of the stack is empty, it calls the subroutine defined by the element corresponding to the 2nd subsequent line, or the 4th, 6th, etc, if no such subroutine is defined.

  • Concatenate - meter is anapestic with the number of feet an even multiple of 4 plus 1. Concatenates strings on the stack and pushes the result onto the stack. The first line in the stanza corresponds to the top of the stack, the 2nd line corresponds to the 2nd element in the stack, etc. Strings corresponding to lines the rhyme with the first line, including the first line, are concatenated, starting with the top of the stack and ending with the deepest element.

  • Return - meter is anapestic with the number of feet an even multiple of 4 plus 2. Returns from the most recent call. Exits if the call stack is empty.

  • Define - meter is anapestic with the number of feet an even multiple of 4 plus 3. The subsequent instructions up to a (non-nested) Return instruction define a subroutine that is associated with a string on the stack. Defines can be nested. Multiple subroutines can be defined. Any subroutines previously associated with a string are discarded. The first line in the stanza corresponds to the top of the stack, the 2nd line corresponds to the 2nd element in the stack, etc. The top of the stack is associated with the subsequent subroutine. Additional strings, in order of increasing depth, corresponding to lines rhyming with the first line, are also associated with subsequent instructions up to a Return instruction.

  • Loop - meter is dactylic with the number of feet an even multiple of 3. If the stack is not empty and the top of the stack is not the empty string, resume execution from the start of the subroutine, or, if the call stack is empty, from the very first instruction. If the stack is empty or if the top of the stack is the empty string, continue execution with the subsequent instruction.

  • DropMatchingHead - meter is dactylic with the number of feet an even multiple of 3 plus 1. The first line in the stanza corresponds to the top of the stack, the 2nd line corresponds to the 2nd element in the stack, etc. For the elements corresponding to lines that rhyme with the first line, starting with the deepest element and ending with the 2nd element, push that string with the start that matches the start of the top of the stack removed. For example, if the rhyme scheme is ABABAB, and the stack contains "abc" "2" "axy" "4" "abcdef" "6" "7", the new top of the stack would be "xy" "def" "7".

  • DropHead - meter is dactylic with the number of feet an even multiple of 3 plus 2. The first line in the stanza corresponds to the top of the stack, the 2nd line corresponds to the 2nd element in the stack, etc. For the elements corresponding to lines that rhyme with the first line, starting with the deepest element and ending with the 2nd element, push that string with the with the initial substring with length the length of the top of the stack removed. For example, if the rhyme scheme is ABABAB, and the stack contains "abc" "2" "axy" "4" "abcdef" "6" "7", the new top of the stack would be "" "def" "".


Implementation


Here is a Java implementation in two files. The first, Boring.java, implements the boring stack interpreter. The second, DoggerelCMUDict.java, implements the verse parsing using a file from CMUdict.

Boring.java


import java.lang.reflect.Field;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class Boring {
public static void main(String[] args) throws Exception {
if (args.length != 1)
System.err.println("Usage: java Boring FILE.BORING");
else
new Interpreter(new BufferedReader(new InputStreamReader(System.in)), new PrintWriter(System.out), BoringSyntaxParser.parse(new BufferedReader(new FileReader(args[0])))).run();
}

public static class NamedCharacters {
private static final String NUL = "\u0000";
private static final String SOH = "\u0001";
private static final String STX = "\u0002";
private static final String ETX = "\u0003";
private static final String EOT = "\u0004";
private static final String ENQ = "\u0005";
private static final String ACK = "\u0006";
private static final String BEL = "\u0007";
private static final String BS = "\u0008";
private static final String HT = "\u0009";
private static final String LF = "\n";
private static final String VT = "\u000b";
private static final String FF = "\u000c";
private static final String CR = "\r";
private static final String SO = "\u000e";
private static final String SI = "\u000f";
private static final String DLE = "\u0010";
private static final String DCONE= "\u0011";
private static final String DCTWO = "\u0012";
private static final String DCTHREE = "\u0013";
private static final String DCFOUR = "\u0014";
private static final String NAK = "\u0015";
private static final String SYN = "\u0016";
private static final String ETB = "\u0017";
private static final String CAN = "\u0018";
private static final String EM = "\u0019";
private static final String SUB = "\u001a";
private static final String ESC = "\u001b";
private static final String FS = "\u001c";
private static final String GS = "\u001d";
private static final String RS = "\u001e";
private static final String US = "\u001f";
private static final String SPACE = "\u0020";
private static final String SPC = "\u0020";
private static final String EXCLAMATION = "\u0021";
private static final String BANG = "\u0021";
private static final String DQUOTE = "\u005c\u0022";
private static final String QUOTE = "\u005c\u0022";
private static final String NUMBER = "\u0023";
private static final String POUND = "\u0023";
private static final String HASH = "\u0023";
private static final String SHARP = "\u0023";
private static final String DOLLAR = "\u0024";
private static final String PERCENT = "\u0025";
private static final String AMPERSAND = "\u0026";
private static final String AMP = "\u0026";
private static final String SQUOTE = "\u0027";
private static final String TICK = "\u0027";
private static final String LPAREN = "\u0028";
private static final String RPAREN = "\u0029";
private static final String ASTERISK = "\u002a";
private static final String STAR = "\u002a";
private static final String PLUS = "\u002b";
private static final String COMMA = "\u002c";
private static final String MINUS = "\u002d";
private static final String HYPHEN = "\u002d";
private static final String DOT = "\u002e";
private static final String PERIOD = "\u002e";
private static final String STOP = "\u002e";
private static final String SLASH = "\u002f";
private static final String ZERO = "\u0030";
private static final String OH = "\u0030";
private static final String ONE = "\u0031";
private static final String TWO = "\u0032";
private static final String THREE = "\u0033";
private static final String FOUR = "\u0034";
private static final String FIVE = "\u0035";
private static final String SIX = "\u0036";
private static final String SEVEN = "\u0037";
private static final String EIGHT = "\u0038";
private static final String NINE = "\u0039";
private static final String COLON = "\u003a";
private static final String SEMICOLON = "\u003b";
private static final String SEMI = "\u003b";
private static final String LESS = "\u003c";
private static final String LT = "\u003c";
private static final String EQUAL = "\u003d";
private static final String EQ = "\u003d";
private static final String GREATER = "\u003e";
private static final String GT = "\u003e";
private static final String QUESTION = "\u003f";
private static final String AT = "\u0040";
private static final String LBRACKET = "\u005b";
private static final String BACKSLASH = "\u005c\u005c";
private static final String RBRACKET = "\u005d";
private static final String CARET = "\u005e";
private static final String CIRCUMFLEX = "\u005e";
private static final String UNDERSCORE = "\u005f";
private static final String BQUOTE = "\u0060";
private static final String GRAVE = "\u0060";
private static final String BACKTICK = "\u0060";
private static final String LBRACE = "\u007b";
private static final String VERTICAL = "\u007c";
private static final String PIPE = "\u007c";
private static final String RBRACE = "\u007d";
private static final String TILDE = "\u007e";
private static final String DEL = "\u007f";

private static HashMap<String,String> map = new HashMap<String,String>();

static {
for (Field field : NamedCharacters.class.getDeclaredFields())
if (String.class == field.getType())
try {
map.put(field.getName(), (String) field.get(null));
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}

public static String get(String name) {
String string = map.get(name.toUpperCase());
return string != null ? string : "";
}
}

public static class BoringSyntaxParser {
private static Instruction parseLine(String line) {
int colon = line.indexOf(':');
if (colon < 0)
return null;
String op = line.substring(0, colon);
String operand = line.substring(colon + 1);
if (op.equals("PushString"))
return new PushInstruction(operand);
if (op.equals("PushCharacter"))
return new PushInstruction(NamedCharacters.get(operand));
if (op.equals("Pop"))
return new PopInstruction(operand);
if (op.equals("Float"))
return new FloatInstruction(operand);
if (op.equals("Define"))
return new DefineInstruction(operand);
if (op.equals("Call"))
return new CallInstruction(operand);
if (op.equals("Return"))
return ReturnInstruction.RETURN_INSTRUCTION;
if (op.equals("Concatenate"))
return new ConcatenateInstruction(operand);
if (op.equals("Write"))
return new WriteInstruction(operand);
if (op.equals("Read"))
return ReadInstruction.READ_INSTRUCTION;
if (op.equals("DropHead"))
return new DropHeadInstruction(operand);
if (op.equals("DropMatchingHead"))
return new DropMatchingHeadInstruction(operand);
if (op.equals("Loop"))
return LoopInstruction.LOOP_INSTRUCTION;
return null;
}

public static List<Instruction> parse(BufferedReader in) {
ArrayList<Instruction> instructions = new ArrayList<Instruction>();
String line;
try {
while ((line = in.readLine()) != null) {
Instruction instruction = parseLine(line);
if (instruction != null)
instructions.add(instruction);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return instructions;
}
}

public static class Interpreter {
private BufferedReader in;
private PrintWriter out;

private List<Instruction> instructions;
private InterpreterFrame currentFrame;
private List<InterpreterFrame> callStack = new LinkedList<InterpreterFrame>();
private List<String> stack = new ArrayList<String>();
private Map<String,List<Instruction>> routines = new HashMap<String,List<Instruction>>();

public Interpreter(BufferedReader in, PrintWriter out, List<Instruction> instructions) {
this.in = in;
this.out = out;
this.instructions = instructions;
this.currentFrame = new InterpreterFrame(instructions);
}

public BufferedReader in() {
return in;
}

public PrintWriter out() {
return out;
}

public List<String> getStack() {
return stack;
}

public void defineRoutine(String routine, List<Instruction> instructions) {
routines.put(routine, instructions);
}

public boolean callRoutine(String routine) {
List<Instruction> instructions = routines.get(routine);
if (instructions == null)
return false;
callStack.add(0, currentFrame);
currentFrame = new InterpreterFrame(instructions);
return true;
}

public void returnFromRoutine() {
if (callStack.size() > 0)
currentFrame = callStack.remove(0);
else
currentFrame.setDone();
}

public InterpreterFrame getFrame() {
return currentFrame;
}

public void run() {
for (;;) {
if (!currentFrame.done())
currentFrame.nextInstruction().execute(this);
else if (callStack.size() != 0)
returnFromRoutine();
else
break;
}
out.flush();
}
}

public static class InterpreterFrame {
private int instructionPointer;
private List<Instruction> instructions;

public InterpreterFrame(List<Instruction> instructions) {
this.instructionPointer = 0;
this.instructions = instructions;
}

public Instruction nextInstruction() {
return instructions.get(instructionPointer++);
}

public void loop() {
instructionPointer = 0;
}

public boolean done() {
return instructionPointer >= instructions.size();
}

public void setDone() {
instructionPointer = instructions.size();
}
}

public static abstract class Instruction {
public abstract void execute(Interpreter state);

protected List<Boolean> parseOperand(String operand) {
List<Boolean> list = new ArrayList<Boolean>();
for (int i = 0; i < operand.length(); i++)
list.add(operand.charAt(i) == operand.charAt(0));
return list;
}

public abstract String getBoring();

protected String getBoringOperand(List<Boolean> elements) {
StringBuilder sb = new StringBuilder();
for (boolean element : elements)
sb.append(element ? "A" : "B");
return sb.toString();
}
}

public static class PushInstruction extends Instruction {
private String string;

public PushInstruction(String string) {
this.string = string;
}

public void execute(Interpreter state) {
state.getStack().add(0, string);
}

public String getString() {
return string;
}

public String getBoring() {
return "PushString:"+string;
}
}

public static class PopInstruction extends Instruction {
private List<Boolean> elements;

public PopInstruction(String operand) {
this.elements = parseOperand(operand);
}

public PopInstruction(List<Boolean> elements) {
this.elements = elements;
}

public void execute(Interpreter state) {
List<String> stack = state.getStack();
for (int i = elements.size() - 1; i >= 0; i--) {
if (elements.get(i) && stack.size() > i)
stack.remove(i);
}
}

public List<Boolean> getElements() {
return elements;
}

public String getBoring() {
return "Pop:"+getBoringOperand(elements);
}
}

public static class FloatInstruction extends Instruction {
private List<Boolean> elements;

public FloatInstruction(String operand) {
this.elements = parseOperand(operand);
}

public FloatInstruction(List<Boolean> elements) {
this.elements = elements;
}

public void execute(Interpreter state) {
List<String> floated = new ArrayList<String>();
List<String> stack = state.getStack();
for (int i = elements.size() - 1; i >= 0; i--) {
if (!elements.get(i) && stack.size() > i)
floated.add(stack.remove(i));
}
for (String string : floated)
stack.add(0, string);
}

public List<Boolean> getElements() {
return elements;
}

public String getBoring() {
return "Float:"+getBoringOperand(elements);
}
}

public static class DefineInstruction extends Instruction {
private List<Boolean> elements;

public DefineInstruction(String operand) {
this.elements = parseOperand(operand);
}

public DefineInstruction(List<Boolean> elements) {
this.elements = elements;
}

private int getRoutineCount() {
int count = 0;
for (boolean element : elements)
if (element)
count++;
return count;
}

public void execute(Interpreter state) {
List<String> stack = state.getStack();
InterpreterFrame frame = state.getFrame();
for (int i = 0; i < elements.size(); i++) {
if (elements.get(i)) {
List<Instruction> instructions = new ArrayList<Instruction>();
int nested = 0;
while (!frame.done()) {
Instruction instruction = frame.nextInstruction();
instructions.add(instruction);
if (instruction instanceof DefineInstruction) {
nested += ((DefineInstruction) instruction).getRoutineCount();
} else if (instruction instanceof ReturnInstruction) {
if (nested == 0)
break;
else
nested--;
}
}
if (i < stack.size())
state.defineRoutine(stack.get(i), instructions);
}
}
}

public List<Boolean> getElements() {
return elements;
}

public String getBoring() {
return "Define:"+getBoringOperand(elements);
}
}

public static class CallInstruction extends Instruction {
private List<Boolean> elements;

public CallInstruction(String operand) {
this.elements = parseOperand(operand);
}

public CallInstruction(List<Boolean> elements) {
this.elements = elements;
}

public void execute(Interpreter state) {
List<String> stack = state.getStack();
int count = 0;
boolean test = false;
for (int i = 0; i < elements.size(); i++) {
if (elements.get(i) && i < stack.size()) {
if (count == 0)
test = stack.get(i).length() == 0;
else if ((count%2 == 0) == test && state.callRoutine(stack.get(i)))
break;
count++;
}
}
}

public List<Boolean> getElements() {
return elements;
}

public String getBoring() {
return "Call:"+getBoringOperand(elements);
}
}

public static class ReturnInstruction extends Instruction {
public static final ReturnInstruction RETURN_INSTRUCTION = new ReturnInstruction();

public void execute(Interpreter state) {
state.returnFromRoutine();
}

public String getBoring() {
return "Return:";
}
}

public static class ConcatenateInstruction extends Instruction {
private List<Boolean> elements;

public ConcatenateInstruction(String operand) {
this.elements = parseOperand(operand);
}

public ConcatenateInstruction(List<Boolean> elements) {
this.elements = elements;
}

public void execute(Interpreter state) {
List<String> stack = state.getStack();
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < elements.size(); i++) {
if (elements.get(i) && i < stack.size())
stringBuilder.append(stack.get(i));
}
stack.add(0, stringBuilder.toString());
}

public List<Boolean> getElements() {
return elements;
}

public String getBoring() {
return "Concatenate:"+getBoringOperand(elements);
}
}

public static class WriteInstruction extends Instruction {
private List<Boolean> elements;

public WriteInstruction(String operand) {
this.elements = parseOperand(operand);
}

public WriteInstruction(List<Boolean> elements) {
this.elements = elements;
}

public void execute(Interpreter state) {
List<String> stack = state.getStack();
for (int i = 0; i < elements.size(); i++) {
if (elements.get(i) && i < stack.size())
state.out().print(stack.get(i));
}
}

public List<Boolean> getElements() {
return elements;
}

public String getBoring() {
return "Write:"+getBoringOperand(elements);
}
}

public static class ReadInstruction extends Instruction {
public static final ReadInstruction READ_INSTRUCTION = new ReadInstruction();

public void execute(Interpreter state) {
try {
state.out().flush();
state.getStack().add(0, state.in().readLine());
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public String getBoring() {
return "Read:";
}
}

public static class DropHeadInstruction extends Instruction {
private List<Boolean> elements;

public DropHeadInstruction(String operand) {
this.elements = parseOperand(operand);
}

public DropHeadInstruction(List<Boolean> elements) {
this.elements = elements;
}

public void execute(Interpreter state) {
List<String> stack = state.getStack();
String head = null;
List<String> tails = new ArrayList<String>();
for (int i = 0; i < elements.size(); i++) {
if (elements.get(i) && i < stack.size()) {
if (head == null)
head = stack.get(i);
else if (stack.get(i).length() <= head.length())
tails.add(0, "");
else
tails.add(0, stack.get(i).substring(head.length()));
}
}
for (String tail : tails)
stack.add(0, tail);
}

public List<Boolean> getElements() {
return elements;
}

public String getBoring() {
return "DropHead:"+getBoringOperand(elements);
}
}

public static class DropMatchingHeadInstruction extends Instruction {
private List<Boolean> elements;

public DropMatchingHeadInstruction(String operand) {
this.elements = parseOperand(operand);
}

public DropMatchingHeadInstruction(List<Boolean> elements) {
this.elements = elements;
}

public void execute(Interpreter state) {
List<String> stack = state.getStack();
String head = null;
List<String> tails = new ArrayList<String>();
for (int i = 0; i < elements.size(); i++) {
if (elements.get(i) && i < stack.size()) {
if (head == null)
head = stack.get(i);
else
tails.add(0, dropMatchingHead(head, stack.get(i)));
}
}
for (String tail : tails)
stack.add(0, tail);
}

private String dropMatchingHead(String head, String string) {
for (int i = 0; i < head.length(); i++)
if (i >= string.length())
return "";
else if (head.charAt(i) != string.charAt(i))
return string.substring(i);
return string.substring(head.length());
}

public List<Boolean> getElements() {
return elements;
}

public String getBoring() {
return "DropMatchingHead:"+getBoringOperand(elements);
}
}

public static class LoopInstruction extends Instruction {
public static final LoopInstruction LOOP_INSTRUCTION = new LoopInstruction();

public void execute(Interpreter state) {
List<String> stack = state.getStack();
if (stack.size() > 0 && stack.get(0).length() > 0)
state.getFrame().loop();
}

public String getBoring() {
return "Loop:";
}
}
}

DoggerelCMUDict.java


import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.StringTokenizer;

public class DoggerelCMUDict {
public static void main(String[] args) throws Exception {
String dictFile = "cmudict.0.7a";
boolean debug = false;
int i = 0;
for (;;) {
if (i + 1 < args.length && args[i].equals("-d")) {
dictFile = args[i + 1];
i++;
} else if (args[i].equals("-debug")) {
debug = true;
} else {
break;
}
i++;
}
if (i + 1 != args.length) {
System.err.println("Usage: java DoggerelCMUDict [-d DICTIONARY-FILE] FILE.DOGGEREL");
} else {
Dict dict = new Dict(dictFile);
Verses verses = new Verses(dict, args[i]);
verses.setDebug(debug);
verses.run();
}
}

public static class Dict {
HashMap<String,String> dict = new HashMap<String,String>();

public Dict(String filename) {
try {
BufferedReader in = new BufferedReader(new FileReader(filename));
String line;
while ((line = in.readLine()) != null) {
if (line.length() < 2 || (line.charAt(0) == ';' && line.charAt(1) == ';'))
continue;
int space = line.indexOf(' ');
if (space < 0 || space + 1 >= line.length() || line.charAt(space + 1) != ' ')
continue;
dict.put(line.substring(0, space), line.substring(space + 1));
}
in.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}

public String lookup(String word) {
return dict.get(word.toUpperCase());
}
}

public static class Phoneme {
private String word;
private String phoneme;
private int stress;

public Phoneme(String word, String phoneme) {
this.word = word;
if (phoneme == null) {
this.phoneme = null;
stress = -1;
} else if (phoneme.endsWith("0")) {
this.phoneme = phoneme.substring(0, phoneme.length() - 1);
stress = 0;
} else if (phoneme.endsWith("1")) {
this.phoneme = phoneme.substring(0, phoneme.length() - 1);
stress = 1;
} else if (phoneme.endsWith("2")) {
this.phoneme = phoneme.substring(0, phoneme.length() - 1);
stress = 2;
} else {
this.phoneme = phoneme;
stress = -1;
}
}

public String toString() {
return (stress > 0 ? " '" : " ") + phoneme;
}

public String getWord() {
return word;
}

public String getPhoneme() {
return phoneme;
}

public int getStress() {
return stress;
}

public void destress() {
assert stress > 0;
stress = 0;
}
}

public static List<Phoneme> toPhonemes(Dict dict, String text) {
ArrayList<Phoneme> phonemes = new ArrayList<Phoneme>();
StringTokenizer st = new StringTokenizer(text, " \t\",.-;:?!");
while (st.hasMoreTokens()) {
String word = st.nextToken();
String lookup = dict.lookup(word);
if (lookup == null) {
phonemes.add(new Phoneme(word, null));
} else {
StringTokenizer st2 = new StringTokenizer(lookup);
while (st2.hasMoreTokens())
phonemes.add(new Phoneme(word, st2.nextToken()));
}
}
destress(phonemes);
return phonemes;
}

private static final String[][] DESTRESS_WORDS = {
{ "a", "an" },
{ "of", "to", "for", "in", "with", "on", "from" },
{ "is", "was", "were", "be" },
{ "that", "which" },
};

private static final HashMap<String,Integer> DESTRESS = new HashMap<String,Integer>();
static {
for (int i = 0; i < DESTRESS_WORDS.length; i++)
for (String s : DESTRESS_WORDS[i])
DESTRESS.put(s, i);
}

private static void destress(List<Phoneme> phonemes) {
Phoneme thisSyllable = null;
Phoneme nextSyllable = null;
for (Phoneme phoneme : phonemes) {
if (phoneme.getStress() < 0)
continue;
thisSyllable = nextSyllable;
nextSyllable = phoneme;
if (thisSyllable == null || thisSyllable.getStress() == 0 || nextSyllable.getStress() == 0)
continue;
Integer thisDestress = DESTRESS.get(thisSyllable.getWord().toLowerCase());
Integer nextDestress = DESTRESS.get(nextSyllable.getWord().toLowerCase());
if (thisDestress == null && nextDestress == null)
continue;
if (thisDestress == null)
nextSyllable.destress();
else if (nextDestress == null)
thisSyllable.destress();
else if (nextDestress.intValue() < thisDestress.intValue())
nextSyllable.destress();
else if (thisDestress.intValue() < nextDestress.intValue())
thisSyllable.destress();
}
}

// iambic (01): PushString or PushCharacter
// even number of feet: PushCharacter, odd number of feet, PushString
// if first line ends in nonstressed syllable (feminine/dactylic rhyme),
// ignore lines rhyming with first line to make acrostic

// trochaic (10): feet mod 4: 0:pop 1:float 2:read 3:write
// anapestic (001): feet mod 4: 0:call 1:concatenate 2:return 3:define
// dactylic (100): feet mod 3: 0:loop 1:dropmatchinghead 2:drophead

public static int getFeet(List<Phoneme> phonemes) {
int syllables = 0;
for (Phoneme phoneme : phonemes)
if (phoneme.getStress() >= 0)
syllables++;
switch (getMeter(phonemes)) {
case ANAPESTIC:
return syllables/3;
case DACTYLIC:
return (syllables+2)/3;
case TROCHAIC:
return (syllables+1)/2;
case IAMBIC:
default:
return syllables/2;
}
}

public static enum Meter {
IAMBIC, TROCHAIC, ANAPESTIC, DACTYLIC
}

public static Meter getMeter(List<Phoneme> phonemes) {
int meter = 0;
int count = 0;
for (Phoneme phoneme : phonemes) {
if (phoneme.getStress() < 0)
continue;
if (phoneme.getStress() > 0)
meter |= 1 << count;
count++;
if (count > 31)
break;
}
// cmudict tends to stress syllables that, in context, shouldn't be
switch (meter & 7) {
case 0: // 000
switch (meter & 24) {
case 0: // 00000
return Meter.IAMBIC;
case 8: // 00010
return Meter.IAMBIC;
case 16: // 00001
return Meter.TROCHAIC;
case 24: // 00011
return Meter.IAMBIC;
default:
assert false;
}
return Meter.IAMBIC;
case 1: // 100
return Meter.DACTYLIC;
case 2: // 010
return Meter.IAMBIC;
case 3: // 110
return Meter.IAMBIC;
case 4: // 001
return Meter.ANAPESTIC;
case 5: // 101
return Meter.TROCHAIC;
case 6: // 011
return Meter.IAMBIC;
case 7: // 111
return Meter.IAMBIC;
default:
assert false;
}
return Meter.IAMBIC;
}

public static boolean masculineRhyme(List<Phoneme> phonemes) {
for (int i = phonemes.size() - 1; i >= 0; i--)
switch (phonemes.get(i).getStress()) {
case 0:
return false;
case 1:
case 2:
return true;
default:
break;
}
return true;
}

public static boolean rhymes(List<Phoneme> line1, List<Phoneme> line2) {
int i1 = line1.size() - 1;
int i2 = line2.size() - 1;
while (i1 >= 0 && i2 >= 0) {
String phoneme1 = line1.get(i1).getPhoneme();
if (phoneme1 == null || !phoneme1.equals(line2.get(i2).getPhoneme()))
return false;
if (line1.get(i1).getStress() > 0)
return line2.get(i2).getStress() > 0;
i1--;
i2--;
}
return false;
}

public static String rhymeScheme(List<List<Phoneme>> lines) {
if (lines.size() == 0)
return "";
StringBuilder sb = new StringBuilder();
char nextRhyme = 'B';
sb.append('A');
for (int i = 1; i < lines.size(); i++) {
boolean hasRhyme = false;
for (int j = 0; j < i; j++)
if (rhymes(lines.get(i), lines.get(j))) {
hasRhyme = true;
sb.append(sb.charAt(j));
break;
}
if (!hasRhyme) {
sb.append(nextRhyme);
if (nextRhyme < 'z')
nextRhyme++;
}
}
return sb.toString();
}

public static String acrostic(List<String> stanza) {
StringBuilder sb = new StringBuilder();
for (String line : stanza) {
for (int i = 0; i < line.length(); i++)
if (Character.isLetter(line.charAt(i))) {
sb.append(line.charAt(i));
break;
}
}
return sb.toString();
}

public static String acrostic(List<String> stanza, String rhymeScheme) {
StringBuilder sb = new StringBuilder();
char first = rhymeScheme.length() > 0 ? rhymeScheme.charAt(0) : 'A';
for (int i = 1; i < stanza.size(); i++) {
if (i >= rhymeScheme.length() || rhymeScheme.charAt(i) != first) {
String line = stanza.get(i);
for (int j = 0; j < line.length(); j++)
if (Character.isLetter(line.charAt(j))) {
sb.append(line.charAt(j));
break;
}
}
}
return sb.toString();
}

public static class Verses {
private Dict dict;
private String filename;
private boolean debug = false;

public Verses(Dict dict, String filename) {
this.dict = dict;
this.filename = filename;
}

private boolean readStanza(BufferedReader in, List<String> stanza) throws IOException {
for (;;) {
String line = in.readLine();
if (line == null)
return stanza.size() > 0;
if (line.trim().length() == 0) {
if (stanza.size() > 0)
return true;
} else if (line.charAt(0) == ' ' || line.charAt(0) == '\t') {
stanza.add(line);
}
}
}

public void setDebug(boolean debug) {
this.debug = debug;
}

public void run() {
ArrayList<String> stanza = new ArrayList<String>();
ArrayList<List<Phoneme>> lines = new ArrayList<List<Phoneme>>();
ArrayList<Boring.Instruction> instructions = new ArrayList<Boring.Instruction>();
try {
BufferedReader in = new BufferedReader(new FileReader(filename));
while (readStanza(in, stanza)) {
for (String line : stanza)
lines.add(toPhonemes(dict, line));
if (debug)
System.out.println("stanza="+stanza+",lines="+lines+",masc=" + masculineRhyme(lines.get(0)) + ", meter=" + getMeter(lines.get(0)) + ", feet=" + getFeet(lines.get(0))+",scheme="+rhymeScheme(lines)+",acrostic1="+acrostic(stanza)+",acrostic2="+acrostic(stanza, rhymeScheme(lines)));
switch (getMeter(lines.get(0))) {
case IAMBIC:
instructions.add(pushInstruction(lines, stanza));
break;
case TROCHAIC:
instructions.add(popFloatReadWriteInstruction(lines));
break;
case ANAPESTIC:
instructions.add(callConcatenateReturnDefineInstruction(lines));
break;
case DACTYLIC:
instructions.add(loopDropInstruction(lines));
break;
}
stanza.clear();
lines.clear();
if (debug)
System.out.println(instructions.get(instructions.size()-1).getBoring());
}
in.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
if (debug)
System.out.println("instructions="+instructions);
new Boring.Interpreter(new BufferedReader(new InputStreamReader(System.in)), new PrintWriter(System.out), instructions).run();
}

private Boring.Instruction pushInstruction(List<List<Phoneme>> lines, List<String> stanza) {
String acrostic = masculineRhyme(lines.get(0)) ? acrostic(stanza) : acrostic(stanza, rhymeScheme(lines));
if (debug)
System.out.println("acrostic="+acrostic+",feet="+getFeet(lines.get(0))+",["+(getFeet(lines.get(0))%2 == 0 ? Boring.NamedCharacters.get(acrostic) : acrostic)+"]");
return new Boring.PushInstruction(getFeet(lines.get(0))%2 == 0 ? Boring.NamedCharacters.get(acrostic) : acrostic);
}

private Boring.Instruction popFloatReadWriteInstruction(List<List<Phoneme>> lines) {
switch (getFeet(lines.get(0))%4) {
case 0:
return new Boring.PopInstruction(rhymeScheme(lines));
case 1:
return new Boring.FloatInstruction(rhymeScheme(lines));
case 2:
return Boring.ReadInstruction.READ_INSTRUCTION;
case 3:
return new Boring.WriteInstruction(rhymeScheme(lines));
}
return null;
}

private Boring.Instruction callConcatenateReturnDefineInstruction(List<List<Phoneme>> lines) {
switch (getFeet(lines.get(0))%4) {
case 0:
return new Boring.CallInstruction(rhymeScheme(lines));
case 1:
return new Boring.ConcatenateInstruction(rhymeScheme(lines));
case 2:
return Boring.ReturnInstruction.RETURN_INSTRUCTION;
case 3:
return new Boring.DefineInstruction(rhymeScheme(lines));
}
return null;
}

private Boring.Instruction loopDropInstruction(List<List<Phoneme>> lines) {
switch (getFeet(lines.get(0))%3) {
case 0:
return Boring.LoopInstruction.LOOP_INSTRUCTION;
case 1:
return new Boring.DropMatchingHeadInstruction(rhymeScheme(lines));
case 2:
return new Boring.DropHeadInstruction(rhymeScheme(lines));
}
return null;
}
}
}

No comments:

Post a Comment