Flores
12th
April 2007
Just over a year ago I wrote the post entitled Yet Another Programming Language. That particular project hasn’t quite been finished yet. However, I have written a very small execution engine in Java named Flores.
Flores has the following features:
- One data type - String. Well, two if you count the singleton
null
. - The
while
operator, complete withcontinue
andbreak
. - The
if
operator, complete withelse
. - The
=
operator. - All functions are defined externally in Java as static methods. These have to be added to a Flores engine object at Java runtime by overriding the abstract static method
callFunction
. - No functions by default. The developer needs to add them.
This may look like an odd features list, but there is method in the madness. I have often needed a small scripting lanaguage with one or more of the following features:
- Security. I want total control over a script’s access to the outside world. With Flores, if I don’t explicitly add a function, it’s not there.
- Maintainability. At 639 lines, including comments, there can only be so many bugs.
- Simplicity. I don’t want tail recursion or co-routines. When I say simple, I really mean it.
- Licensing. I need a license that’s business friendly, and for that, I’ve always found the zlib/libpng License reliable.
Flores is available below. If you have some of the same criteria that I do, hopefully you’ll find it useful.
/*
* Copyright (c) 2007 John Scott
*
* This software is provided 'as-is', without any express or implied warranty.
* In no event will the authors be held liable for any damages arising from
* the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software in
* a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source distribution.
*/
package com.jjrscott.lang;
import java.util.ArrayList;
import java.util.HashMap;
import java.lang.Exception;
public abstract class Flores {
private static class Cookie {
public String program_;
public int index_;
public Cookie(String program) {
program_ = program;
index_ = 0;
}
}
public static class Argument {
private String key_;
private String value_;
Argument(String key, String value) {
key_ = key;
value_ = value;
}
public String getKey() {
return key_;
}
public String getValue() {
return value_;
}
}
public static class Parameter {
public String key_;
public Unit unit_;
Parameter(String key, Unit unit) {
key_ = key;
unit_ = unit;
}
public String getKey() {
return key_;
}
public Unit getUnit() {
return unit_;
}
}
private static class Variables {
private HashMap variables_;
public Variables() {
variables_ = new HashMap();
}
public void setVariable(String variable, String value) {
variables_.put(variable, value);
}
public String getVariable(String variable) {
return (String) variables_.get(variable);
}
private boolean hasVariable(String variable) {
return variables_.containsKey(variable);
}
}
public static class ParseException extends Exception {
Cookie cookie_;
public ParseException(Cookie cookie, String message) {
super(message);
cookie_ = cookie;
}
public String getMessage() {
return super.getMessage() + ": " + cookie_.program_.substring(cookie_.index_) + " (" + cookie_.index_ + ")";
}
}
public static class RuntimeException extends Exception {
public RuntimeException(Flores engine, String message) {
super(message);
}
public RuntimeException(Flores engine, String message, Throwable cause) {
super(message, cause);
}
}
private static class ReturnJump extends RuntimeException {
public ReturnJump(Flores engine, String result) {
super(engine, result);
}
}
private static class ContinueJump extends RuntimeException {
public ContinueJump(Flores engine) {
super(engine, null);
}
}
private static class BreakJump extends RuntimeException {
BreakJump(Flores engine) {
super(engine, null);
}
}
private static interface Unit {
public String execute(Flores engine, Variables variables) throws Throwable;
}
private static class If implements Unit {
public Unit expression_;
public Unit yes_;
public Unit no_;
public String execute(Flores engine, Variables variables) throws Throwable {
if (Flores.isTrue(expression_.execute(engine, variables)))
return yes_.execute(engine, variables);
else if (no_ != null)
return no_.execute(engine, variables);
else
return null;
}
}
private static class Return implements Unit {
public Unit expression_;
public String execute(Flores engine, Variables variables) throws Throwable {
throw new ReturnJump(engine, expression_.execute(engine, variables));
}
}
private static class Break implements Unit {
public String execute(Flores engine, Variables variables) throws Throwable {
throw new BreakJump(engine);
}
}
private static class Continue implements Unit {
public String execute(Flores engine, Variables variables) throws Throwable {
throw new ContinueJump(engine);
}
}
private static class While implements Unit {
public Unit expression_;
public Unit yes_;
public String execute(Flores engine, Variables variables) throws Throwable {
while (Flores.isTrue(expression_.execute(engine, variables)))
try {
yes_.execute(engine, variables);
}
catch (BreakJump e) {
break;
} catch (ContinueJump e) {
continue;
}
return null;
}
}
private static class Block implements Unit {
ArrayList block_;
public Block() {
block_ = new ArrayList();
}
public String execute(Flores engine, Variables variables) throws Throwable {
for (int index = 0; index < block_.size(); index++)
((Unit) block_.get(index)).execute(engine, variables);
return null;
}
}
private static class Call implements Unit {
public ArrayList expressions_;
public String function_;
public Call() {
expressions_ = new ArrayList();
}
public String execute(Flores engine, Variables variables) throws Throwable {
Argument[] results = new Argument[expressions_.size()];
for (int index = 0; index < expressions_.size(); index++) {
Parameter parameter = (Parameter) expressions_.get(index);
results[index] = new Argument(parameter.getKey(), parameter.getUnit().execute(engine, variables));
}
return engine.callFunction(function_, results);
}
}
private static class Quote implements Unit {
private String string_;
public Quote(String string) {
string_ = string;
}
public String execute(Flores engine, Variables variables) throws Throwable {
return string_;
}
}
private static class Null implements Unit {
public Null() {}
public String execute(Flores engine, Variables variables) throws Throwable {
return null;
}
}
private static class Variable implements Unit {
private String variable_;
public Variable(String variable) {
variable_ = variable;
}
public String execute(Flores engine, Variables variables) throws Throwable {
if (variables.hasVariable(variable_))
return variables.getVariable(variable_);
else
throw new RuntimeException(engine, "Variable " + variable_ + " does not exist");
}
}
private static class Assign implements Unit {
public String variable_;
public Unit expression_;
public String execute(Flores engine, Variables variables) throws Throwable {
variables.setVariable(variable_, expression_.execute(engine, variables));
return null;
}
}
public Flores() {}
public abstract String callFunction(String name, Argument[] arguments) throws Throwable;
public String execute(String program) throws ParseException, RuntimeException, Throwable {
Cookie cookie = new Cookie(program);
Unit unit = parse(cookie);
if (unit == null)
throw new RuntimeException(this, "No program to run");
Variables variables = new Variables();
try {
return unit.execute(this, variables);
} catch (ReturnJump e) {
return e.getMessage();
}
}
private static Unit parse(Cookie cookie) throws ParseException {
Unit unit = parse_block(cookie);
if (skipWhitespace(cookie))
throw new ParseException(cookie, "Program was not fully parsed");
return unit;
}
private static Unit parse_block(Cookie cookie) throws ParseException {
Block block = new Block();
Unit found = null;
while ((found = parse_statement(cookie)) != null)
block.block_.add(found);
if (block.block_.size() > 0)
return block;
else
return null;
}
private static Unit parse_statement(Cookie cookie) throws ParseException {
Unit found;
found = parse_if(cookie);
if (found != null)
return found;
found = parse_while(cookie);
if (found != null)
return found;
found = parse_return(cookie);
if (found != null)
return found;
found = parse_break(cookie);
if (found != null)
return found;
found = parse_continue(cookie);
if (found != null)
return found;
found = parse_assign(cookie);
return found;
}
private static Unit parse_if(Cookie cookie) throws ParseException {
If unit = new If();
if (!bnf_alpha_sw(cookie, "if"))
return null;
if (!bnf_alpha_sw(cookie, "("))
throw new ParseException(cookie, "Expected (");
unit.expression_ = parse_expression(cookie);
if (unit.expression_ == null)
throw new ParseException(cookie, "We wanted a expression here");
if (!bnf_alpha_sw(cookie, ")"))
throw new ParseException(cookie, "Expected )");
if (!bnf_alpha_sw(cookie, "{"))
throw new ParseException(cookie, "Expected {");
unit.yes_ = parse_block(cookie);
if (unit.yes_ == null)
throw new ParseException(cookie, "We wanted a block here");
if (!bnf_alpha_sw(cookie, "}"))
throw new ParseException(cookie, "Expected }");
if (!bnf_alpha_sw(cookie, "else"))
return unit;
unit.no_ = parse_if(cookie);
if (unit.no_ != null)
return unit;
if (!bnf_alpha_sw(cookie, "{"))
throw new ParseException(cookie, "Expected {");
unit.no_ = parse_block(cookie);
if (unit.no_ == null)
throw new ParseException(cookie, "We wanted a block here");
if (!bnf_alpha_sw(cookie, "}"))
throw new ParseException(cookie, "Expected }");
return unit;
}
private static Unit parse_while(Cookie cookie) throws ParseException {
While unit = new While();
if (!bnf_alpha_sw(cookie, "while"))
return null;
if (!bnf_alpha_sw(cookie, "("))
throw new ParseException(cookie, "Expected (");
unit.expression_ = parse_expression(cookie);
if (unit.expression_ == null)
throw new ParseException(cookie, "We wanted a expression here");
if (!bnf_alpha_sw(cookie, ")"))
throw new ParseException(cookie, "Expected )");
if (!bnf_alpha_sw(cookie, "{"))
throw new ParseException(cookie, "Expected {");
unit.yes_ = parse_block(cookie);
if (unit.yes_ == null)
throw new ParseException(cookie, "We wanted a block here");
if (!bnf_alpha_sw(cookie, "}"))
throw new ParseException(cookie, "Expected }");
return unit;
}
private static Unit parse_return(Cookie cookie) throws ParseException {
Return unit = new Return();
if (!bnf_alpha_sw(cookie, "return"))
return null;
unit.expression_ = parse_expression(cookie);
if (unit.expression_ == null)
throw new ParseException(cookie, "We wanted a expression here");
if (!bnf_alpha_sw(cookie, ";"))
throw new ParseException(cookie, "Expected ;");
return unit;
}
private static Unit parse_break(Cookie cookie) throws ParseException {
Break unit = new Break();
if (!bnf_alpha_sw(cookie, "break"))
return null;
if (!bnf_alpha_sw(cookie, ";"))
throw new ParseException(cookie, "Expected ;");
return unit;
}
private static Unit parse_continue(Cookie cookie) throws ParseException {
Continue unit = new Continue();
if (!bnf_alpha_sw(cookie, "continue"))
return null;
if (!bnf_alpha_sw(cookie, ";"))
throw new ParseException(cookie, "Expected ;");
return unit;
}
private static Unit parse_quote(Cookie cookie) throws ParseException {
if (!bnf_alpha_sw(cookie, "\""))
return null;
StringBuffer string = new StringBuffer();
int start = cookie.index_;
while (cookie.index_ < cookie.program_.length() && cookie.program_.charAt(cookie.index_) != '"') {
if (bnf_alpha(cookie, "\\n")) {
string.append("\n");
} else if (bnf_alpha(cookie, "\\\\")) {
string.append("\\");
} else if (bnf_alpha(cookie, "\\\"")) {
string.append("\"");
} else {
string.append(cookie.program_.substring(cookie.index_, cookie.index_ + 1));
cookie.index_ += 1;
}
}
if (!bnf_alpha_sw(cookie, "\""))
throw new ParseException(cookie, "Expected \"");
else
return new Quote(string.toString());
}
private static Unit parse_expression(Cookie cookie) throws ParseException {
if (!skipWhitespace(cookie))
return null;
Unit quote = parse_quote(cookie);
if (quote != null)
return quote;
String name = bnf_name_sw(cookie);
if (name.equals("null"))
return new Null();
Unit call = parse_call(cookie, name);
if (call != null)
return call;
return new Variable(name);
}
private static Unit parse_call(Cookie cookie, String name) throws ParseException {
if (!bnf_alpha_sw(cookie, "("))
return null;
Call call = new Call();
call.function_ = name;
if (bnf_alpha_sw(cookie, ")"))
return call;
do {
String key = bnf_name_sw(cookie);
Unit expression = null;
if (key != null && !bnf_alpha_sw(cookie, ":")) {
if (key.equals("null"))
expression = new Null();
if (expression == null)
expression = parse_call(cookie, key);
if (expression == null)
expression = new Variable(key);
key = null;
}
if (expression == null)
expression = parse_expression(cookie);
if (expression != null)
call.expressions_.add(new Parameter(key, expression));
else
throw new ParseException(cookie, "Expected an expression");
} while (bnf_alpha_sw(cookie, ","));
if (!bnf_alpha_sw(cookie, ")"))
throw new ParseException(cookie, "Expected )");
return call;
}
private static Unit parse_assign(Cookie cookie) throws ParseException {
if (!skipWhitespace(cookie))
return null;
String name = bnf_name_sw(cookie);
if (name == null)
return null;
Unit call = parse_call(cookie, name);
if (call != null) {
if (!bnf_alpha_sw(cookie, ";"))
throw new ParseException(cookie, "Expected ;");
return call;
} else if (bnf_alpha_sw(cookie, "=")) {
Assign assign = new Assign();
assign.variable_ = name;
assign.expression_ = parse_expression(cookie);
if (assign.expression_ == null)
throw new ParseException(cookie, "Expexted an expression");
if (!bnf_alpha_sw(cookie, ";"))
throw new ParseException(cookie, "Expected ;");
return assign;
} else {
throw new ParseException(cookie, "Expected = or a (");
}
}
private static String bnf_name_sw(Cookie cookie) {
skipWhitespace(cookie);
int start = cookie.index_;
while (cookie.index_ < cookie.program_.length() && Character.isLetterOrDigit(cookie.program_.charAt(cookie.index_)))
cookie.index_++;
if (start == cookie.index_)
return null;
return cookie.program_.substring(start, cookie.index_);
}
private static boolean skipWhitespace(Cookie cookie) {
while (cookie.index_ < cookie.program_.length() && Character.isWhitespace(cookie.program_.charAt(cookie.index_)))
cookie.index_++;
if (bnf_alpha(cookie, "/*")) {
while (!bnf_alpha(cookie, "*/") && cookie.index_ < cookie.program_.length())
cookie.index_++;
while (cookie.index_ < cookie.program_.length() && Character.isWhitespace(cookie.program_.charAt(cookie.index_)))
cookie.index_++;
}
if (cookie.index_ < cookie.program_.length())
return true;
else
return false;
}
private static boolean bnf_alpha_sw(Cookie cookie, String constant) {
skipWhitespace(cookie);
return bnf_alpha(cookie, constant);
}
private static boolean bnf_alpha(Cookie cookie, String constant) {
if (cookie.index_ + constant.length() > cookie.program_.length())
return false;
if (cookie.program_.startsWith(constant, cookie.index_)) {
cookie.index_ += constant.length();
return true;
} else {
return false;
}
}
private static boolean isTrue(String string) {
return string != null;
}
}