<?php
final class NodeList
{
private static $factories = array();
public static function registerFactory($factory)
{
if(is_object($factory))
{
foreach(self::$factories as $f)
{
if($f == $factory)
return false; // Factory already registred
}
self::$factories[] = $factory;
return true;
}
if(is_array($factory))
{
foreach($factory as $f)
self::registerFactory($f);
}
}
public static function enumFactoryGroups()
{
$ret = array();
foreach(self::$factories as $factory)
{
if(!in_array($factory->getGroup(), $ret))
$ret[] = $factory->getGroup();
}
return $ret;
}
public static function enumFactories($group)
{
$ret = array();
foreach(self::$factories as $factory)
{
if($factory->getGroup() == $group)
$ret[] = $factory;
}
return $ret;
}
public static function getFactoryCount()
{
return sizeof(self::$factories);
}
public static function findFactory($name)
{
foreach(self::$factories as $f)
{
if($f->getName() == $name)
return $f;
}
return null;
}
}
class NodeFactory
{
public function getName()
{
return "BaseNode";
}
public function getIcon()
{
return "default.png";
}
public function getGroup()
{
return "Core language";
}
public function instantiate($blueprint)
{
return null;
}
/*
* Possible attribute parameters:
* Name(required) - Attribute name
* Required - If defined, attribute is marked as required for compilation
* Enum - If defined, and array of possible values is assigned(for example "enum" => array("1", "2")), compiler will clamp values to this enumeration
* attribute is treated as enumeration, and compiler force values to be in range of enumeration.
* Big - If defined, this attribute is treated as big, and larger text area is used(primarily, for editor).
*/
public function queryAttributeList()
{
return array();
}
}
abstract class BaseNode
{
private $attributes;
private $parentFactory;
private $blueprint;
public final function __construct($pFactory, $blueprint)
{
$this->attributes = array();
$this->blueprint = $blueprint;
$this->parentFactory = $pFactory;
}
public final function setAttribute($a, $b)
{
$attrs = $this->parentFactory->queryAttributeList();
foreach($attrs as $attr)
{
if($attr["name"] == $a)
{
$this->attributes[$attr["name"]] = $b;
if(isset($attr["enum"]) && is_array($attr["enum"]) && sizeof($attr["enum"]) > 0)
{
if(!in_array(strtolower($b), $attr["enum"]))
$this->attributes[$attr["name"]] = $attr["enum"][0];
else
$this->attributes[$attr["name"]] = strtolower($b);
}
return true;
}
}
return false;
}
private final function explodeByMask($mask, $haystack)
{
$ret = array();
$ident = "";
for($i = 0; $i < strlen($haystack); $i++)
{
for($j = 0; $j < strlen($mask); $j++)
{
if($haystack[$i] == $mask[$j] && strlen($ident) > 0)
{
$ret[] = $ident;
$ident = "";
continue;
}
}
$ident .= $haystack[$i];
}
if(strlen($ident) > 0)
$ret[] = $ident;
return $ret;
}
/*
* Attributed format
*
* Helper function for formatting strings with attributes
* All underscores "_" will be automatically replaced to spaces " "
*
* Usage:
* format("%attribute") - where %attribute - attribute name
*/
protected final function format($fmt, $nullAsStr = false)
{
$entries = $this->explodeByMask("% []{};'\"/.,<>()*", $fmt);
foreach($entries as $entry)
{
$entry = str_replace("_", " ", $entry);
if($entry[0] == "%")
{
$val = $this->hasAttribute(substr($entry, 1)) ? $this->getAttribute(substr($entry, 1)) : null;
if($val == null && $nullAsStr)
$val = "null";
$fmt = str_replace(str_replace(" ", "_", $entry), $val, $fmt);
continue;
}
}
return $fmt;
}
public final function getAttribute($a)
{
return isset($this->attributes[$a]) ? $this->attributes[$a] : null;
}
public final function getBlueprint()
{
return $this->blueprint;
}
public final function getFactory()
{
return $this->parentFactory;
}
public final function hasAttribute($a)
{
return isset($this->attributes[$a]);
}
public final function isAttributeSupported($a)
{
if($this->parentFactory)
{
$attrs = $this->parentFactory->queryAttributeList();
foreach($attrs as $attr)
{
if($attr["name"] == $a)
return true;
}
}
return false;
}
public final function compile()
{
$attrs = $this->parentFactory->queryAttributeList();
foreach($attrs as $attr)
{
if(!$this->hasAttribute($attr["name"]) && isset($attr["required"]))
{
$this->blueprint->throwError("Missing required attribute \"$attr[name]\"");
return false;
}
}
return $this->process();
}
/*
// Should compiler treat this node as block start(for conditions, loops, etc)?
public function isBlock()
{
return false;
}*/
/*
* Get short description of what node does
*/
public function getDescription()
{
return $this->parentFactory->getName();
}
public abstract function process();
}
/*
* Core language
*/
class IncludeBlockNodeFactory extends NodeFactory
{
public function getName()
{
return "Include PHP file";
}
public function getIcon()
{
return "php.png";
}
public function instantiate($blueprint)
{
if($blueprint)
return new IncludeNode($this, $blueprint);
return null;
}
public function queryAttributeList()
{
return array(
array("name" => "path", "required" => true)
);
}
}
class ExitNodeFactory extends NodeFactory
{
public function getName()
{
return "Exit from script";
}
public function getIcon()
{
return "exit.png";
}
public function instantiate($blueprint)
{
if($blueprint)
return new ExitNode($this, $blueprint);
return null;
}
}
class CommentNodeFactory extends NodeFactory
{
public function getName()
{
return "Insert comment";
}
public function getIcon()
{
return "info.png";
}
public function instantiate($blueprint)
{
if($blueprint)
return new CommentNode($this, $blueprint);
return null;
}
public function queryAttributeList()
{
return array(
array("name" => "text", "required" => true)
);
}
}
class StartBlockNodeFactory extends NodeFactory
{
public function getName()
{
return "BlockStart";
}
public function getIcon()
{
return "brackets.png";
}
public function instantiate($blueprint)
{
if($blueprint)
return new StartBlockNode($this, $blueprint);
return null;
}
}
class EndBlockNodeFactory extends NodeFactory
{
public function getName()
{
return "BlockEnd";
}
public function getIcon()
{
return "brackets.png";
}
public function instantiate($blueprint)
{
if($blueprint)
return new EndBlockNode($this, $blueprint);
return null;
}
}
class VariableNodeFactory extends NodeFactory
{
public function getName()
{
return "Variable";
}
public function instantiate($blueprint)
{
if($blueprint)
return new VariableNode($this, $blueprint);
return null;
}
public function queryAttributeList()
{
return array(
array("name" => "name", "required" => true),
array("name" => "value")
);
}
}
class PrintNodeFactory extends NodeFactory
{
public function getName()
{
return "Print";
}
public function getIcon()
{
return "type.png";
}
public function instantiate($blueprint)
{
if($blueprint)
return new PrintNode($this, $blueprint);
return null;
}
public function queryAttributeList()
{
return array(
array("name" => "text", "required" => true)
);
}
}
class EvalNodeFactory extends NodeFactory
{
public function getName()
{
return "Execute piece of code";
}
public function getIcon()
{
return "binary.png";
}
public function instantiate($blueprint)
{
if($blueprint)
return new EvalNode($this, $blueprint);
return null;
}
public function queryAttributeList()
{
return array(
array("name" => "code", "required" => true, "big" => true)
);
}
}
class ConditionNodeFactory extends NodeFactory
{
public function getName()
{
return "Condition";
}
public function getIcon()
{
return "question.png";
}
public function instantiate($blueprint)
{
if($blueprint)
return new ConditionNode($this, $blueprint);
return null;
}
public function queryAttributeList()
{
return array(
array("name" => "comparator", "enum" => ConditionNode::getComparatorEnum(), "required" => true),
array("name" => "operand 1", "required" => true),
array("name" => "operand 2", "required" => true)
);
}
}
class LoopNodeFactory extends NodeFactory
{
public function getName()
{
return "Loop";
}
public function getIcon()
{
return "loop.png";
}
public function instantiate($blueprint)
{
if($blueprint)
return new LoopNode($this, $blueprint);
return null;
}
public function queryAttributeList()
{
return array(
array("name" => "comparator", "enum" => ConditionNode::getComparatorEnum(), "required" => true),
array("name" => "operand 1", "required" => true),
array("name" => "operand 2", "required" => true)
);
}
}
class CounterNodeFactory extends NodeFactory
{
public function getName()
{
return "Counter";
}
public function getIcon()
{
return "loop.png";
}
public function instantiate($blueprint)
{
if($blueprint)
return new CounterNode($this, $blueprint);
return null;
}
public function queryAttributeList()
{
return array(
array("name" => "variable name", "required" => true, "default" => "i"),
array("name" => "comparator", "enum" => ConditionNode::getComparatorEnum(), "required" => true),
array("name" => "from", "required" => true),
array("name" => "to", "required" => true),
array("name" => "direction", "enum" => array("upwards", "downwards"), "required" => true, "default" => "upwards")
);
}
}
class VariableNode extends BaseNode
{
public function getDescription()
{
return $this->format("Assign variable <b>$%name</b> value %value");
}
public function process()
{
return $this->format("$%name = %value;", true);
}
}
class ExitNode extends BaseNode
{
public function getDescription()
{
return $this->format("Exit from script");
}
public function process()
{
return $this->format("exit();");
}
}
class CommentNode extends BaseNode
{
public function getDescription()
{
return $this->format("Comment \"%text\"");
}
public function process()
{
return $this->format("/* %text */", true);
}
}
class IncludeNode extends BaseNode
{
public function getDescription()
{
return $this->format("Include PHP file <b>%path</b>");
}
public function process()
{
return $this->format("if(file_exists(%path))include %path;", true);
}
}
class StartBlockNode extends BaseNode
{
public function getDescription()
{
return "Begin block";
}
public function process()
{
return "{";
}
}
class EndBlockNode extends BaseNode
{
public function getDescription()
{
return "End block";
}
public function process()
{
return "}";
}
}
class ConditionNode extends BaseNode
{
public static function getComparatorEnum()
{
return array("equal", "not equal", "less", "greater");
}
public static function comparatorToOperator($comparator)
{
switch($comparator)
{
default:
case "equal":
return "==";
case "not equal":
return "!=";
case "less":
return "<";
case "greater":
return ">";
}
}
public function getDescription()
{
return $this->format("Test if %operand_1 is %comparator %operand_2");
}
public function process()
{
$comparator = ConditionNode::comparatorToOperator($this->getAttribute("comparator"));
return $this->format("if(%operand_1 $comparator %operand_2)");
}
}
class LoopNode extends BaseNode
{
public function getDescription()
{
$comparator = $this->getAttribute("comparator");
return $this->format("Loop until %operand_1 is $comparator %operand_2");
}
public function process()
{
$comparator = ConditionNode::comparatorToOperator($this->getAttribute("comparator"));
return $this->format("while(%operand_1 $comparator %operand_2)");
}
}
class CounterNode extends BaseNode
{
public static function directionToOperator($direction)
{
if($direction == "upwards")
return "++";
else
return "--";
}
public function getDescription()
{
return $this->format("Count in variable <b>%variable_name</b> from %from to %to %direction");
}
public function process()
{
$comparator = ConditionNode::comparatorToOperator($this->getAttribute("comparator"));
$dir = self::directionToOperator($this->getAttribute("direction"));
return $this->format("for($%variable_name = %from; $%variable_name $comparator %to; $%variable_name $dir)");
}
}
class PrintNode extends BaseNode
{
public function getDescription()
{
return $this->format("Print %text on screen");
}
public function process()
{
return $this->format("echo %text;");
}
}
class EvalNode extends BaseNode
{
public function getDescription()
{
return "Execute a piece of code";
}
public function process()
{
return sprintf("eval(\"%s\");", str_replace("\"", "\\\"", $this->getAttribute("code")));
}
}
class NodeLibraryList
{
const LIBRARY_DIR = "libs";
private static $libraryList = array();
public static function load($lib)
{
$path = NodeLibraryList::LIBRARY_DIR . "/$lib";
if(file_exists($path) && is_dir($path) && file_exists("$path/libinfo.ini"))
{
$library = array();
$library["name"] = $lib;
$library["icon"] = file_exists("$path/icon.png") ? "$path/icon.png" : null;
$library["config"] = parse_ini_file("$path/libinfo.ini");
if(isset($library["config"]["entrypoint"]) && file_exists("$path/" . $library["config"]["entrypoint"]))
{
include "$path/" . $library["config"]["entrypoint"];
self::$libraryList[] = $library;
return true;
}
}
return false;
}
public static function isLibraryLoaded($name)
{
foreach(self::$libraryList as $lib)
{
if($lib["name"] == $name)
return true;
}
return false;
}
public static function getLoadedLibraries()
{
return self::$libraryList;
}
}
// Core language components
NodeList::registerFactory(array(new IncludeBlockNodeFactory, new VariableNodeFactory, new PrintNodeFactory, new EvalNodeFactory));
NodeList::registerFactory(array(new StartBlockNodeFactory, new EndBlockNodeFactory, new CommentNodeFactory, new ExitNodeFactory));
NodeList::registerFactory(array(new ConditionNodeFactory, new LoopNodeFactory, new CounterNodeFactory));
if(file_exists(NodeLibraryList::LIBRARY_DIR . "/libs.txt"))
{
$f = fopen(NodeLibraryList::LIBRARY_DIR . "/libs.txt", "r");
if($f)
{
while(!feof($f))
NodeLibraryList::load(trim(fgets($f)));
fclose($f);
}
}