Alpha is an expression-oriented fragment of the Omega language. Alpha provides basic functionality for evaluating expressions involving arithmetic and logical operators. Alpha lacks names and therefore definitions. Alpha also lacks variables and therefore commands.
Assignment: Implement an interpreter for Alpha. You must submit a print out of your source code plus a print out of a sample Alpha session that is at least two pages long and begins by duplicating the sample session shown below.
User input is shown in boldface font:
type
"help" for commands
-> 3.14
Number: 3.14
-> false
Boolean: false
->(* 1 2 3 4)
Number: 24
-> (+ (+ 3 4) (+ 5 6))
Number: 18
-> (and (< 3 5) (= 42 (* 6 7)))
Boolean: true
-> Number
Type: Number
-> (+ 2 3
Syntax error
-> (+ 2 true)
Type Error:
-> quit
Save changes?(y/n): n
changes discarded
bye
For convenience, we take Alpha tokens to be the same as Omega tokens:
<Token> ::= <Number> | <Punctuation> | <Symbol>
<Number> ::= (+ | -)?<Digit>+(.<Digit>+)?
<Punctuation> ::= . | ; | ( | ) | [ | ] | { | }
<Symbol> ::= <Identifier> | <Operator>
<Identifier> ::= <Letter>(<Letter> |
<Digit>)*
<Operator> ::= + | * | - | / | = | <
| >
<Letter> ::= [A-Z] | [a-z]
<Digit> ::= [0-9]
Expressions are the only type of Alpha phrase:
<Phrase> ::= <Expression>
Literals and function calls are the only types of expressions:
<Expression> ::= <Literal> | <FunCall>
<Literal> ::= <Boole Literal> | <Number
Literal> | <Type Literal>
<Boole Literal> ::= true | false
<Number Literal> ::= <Number>
<Type Literal> ::= Number | Boolean | TypeError | Type
<FunCall> ::= (<Symbol> <Expression>+)
<Symbol> ::= <ArithmeticOp> | <ComparisonOp> |
<LogicOp>
<ArithmeticOp> ::= + | * | - | /
<ComparisonOp> ::= < | > | =
<LogicOp> ::= and | or | not
Alpha consists of two packages. Note the one-way dependency:
In the phrases package you must define the following classes:
Here's a working base class for the Phrase hierarchy:
abstract class Phrase {
// Omega Tokens:
public static final String SYMBOL =
Lex.join(Lex.IDENTIFIER,
Lex.OPERATOR);
public static final String NUMBER =
Lex.NUMBER;
// PATTERN ::= SYMBOL | OPERATOR |
PUNCTUATION | NUMBER
public static String PATTERN = SYMBOL;
static {
PATTERN = Lex.join(PATTERN,
Lex.PUNCTUATION);
PATTERN = Lex.join(PATTERN,
Lex.NUMBER);
}
// top level parser
public static Phrase
parse(List<String> tokens) throws AppError {
if (Expression.isNext(tokens)) return
Expression.parse(tokens);
if (Command.isNext(tokens)) return
Command.parse(tokens);
if (Definition.isNext(tokens)) return
Definition.parse(tokens);
throw new AppError("Unrecognized
phrase in Phrase.parse");
}
// top level evaluator
abstract public Value eval(Environment
env)throws AppError;
}
Alpha values are numbers, Booleans, types, frames, and ten pre-defined functions:
Value = Number + Boolean + Frame + Function
Function = {+, *, -, /, <, >, =, and, or, not}
PrimitiveValue and CompositeValue are purely conceptual classes. You don't need to implement them. However, you must implement the other classes:
The composite values include things like objects, composite types, and functions:
Alpha values implement an empty Value interface:
interface Value extends Serializable { }
For example, the Number class encapsulates a double:
class Number implements Value, Comparable {
private static final long
serialVersionUID = 1L;
private double value;
public Number add(Number other) {...}
public Number times(Number other) {...}
public Number sub(Number other) {...}
public Number div(Number other) {...}
public String toString() { return
"" + value; }
public boolean equals(Object other) {
if (other == null) return false;
Class c = other.getClass();
if (!c.equals(getClass())) return
false;
Number b = (Number)other;
return b.value == value;
}
public int hashCode() { return
toString().hashCode(); }
public int compareTo(Object arg0) {
Number arg = (Number)arg0;
return (int)(value - arg.value);
}
}
Functions must implement the Abstract interface:
interface Abstract extends Value {
// = result of applying this to args in
env
Value apply(List<Value> args,
Environment env) throws AppError;
}
Note: A primitive function is a function that doesn't use its Environment parameter, env. Presumably, such functions are so simple that they don't call other functions. These functions might be directly implemented in the ALU circuits on board the processor. All ten Alpha functions are primitive.
A frame is a special kind of value. It is a table of associations between symbols and values. For now we can take symbols to be the same as Java Strings:
class Symbol extends String { } // for now
An environment, also called a Context, is a frame together with (possibly null) references to a defining environment and a calling environment:
Alpha uses a single environment called the global environment. It contains associations between the names of all of the ten Alpha operator symbols and the corresponding function objects.
phrase.eval(environment) = value
Of course the algorithm for eval varies polymorphically in the different subclasses of Phrase. For example, the value of a literal is itself.
Note: there are currently no phrases that produce frames as values. Frames are strictly internal use values at this stage.
The Omega Console extends the CUI Framework:
public class OmegaConsole extends Console {
public OmegaConsole(Serializable model)
{
super(model);
}
protected Environment getModel() {
return (Environment)model; }
public OmegaConsole() { this(new
Environment()); }
protected String execute(String cmmd)
throws AppError {
List<String> tokens =
Lex.scan(Phrase.PATTERN, cmmd);
Phrase phrase =
Phrase.parse(tokens);
Environment
env = getModel();
Value value = phrase.eval(env);
unsavedChanges = true;
return value.toString();
}
public static void main(String[] args)
{
Environment model = new
Environment();
Console console = new
OmegaConsole(model);
console.controlLoop();
}
}