Jonah Scheinerman

Phoenix: Infix-Oriented Language

About

Phoenix is an interpreted, procedural proramming language written in Java SE 1.5. Phoenix is similar in style to Python and C and features a full complement of operators, and a large library of functions. Phoenix is designed to have many of the useful functions seen in modern programming languages, while making several key upgrades in syntax and readability. Since Phoenix is built in Java, using it from within a Java application is a breeze. Here is brief overview of the features of Phoenix:

  • Excellent interoperability with Java. Phoenix programs can be run from Java in two lines of code. Java applications can insert their own functions into Phoenix for use at runtime, so that the Phoenix program can send information back to the Java application.
  • Functions with left-hand and right-hand arguments. In Phoenix, one can define functions that take arguments both before and after the name of the function. This improves upoon redability. Unlike most programming languages in which functions take the form function(arg1, arg2, ... argN), Phoenix programs can have functions which take any of the following forms:
    • function (arg1, arg2, ... argN)
    • (arg1, arg2, ... argN) function
    • (arg1, arg2, ... argM) function (argM+1,argM+2, ... argN)
  • Better designed operators. Phoenix uses slightly different operators and adds a few new ones to the standard mix of operators. For example, Phoenix has a rounding operator which rounds one operand to the nearest multiple of the second. Also, the carrot, ^, operator is now exponentiation (not bitwise xor) and a new o-plus operator, (+), serves the function of a logical xor.
  • Flexible break statements. In Phoenix, when using a break statement to break out of a loop or switch case statement, one can append a number to it (e.g. break 3), to indicate how many loops or case statements should be broken out of . To break out of all existing loops, simply use the break all statement.

Download

You can download Phoenix v.0.9.0 here. This includes the Phoenix user manual. The Phoenix APIs are available here (coming soonish?). If you have a phoenix source file, you can run it from the command line as follows:

% java -jar Phoenix.jar path/to/source/file.phx

Or, to enter the interactive interface, simply enter:

% java -jar Phoenix.jar

Usage

Below we give a brief overview of running the Phoenix environment within a Java application. One of the advantages of using Phoenix is that it is quite easy to run Phoenix code with in a Java application, thus making it a great scripting language for Java applications. For information on Phoenix language syntax take a look at the manual included in the download.

Suppose we have an application for graphing functions. Instead of having to write all of the code to parse and interpret algebraic expressions, we are going to use Phoenix to do the parsing for us. First, we want our users to have access to some mathematical functions when graphing, so we will define a Phoenix module. A Phoenix module is a class containing a set of functions that will be inserted into the Phoenix environment at runtime. Here's an example:

import net.scheinerman.phoenix.interpreter.functions.BuiltInFunction;
import net.scheinerman.phoenix.interpreter.variables.NumberVariable;
import net.scheinerman.phoenix.library.AbstractModule;

public class GrapherModule implements AbstractModule {
  /* Define a built in function sin(x). */
  private static class Sin extends BuiltInFunction {
    public Sin() {
      super("sin", // The name of the function
            "num", // The return type of the function
            new String[] {},      // The types of the left-hand arguments (none)
            new String[] {"num"}, // The types of the right-hand arguments
            new String[] {},      // The names of the left-hand arguments
            new String[] {"x"});  // The names of the right-hand arguments
    }

    public void run(String[] left, String[] right) {
      Double d = Double.parseDouble(right[0]);     // Get the first right hand argument
      retValue = new NumberVariable(Math.sqrt(d)); // Save the return value
    }
  }

  // More functions defined here...

  public GrapherModule() {
    super("")
    functions.add(new Sin());
    // Add other functions here...
  }
}

Since our application is interpreting code that is putting in on the fly, we want to interpret this code with an interactive interpreter. We can set one up in the following way:

InteractivePhoenixEnvironment environment = new InteractivePhoenixEnvironment();
environment.addModule(new GrapherModule()); // Insert the functions that we want the user to have                                             // into the enviroment.
// Initialize the x and y variables in the Phoenix environment...
environment.runCode("global num x = 0");
environment.runCode("global num y = 0";

Note: We could have created NumberVariable objects for x and y and then directly inserted these into Phoenix environment's variable allocation table. However, its easier to have the environment run some Phoenix code which accomplishes the same thing.

Next, we want to interpret a piece of user given code for various values of x. We do this in the following fashion:

public double getFunctionValue(String code, double xValue) throws PhoenixRuntimeException {
  environment.runCode("x = " + xValue); // Set the value of x
  environment.runCode("y = " + code);   // If the code has a problem this will throw an exception
  NumberVariable yVariable = (NumberVariable)environment.getVAT().getLast().get("y");
  return yVariable.value();
}

Lets pick apart the second to last line of the function:
    NumberVariable yVariable = (NumberVariable)environment.getVAT().getLast().get("y");
This is a complex one so lets look at whats really going on here.

The first thing to note is that Phoenix stores variables in something I called a variable allocation table (VAT). We want to retrieve the value of the y Phoenix variable, so we have to go to where the variables are stored, hence environment.getVAT(). The VAT is stored as a list of maps of identifiers to variables. Specifically, the type definition for the VAT is: LinkdList< HashMap< String, Variable > >. Each element of the list is a map containing the variables for a specific scope. The earlier in the list, the more inner the scope and the later in the list the more outer the scope. Therefore, the last element of that list (environment.getVAT() .getLast()) contains the variables in the global scope (the outermost possible variable scope). The, within the global scope, we extract the variable by its name. Since we know this variable is a number, we can safely cast it to a NumberVariable. We know have successfully gotten the variable we are looking for!

If you'd like to see this in action, I've taken the liberty of putting together a very simple graphing application. If you download it and unzip the folder you'll find two things in the folder. Grapher.jar contains the application. To run it, either double click or run the command java -jar Grapher.jar. Also in the folder is the folder src. This contains the source code for the grapher, so take a look. It should look pretty similar to the example above.