Each function is implemented as a class in C++. The reasoning for this is flexibility at the initial implementation stage, although no doubt it will cause some problems later on when it comes to JIT and compiling - although by the time we get to be self-hosting, the problem should have gone away :
The flexibility does allow for some nice features such as crossing the compile/runtime boundry - function classes (’;cause that makes sense, honest :)) can provide inline dom and byte code generation so that some functions don’t need to be called at all runtime, and can instead have the CodeDom and/or byte code injected at compile time instead.
It also means that far stricter error cheking can be done at compile time on built in functions (assuming we can’t override them and namespace resolution is static), so we could check this before anything is executed:
// This code would not compile because the 'Z' flag is // invalid to fopen, and can be detected at compile time. if (($fp = fopen($file, 'Z')) === false) { } // However, this code wouldn't error if optimization didn't rewrite this code // to the same as above (which it should if optimization is enabled) $z = 'Z'; if (($fp = fopen($file, $z)) === false) { }
And in terms of functions emitting their own ByteCode, it means that inline optimizations can still be performed with internal functions:
struct add : public InternalFunction { /* * This member fucntion emits the code dom to call this function. If it is not * implemented, InternalFunction::EmitCodeDom() will emit the nodes to call the * C++ implementation of the function. */ void EmitCodeDom(CompilerContext & ctx, CodeExpression & target, std::list<CodeExpressionVariant> args) { /* * possiblyNumeric: would return false when we are sure the argument is something not * convertable to an integer - which would depend on the context it is running in. In PHP * compatibility mode, this could be pretty much anything; however in Phix strict mode, * this would be either a untyped variable, int, float, bool, or object that is castable * to a number (__toNumeric()). A string may be cast to a numeric by casting, not in any * other way - however it is not explicit. * * $moo = 1; * string $cow = '1'; * * add($moo, $cow); // would fail compile as we are sure $cow is a string * add($moo, (string)$cow); // would compile as we cast to string first * */ if (args.size() != 2 || ctx.possiblyNumeric(args[0].what()) == false || ctx.possiblyNumeric(args[1]) == false) { throw std::runtime_error("add() requires exactly 2 arguments that must be convertible to numbers"); } /* * Here we re-write the function to just execute OpAdd. This will be optimized * by the compiler to the best quality opcodes of available - in the case of a binary * expression, we will attempt to calculate the answer at compile time rather than runtime# * so add(1, 5) will actually just push 6 onto the stack without any other call. */ CodeBinaryExpression expr; expr.op = OpAdd; expr.left = args[0]; expr.right = args[1]; target = expr; } };