One of the first in-class examples for my programming language class is a program that generates numbers, following a grammar from Scott, Programming Language Pragmatics.
In #Python, this is 10 tiny functions, 49 lines including lots of PEP 8 whitespace:
import random
def evaluate(exp):
if isinstance(exp, str):
return exp
return exp()
def concatenate(*exps):
return ''.join([evaluate(e) for e in exps])
def choose(*exps):
return evaluate(random.choice(exps))
def star(exp):
result = ''
while random.randint(0, 1):
result += evaluate(exp)
return result
def number():
return choose(integer, real)
def integer():
return concatenate(digit, star(digit))
def real():
return choose(concatenate(integer, exponent), concatenate(decimal, choose(exponent, '')))
def decimal():
return concatenate(star(digit), choose(concatenate('.', digit), concatenate(digit, '.')), star(digit))
def exponent():
return concatenate(choose('e', 'E'), choose('+', '-', ''), integer)
def digit():
return choose('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')
print(number())
In #Java, largely because there's no way to define a new type that subsumes String, it's an interface, 5 classes, 11 functions, 6 instance variables, 6 enum values, a huge switch statement, and 125 lines.
import java.util.Random;
interface Symbol {
public String generate();
}
class Terminal implements Symbol {
String symbol;
public Terminal(String symbol) {
this.symbol = symbol;
}
public String generate() {
return symbol;
}
}
class Concatenation implements Symbol {
Symbol[] symbols;
public Concatenation(Symbol... symbols) {
this.symbols = symbols;
}
public String generate() {
String result = "";
for (Symbol s : symbols) {
result += s.generate();
}
return result;
}
}
class Choice implements Symbol {
static Random r = new Random();
Symbol[] symbols;
public Choice(Symbol... symbols) {
this.symbols = symbols;
}
public String generate() {
return symbols[r.nextInt(symbols.length)].generate();
}
}
class Star implements Symbol {
static Random r = new Random();
Symbol symbol;
public Star(Symbol symbol) {
this.symbol = symbol;
}
public String generate() {
String result = "";
while (r.nextBoolean()) {
result += symbol.generate();
}
return result;
}
}
public enum Nonterminal implements Symbol {
NUMBER, INTEGER, REAL, DECIMAL, EXPONENT, DIGIT;
public String generate() {
switch (this) {
case NUMBER:
return new Choice(INTEGER, REAL).generate();
case INTEGER:
return DIGIT.generate() + new Star(DIGIT).generate();
case REAL:
return new Choice(
new Concatenation(INTEGER, EXPONENT),
new Concatenation(DECIMAL, new Choice(EXPONENT, new Terminal("")))).generate();
case DECIMAL:
return new Concatenation(
new Star(DIGIT),
new Choice(
new Concatenation(new Terminal("."), DIGIT),
new Concatenation(DIGIT, new Terminal("."))),
new Star(DIGIT)
).generate();
case EXPONENT:
return new Concatenation(
new Choice(new Terminal("e"), new Terminal("E")),
new Choice(new Terminal("+"), new Terminal("-"), new Terminal("")),
INTEGER
).generate();
case DIGIT:
return new Choice(
new Terminal("0"),
new Terminal("1"),
new Terminal("2"),
new Terminal("3"),
new Terminal("4"),
new Terminal("5"),
new Terminal("6"),
new Terminal("7"),
new Terminal("8"),
new Terminal("9")
).generate();
}
return null; // We should never get here
}
public static void main(String[] args) {
System.out.println(NUMBER.generate());
}
}
I used to love Java, but this amount of boilerplate really gets in the way.