/*
 *  Forth.java
 *  Copyright (C) 2002  Frank Buß (fb@frank-buss.de)
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You can get the GNU General Public License at
 *  http://www.gnu.org/licenses/gpl.html
 */

import java.awt.*;
import java.applet.*;
import java.util.*;
import java.awt.event.*;

public class Forth extends Applet implements ActionListener {
	private TextArea outputArea;
	private TextField inputField;

	private Hashtable dictionary = new Hashtable();
	private Stack stack = new Stack();
	private Stack loopPC = new Stack();
	private Stack loopIndex = new Stack();
	private Stack loopEnd = new Stack();
	private int memory[] = new int[100000];
	private int memoryPointer;

	/**
	 * Create GUI.
	 */
	public void init() {
		setLayout(new BorderLayout());
		setBackground(Color.white);

		outputArea = new TextArea();
		outputArea.setEditable(false);
		add(BorderLayout.CENTER, outputArea);

		inputField = new TextField();
		inputField.addActionListener(this);
		add(BorderLayout.SOUTH, inputField);
	}

	private void doOutput(String output) {
		outputArea.append(output);
	}

	private void appendLine(String output) {
		doOutput(output + "\n");
	}

	private int popInt() {
		return ((Integer) stack.pop()).intValue();
	}

	private void pushInt(int value) {
		stack.push(new Integer(value));
	}

	private void executeToken(Vector tokens) throws Exception {
		// for compilation mode
		boolean compilationMode = false;
		Vector currentTokens = null;
		String currentNewName = null;

		// loop through all tokens
		for (int pc = 0; pc < tokens.size(); pc++) {
			String token = (String) tokens.elementAt(pc);
			if (compilationMode) {
				if (token.equals(";")) {
					dictionary.put(currentNewName, currentTokens);
					compilationMode = false;
				} else {
					currentTokens.add(token);
				}
				continue;
			}

			// search token in dictionary
			Object entry = dictionary.get(token);
			if (entry == null) {
				// try built-in words
				if (token.equals(".")) {
					doOutput(((Integer) stack.pop()).toString() + " ");
				} else if (token.equals("constant")) {
					dictionary.put((String) tokens.elementAt(++pc), (Integer) stack.pop());
				} else if (token.equals("create")) {
					dictionary.put((String) tokens.elementAt(++pc), new Integer(memoryPointer));
				} else if (token.equals("+")) {
					pushInt(popInt() + popInt());
				} else if (token.equals("*")) {
					pushInt(popInt() * popInt());
				} else if (token.equals("/")) {
					int v1 = popInt();
					int v2 = popInt();
					pushInt(v2 / v1);
				} else if (token.equals("-")) {
					int v1 = popInt();
					int v2 = popInt();
					pushInt(v2 - v1);
				} else if (token.equals("cells")) {
					// ignore, because the cell size is 1
				} else if (token.equals("allot")) {
					memoryPointer += popInt();
				} else if (token.equals("true")) {
					pushInt(-1);
				} else if (token.equals("false")) {
					pushInt(0);
				} else if (token.equals("dup")) {
					int v = popInt();
					pushInt(v);
					pushInt(v);
				} else if (token.equals("swap")) {
					int v1 = popInt();
					int v2 = popInt();
					pushInt(v1);
					pushInt(v2);
				} else if (token.equals("drop")) {
					popInt();
				} else if (token.equals("!")) {
					int adr = popInt();
					int v = popInt();
					memory[adr] = v;
				} else if (token.equals("@")) {
					int adr = popInt();
					pushInt(memory[adr]);
				} else if (token.equals("cr")) {
					appendLine("");
				} else if (token.equals("if")) {
					if (popInt() == 0) {
						while (!token.equals("then"))
							token = (String) tokens.elementAt(++pc);
					}
				} else if (token.equals("then")) {
					// ignore, because it is only a marker for 'if'
				} else if (token.equals("do")) {
					int start = popInt();
					int end = popInt();
					loopPC.push(new Integer(pc));
					loopIndex.push(new Integer(start));
					loopEnd.push(new Integer(end));
				} else if (token.equals("loop")) {
					int index = ((Integer) loopIndex.pop()).intValue();
					int end = ((Integer) loopEnd.peek()).intValue();
					if (++index < end) {
						loopIndex.push(new Integer(index));
						pc = ((Integer) loopPC.peek()).intValue();
					} else {
						loopEnd.pop();
						loopPC.pop();
					}
				} else if (token.equals("+loop")) {
					int index = ((Integer) loopIndex.pop()).intValue();
					int end = ((Integer) loopEnd.peek()).intValue();
					int ofs = popInt();
					index += ofs;
					if (index < end) {
						loopIndex.push(new Integer(index));
						pc = ((Integer) loopPC.peek()).intValue();
					} else {
						loopEnd.pop();
						loopPC.pop();
					}
				} else if (token.equals("i")) {
					stack.push(loopIndex.peek());
				} else if (token.equals("j")) {
					stack.push(loopIndex.elementAt(loopIndex.size() - 2));
				} else if (token.equals(":")) {
					currentNewName = (String) tokens.elementAt(++pc);
					currentTokens = new Vector();
					compilationMode = true;
				} else {
					// try number parsing
					try {
						pushInt(Integer.parseInt(token));
					} catch (NumberFormatException n) {
						throw new Exception(token + " is undefined");
					}
				}
			} else {
				// entry from dictionary
				if (entry instanceof Integer) {
					stack.push(entry);
				} else if (entry instanceof Vector) {
					executeToken((Vector) entry);
				}
			}
		}
	}

	private void parse(String input) throws Exception {
		StringTokenizer tokenizer = new StringTokenizer(input);
		Vector tokens = new Vector();
		while (tokenizer.hasMoreTokens())
			tokens.addElement(tokenizer.nextToken());
		executeToken(tokens);
	}

	public void actionPerformed(ActionEvent e) {
		try {
			parse(inputField.getText());
			appendLine("ok");
			inputField.setText("");
		} catch (Exception x) {
			appendLine("Exception: " + x.toString());
			x.printStackTrace();
		}
	}
}
