/src/main/php/xp/compiler/emit/source/Emitter.class.php
PHP | 2586 lines | 2085 code | 108 blank | 393 comment | 109 complexity | d6964c528df701ffae665557872e23c3 MD5 | raw file
Large files files are truncated, but you can click here to view the full file
- <?php namespace xp\compiler\emit\source;
- use xp\compiler\types\CompiledType;
- use xp\compiler\types\TypeDeclaration;
- use xp\compiler\types\TypeInstance;
- use xp\compiler\types\TypeName;
- use xp\compiler\types\Types;
- use xp\compiler\types\Scope;
- use xp\compiler\types\CompilationUnitScope;
- use xp\compiler\types\TypeDeclarationScope;
- use xp\compiler\types\MethodScope;
- use xp\compiler\types\Method;
- use xp\compiler\types\Field;
- use xp\compiler\types\Constructor;
- use xp\compiler\types\Property;
- use xp\compiler\types\Operator;
- use xp\compiler\types\Indexer;
- use xp\compiler\types\Constant;
- use xp\compiler\types\Parameter;
- use xp\compiler\ast\ParseTree;
- use xp\compiler\ast\VariableNode;
- use xp\compiler\ast\TypeDeclarationNode;
- use xp\compiler\ast\Resolveable;
- use xp\compiler\ast\ArrayAccessNode;
- use xp\compiler\ast\StaticMemberAccessNode;
- use xp\compiler\ast\MethodCallNode;
- use xp\compiler\ast\MemberAccessNode;
- use xp\compiler\ast\StaticMethodCallNode;
- use xp\compiler\ast\FinallyNode;
- use xp\compiler\ast\CatchNode;
- use xp\compiler\ast\ThrowNode;
- use xp\compiler\ast\ClassNode;
- use xp\compiler\ast\AssignmentNode;
- use xp\compiler\ast\ArrayNode;
- use xp\compiler\ast\FieldNode;
- use xp\compiler\ast\ConstructorNode;
- use xp\compiler\ast\MethodNode;
- use xp\compiler\ast\ReturnNode;
- use xp\compiler\ast\StringNode;
- use xp\compiler\ast\EnumMemberNode;
- use xp\compiler\ast\IndexerNode;
- use xp\compiler\ast\StaticInitializerNode;
- use xp\compiler\ast\LocalsToMemberPromoter;
- use xp\compiler\emit\Buffer;
- use lang\reflect\Modifiers;
- use lang\Throwable;
- /**
- * Emits sourcecode using PHP sourcecode
- *
- * @test xp://net.xp_lang.tests.execution.source.AnnotationTest
- * @test xp://net.xp_lang.tests.execution.source.ArrayTest
- * @test xp://net.xp_lang.tests.execution.source.AssignmentTest
- * @test xp://net.xp_lang.tests.execution.source.AutoPropertiesTest
- * @test xp://net.xp_lang.tests.execution.source.CastingTest
- * @test xp://net.xp_lang.tests.execution.source.CatchTest
- * @test xp://net.xp_lang.tests.execution.source.ChainingTest
- * @test xp://net.xp_lang.tests.execution.source.ClassDeclarationTest
- * @test xp://net.xp_lang.tests.execution.source.ComparisonTest
- * @test xp://net.xp_lang.tests.execution.source.ConcatTest
- * @test xp://net.xp_lang.tests.execution.source.DefaultArgsTest
- * @test xp://net.xp_lang.tests.execution.source.EnumDeclarationTest
- * @test xp://net.xp_lang.tests.execution.source.ExecutionTest
- * @test xp://net.xp_lang.tests.execution.source.ExtensionMethodsTest
- * @test xp://net.xp_lang.tests.execution.source.Filter
- * @test xp://net.xp_lang.tests.execution.source.FinallyTest
- * @test xp://net.xp_lang.tests.execution.source.Functions
- * @test xp://net.xp_lang.tests.execution.source.InstanceCreationTest
- * @test xp://net.xp_lang.tests.execution.source.InterfaceDeclarationTest
- * @test xp://net.xp_lang.tests.execution.source.LambdaTest
- * @test xp://net.xp_lang.tests.execution.source.LoopExecutionTest
- * @test xp://net.xp_lang.tests.execution.source.MathTest
- * @test xp://net.xp_lang.tests.execution.source.MemberInitTest
- * @test xp://net.xp_lang.tests.execution.source.MethodOverloadingTest
- * @test xp://net.xp_lang.tests.execution.source.MultiCatchTest
- * @test xp://net.xp_lang.tests.execution.source.NativeClassUsageTest
- * @test xp://net.xp_lang.tests.execution.source.NavigationOperatorTest
- * @test xp://net.xp_lang.tests.execution.source.OperatorOverloadingTest
- * @test xp://net.xp_lang.tests.execution.source.OperatorTest
- * @test xp://net.xp_lang.tests.execution.source.PropertiesTest
- * @test xp://net.xp_lang.tests.execution.source.StaticImportTest
- * @test xp://net.xp_lang.tests.execution.source.StringBuffer
- * @test xp://net.xp_lang.tests.execution.source.TernaryOperatorTest
- * @test xp://net.xp_lang.tests.execution.source.VarArgsTest
- * @test xp://net.xp_lang.tests.execution.source.VariablesTest
- * @test xp://net.xp_lang.tests.execution.source.WithTest
- * @test xp://net.xp_lang.tests.compilation.TypeTest
- * @see xp://xp.compiler.ast.ParseTree
- */
- class Emitter extends \xp\compiler\emit\Emitter {
- protected
- $temp = array(null),
- $method = array(null),
- $finalizers = array(null),
- $metadata = array(null),
- $properties = array(null),
- $inits = array(null),
- $local = array(null),
- $types = array(null);
- /**
- * Returns a new temporary variable
- *
- * @return string
- */
- protected function tempVar() {
- return '$T'.($this->temp[0]++);
- }
-
- /**
- * Check whether a node is writeable - that is: can be the left-hand
- * side of an assignment
- *
- * @param xp.compiler.ast.Node node
- * @return bool
- */
- protected function isWriteable($node) {
- if ($node instanceof VariableNode || $node instanceof ArrayAccessNode) {
- return true;
- } else if ($node instanceof MemberAccessNode || $node instanceof StaticMemberAccessNode) {
- return true; // TODO: Check for private, protected
- }
- return false;
- }
-
- /**
- * Creates a generic component string for use in meta data
- *
- * @param xp.compiler.types.TypeName type
- * @return string
- */
- protected function genericComponentAsMetadata($type) {
- $s= '';
- foreach ($type->components as $component) {
- $s.= ', '.$component->name;
- }
- return substr($s, 2);
- }
-
- /**
- * Emit uses statements for a given list of types
- *
- * @param xp.compiler.emit.Buffer b
- * @param [:bool] types
- */
- protected function emitUses($b, array $types) {
- static $bootstrap= array(
- 'lang.Object' => true,
- 'lang.StackTraceElement' => true,
- 'lang.Throwable' => true,
- 'lang.Error' => true,
- 'lang.XPException' => true,
- 'lang.Type' => true,
- 'lang.Primitive' => true,
- 'lang.types.Character' => true,
- 'lang.types.Number' => true,
- 'lang.types.Byte' => true,
- 'lang.types.Bytes' => true,
- 'lang.types.String' => true,
- 'lang.types.Integer' => true,
- 'lang.types.Double' => true,
- 'lang.types.Boolean' => true,
- 'lang.types.ArrayListIterator' => true,
- 'lang.types.ArrayList' => true,
- 'lang.ArrayType' => true,
- 'lang.MapType' => true,
- 'lang.reflect.Routine' => true,
- 'lang.reflect.Parameter' => true,
- 'lang.reflect.TargetInvocationException' => true,
- 'lang.reflect.Method' => true,
- 'lang.reflect.Field' => true,
- 'lang.reflect.Constructor' => true,
- 'lang.reflect.Modifiers' => true,
- 'lang.reflect.Package' => true,
- 'lang.XPClass' => true,
- 'lang.NullPointerException' => true,
- 'lang.IllegalAccessException' => true,
- 'lang.IllegalArgumentException' => true,
- 'lang.IllegalStateException' => true,
- 'lang.FormatException' => true,
- 'lang.ClassNotFoundException' => true,
- 'lang.AbstractClassLoader' => true,
- 'lang.FileSystemClassLoader' => true,
- 'lang.DynamicClassLoader' => true,
- 'lang.archive.ArchiveClassLoader' => true,
- 'lang.ClassLoader' => true,
- );
- // Do not add uses() entries for:
- // * Types emitted inside the same sourcefile
- // * Native classes
- // * Bootstrap classes
- $this->cat && $this->cat->debug('uses(', $types, ')');
- $uses= array();
- foreach ($types as $type => $used) {
- if (isset($this->local[0][$type]) || 'php.' === substr($type, 0, 4) || isset($bootstrap[$type])) continue;
- // TODO: Find out why this would make a difference, $type should already be fully-qualified
- // @net.xp_lang.tests.execution.source.PropertiesOverloadingTest
- // @net.xp_lang.tests.integration.CircularDependencyTest
- try {
- $uses[]= $this->resolveType(new TypeName($type), false)->name();
- } catch (Throwable $e) {
- $this->error('0424', $e->toString());
- }
- }
- $uses && $b->insert('uses(\''.implode("', '", $uses).'\');', 0);
- }
-
- /**
- * Emit parameters
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.Node[] params
- * @param bool brackets
- * @return int
- */
- protected function emitInvocationArguments($b, array $params, $brackets= true) {
- $brackets && $b->append('(');
- $s= sizeof($params)- 1;
- foreach ($params as $i => $param) {
- $this->emitOne($b, $param);
- $i < $s && $b->append(',');
- }
- $brackets && $b->append(')');
- return sizeof($params);
- }
-
- /**
- * Emit invocations
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.InvocationNode inv
- */
- protected function emitInvocation($b, $inv) {
- if (!isset($this->scope[0]->statics[$inv->name])) {
- if (!($resolved= $this->scope[0]->resolveStatic($inv->name))) {
- $this->error('T501', 'Cannot resolve '.$inv->name.'()', $inv);
- return;
- }
- $this->scope[0]->statics[$inv->name]= $resolved; // Cache information
- }
- $ptr= $this->scope[0]->statics[$inv->name];
- // Static method call vs. function call
- if (true === $ptr) {
- $b->append($inv->name);
- $this->emitInvocationArguments($b, (array)$inv->arguments);
- $this->scope[0]->setType($inv, TypeName::$VAR);
- } else {
- $b->append($ptr->holder->literal().'::'.$ptr->name());
- $this->emitInvocationArguments($b, (array)$inv->arguments);
- $this->scope[0]->setType($inv, $ptr->returns);
- }
- }
-
- /**
- * Emit strings
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.StringNode str
- */
- protected function emitString($b, $str) {
- $b->append("'");
- $b->append(strtr($str->resolve(), array(
- "'" => "\'",
- '\\' => '\\\\'
- )));
- $b->append("'");
- }
- /**
- * Emit an array (a sequence of elements with a zero-based index)
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.ArrayNode arr
- */
- protected function emitArray($b, $arr) {
- $b->append('array(');
- foreach ((array)$arr->values as $value) {
- $this->emitOne($b, $value);
- $b->append(',');
- }
- $b->append(')');
- }
- /**
- * Emit a map (a key/value pair dictionary)
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.MapNode map
- */
- protected function emitMap($b, $map) {
- $b->append('array(');
- foreach ((array)$map->elements as $pair) {
- $this->emitOne($b, $pair[0]);
- $b->append(' => ');
- $this->emitOne($b, $pair[1]);
- $b->append(',');
- }
- $b->append(')');
- }
- /**
- * Emit booleans
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.BooleanNode const
- */
- protected function emitBoolean($b, $const) {
- $b->append($const->resolve() ? 'TRUE' : 'FALSE');
- }
- /**
- * Emit null
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.NullNode const
- */
- protected function emitNull($b, $const) {
- $b->append('NULL');
- }
-
- /**
- * Emit constants
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.ConstantNode const
- */
- protected function emitConstant($b, $const) {
- if ($constant= $this->scope[0]->resolveConstant($const->name)) {
- $b->append(var_export($constant->value, true));
- return;
- }
- $b->append($const->name);
- }
- /**
- * Emit \casts
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.CastNode cast
- */
- protected function emitCast($b, $cast) {
- static $primitives= array(
- 'int' => '(int)',
- 'double' => '(double)',
- 'string' => '(string)',
- 'array' => '(array)',
- 'bool' => '(bool)',
- // Missing intentionally: object and unset \casts
- );
- if (!$cast->check) {
- $this->emitOne($b, $cast->expression);
- } else if ($cast->type->isPrimitive()) {
- $b->append($primitives[$cast->type->name]);
- $this->emitOne($b, $cast->expression);
- } else if ($cast->type->isArray() || $cast->type->isMap()) {
- $b->append('(array)');
- $this->emitOne($b, $cast->expression);
- } else {
- $b->append('cast(');
- $this->emitOne($b, $cast->expression);
- $b->append(', \'')->append($this->resolveType($cast->type)->name())->append('\')');
- }
-
- $this->scope[0]->setType($cast, $cast->type);
- }
- /**
- * Emit integers
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.IntegerNode num
- */
- protected function emitInteger($b, $num) {
- $b->append($num->resolve());
- }
- /**
- * Emit decimals
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.DecimalNode num
- */
- protected function emitDecimal($b, $num) {
- $res= $num->resolve();
- $b->append($res);
-
- // Prevent float(2) being dumped as "2" and thus an int literal
- strstr($res, '.') || $b->append('.0');
- }
- /**
- * Emit hex numbers
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.HexNode num
- */
- protected function emitHex($b, $num) {
- $b->append($num->resolve());
- }
- /**
- * Emit octal numbers
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.OctalNode num
- */
- protected function emitOctal($b, $num) {
- $b->append($num->resolve());
- }
-
- /**
- * Emit a variable
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.VariableNode var
- */
- protected function emitVariable($b, $var) {
- $b->append('$'.$var->name);
- }
- /**
- * Emit a member access. Helper to emitChain()
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.DynamicVariableReferenceNode access
- */
- public function emitDynamicMemberAccess($b, $access) {
- $this->emitOne($b, $call->target);
- $b->append('->{');
- $this->emitOne($b, $access->expression);
- $b->append('}');
-
- $this->scope[0]->setType($call, TypeName::$VAR);
- }
- /**
- * Emit static method call
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.StaticMethodCallNode call
- */
- public function emitStaticMethodCall($b, $call) {
- $ptr= $this->resolveType($call->type);
- $b->append($ptr->literal().'::'.$call->name);
- $this->emitInvocationArguments($b, (array)$call->arguments);
- // Record type
- $this->scope[0]->setType($call, $ptr->hasMethod($call->name) ? $ptr->getMethod($call->name)->returns : TypeName::$VAR);
- }
- /**
- * Emit instance call
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.InstanceCallNode call
- */
- public function emitInstanceCall($b, $call) {
- // Navigation operator
- if ($call->nav) {
- $var= $this->tempVar();
- $b->append('(null === ('.$var.'=');
- $this->emitOne($b, $call->target);
- $b->append(') ? null : call_user_func(')->append($var);
- if ($call->arguments) {
- $b->append(', ');
- $this->emitInvocationArguments($b, $call->arguments, false);
- }
- $b->append('))');
- } else {
- $b->append('call_user_func(');
- $this->emitOne($b, $call->target);
- if ($call->arguments) {
- $b->append(', ');
- $this->emitInvocationArguments($b, $call->arguments, false);
- }
- $b->append(')');
- }
- }
- /**
- * Emit method call
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.MethodCallNode call
- */
- public function emitMethodCall($b, $call) {
- $mark= $b->mark();
- $this->emitOne($b, $call->target);
-
- // Check for extension methods
- $ptr= new TypeInstance($this->resolveType($this->scope[0]->typeOf($call->target), false));
- if (null !== ($ext= $this->scope[0]->getExtension($ptr, $call->name))) {
- $b->insert($ext->holder->literal().'::'.$call->name.'(', $mark);
- if ($call->arguments) {
- $b->append(', ');
- $this->emitInvocationArguments($b, $call->arguments, false);
- }
- $b->append(')');
- $this->scope[0]->setType($call, $ext->returns);
- return;
- }
- // Manually verify as we can then rely on call target type being available
- if (!$this->checks->verify($call, $this->scope[0], $this, true)) return;
- if ($call->nav) {
- $var= $this->tempVar();
- $b->insert('(NULL === ('.$var.'=', $mark);
- $b->append(') ? NULL : ')->append($var)->append('->');
- $b->append($call->name);
- $this->emitInvocationArguments($b, (array)$call->arguments);
- $b->append(')');
- } else {
- // Rewrite for unsupported syntax
- // - new Date().toString() to create(new Date()).toString()
- // - (<expr>).toString to create(<expr>).toString()
- if (
- !$call->target instanceof ArrayAccessNode &&
- !$call->target instanceof MethodCallNode &&
- !$call->target instanceof MemberAccessNode &&
- !$call->target instanceof VariableNode &&
- !$call->target instanceof StaticMemberAccessNode &&
- !$call->target instanceof StaticMethodCallNode
- ) {
- $b->insert('create(', $mark);
- $b->append(')');
- }
- $b->append('->'.$call->name);
- $this->emitInvocationArguments($b, (array)$call->arguments);
- }
- // Record type
- $this->scope[0]->setType($call, $ptr->hasMethod($call->name) ? $ptr->getMethod($call->name)->returns : TypeName::$VAR);
- }
- /**
- * Emit member access
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.StaticMemberAccessNode access
- */
- public function emitStaticMemberAccess($b, $access) {
- $ptr= $this->resolveType($access->type);
- $b->append($ptr->literal().'::$'.$access->name);
- // Record type
- $this->scope[0]->setType($access, $ptr->hasField($access->name) ? $ptr->getField($access->name)->type : TypeName::$VAR);
- }
- /**
- * Emit member access
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.MemberAccessNode access
- */
- public function emitMemberAccess($b, $access) {
- $mark= $b->mark();
- $this->emitOne($b, $access->target);
-
- $type= $this->scope[0]->typeOf($access->target);
-
- // Overload [...].length
- if ($type->isArray() && 'length' === $access->name) {
- $b->insert('sizeof(', $mark);
- $b->append(')');
- $this->scope[0]->setType($access, new TypeName('int'));
- return;
- }
- // Manually verify as we can then rely on call target type being available
- if (!$this->checks->verify($access, $this->scope[0], $this, true)) return;
- // Navigation operator
- if ($access->nav) {
- $var= $this->tempVar();
- $b->insert('(NULL === ('.$var.'=', $mark);
- $b->append(') ? NULL : ')->append($var)->append('->');
- $b->append($access->name);
- $b->append(')');
- } else {
- // Rewrite for unsupported syntax
- // - new Person().name to create(new Person()).name
- // - (<expr>).name to create(<expr>).name
- if (
- !$access->target instanceof ArrayAccessNode &&
- !$access->target instanceof MethodCallNode &&
- !$access->target instanceof MemberAccessNode &&
- !$access->target instanceof VariableNode &&
- !$access->target instanceof StaticMemberAccessNode &&
- !$access->target instanceof StaticMethodCallNode
- ) {
- $b->insert('create(', $mark);
- $b->append(')');
- }
- $b->append('->'.$access->name);
- }
-
- // Record type
- $ptr= new TypeInstance($this->resolveType($type));
- if ($ptr->hasField($access->name)) {
- $result= $ptr->getField($access->name)->type;
- } else if ($ptr->hasProperty($access->name)) {
- $result= $ptr->getProperty($access->name)->type;
- } else {
- $result= TypeName::$VAR;
- }
- $this->scope[0]->setType($access, $result);
- }
- /**
- * Emit array access
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.ArrayAccessNode access
- */
- public function emitArrayAccess($b, $access) {
- $mark= $b->mark();
- $this->emitOne($b, $access->target);
-
- // Manually verify as we can then rely on call target type being available
- if (!$this->checks->verify($access, $this->scope[0], $this, true)) return;
-
- // Rewrite for unsupported syntax
- // - $a.getMethods()[2] to this($a.getMethods(), 2)
- // - T::asList()[2] to this(T::asList(), 2)
- // - new int[]{5, 6, 7}[2] to this(array(5, 6, 7), 2)
- if (
- !$access->target instanceof ArrayAccessNode &&
- !$access->target instanceof MemberAccessNode &&
- !$access->target instanceof VariableNode &&
- !$access->target instanceof StaticMemberAccessNode
- ) {
- $b->insert('this(', $mark);
- $b->append(',');
- $this->emitOne($b, $access->offset);
- $b->append(')');
- } else {
- $b->append('[');
- $access->offset && $this->emitOne($b, $access->offset);
- $b->append(']');
- }
- }
- /**
- * Emit constant access
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.ConstantAccessNode access
- */
- public function emitConstantAccess($b, $access) {
- $ptr= $this->resolveType($access->type);
- $b->append($ptr->literal().'::'.$access->name);
- // Record type
- $this->scope[0]->setType($access, $ptr->hasConstant($access->name) ? $ptr->getConstant($access->name)->type : TypeName::$VAR);
- }
- /**
- * Emit class access
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.ClassAccessNode access
- */
- public function emitClassAccess($b, $access) {
- $ptr= $this->resolveType($access->type);
- $b->append('XPClass::forName(\''.$ptr->name().'\')');
- // Record type
- $this->scope[0]->setType($access, new TypeName('lang.XPClass'));
- }
- /**
- * Emit a braced expression
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.BracedExpressionNode const
- */
- protected function emitBracedExpression($b, $braced) {
- $b->append('(');
- $this->emitOne($b, $braced->expression);
- $b->append(')');
- }
- /**
- * Emit binary operation node
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.BinaryOpNode bin
- */
- protected function emitBinaryOp($b, $bin) {
- static $ops= array(
- '~' => array(true, '.'),
- '-' => array(true, '-'),
- '+' => array(true, '+'),
- '*' => array(true, '*'),
- '/' => array(true, '/'),
- '%' => array(true, '%'),
- '|' => array(true, '|'),
- '&' => array(true, '&'),
- '^' => array(true, '^'),
- '&&' => array(true, '&&'),
- '||' => array(true, '||'),
- '>>' => array(true, '>>'),
- '<<' => array(true, '<<'),
- '**' => array(false, 'pow')
- );
- static $ovl= array(
- '~' => 'concat',
- '-' => 'minus',
- '+' => 'plus',
- '*' => 'times',
- '/' => 'div',
- '%' => 'mod',
- );
-
- $t= $this->scope[0]->typeOf($bin->lhs);
- if ($t->isClass()) {
- $ptr= $this->resolveType($t);
- if ($ptr->hasOperator($bin->op)) {
- $o= $ptr->getOperator($bin->op);
- $b->append($ptr->literal());
- $b->append('::operatorии')->append($ovl[$bin->op])->append('(');
- $this->emitOne($b, $bin->lhs);
- $b->append(',');
- $this->emitOne($b, $bin->rhs);
- $b->append(')');
- $this->scope[0]->setType($bin, $o->returns);
- return;
- }
- }
- $o= $ops[$bin->op];
- if ($o[0]) { // infix
- $this->emitOne($b, $bin->lhs);
- $b->append($o[1]);
- $this->emitOne($b, $bin->rhs);
- } else {
- $b->append($o[1])->append('(');
- $this->emitOne($b, $bin->lhs);
- $b->append(',');
- $this->emitOne($b, $bin->rhs);
- $b->append(')');
- }
- }
- /**
- * Emit unary operation node
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.UnaryOpNode un
- */
- protected function emitUnaryOp($b, $un) {
- static $ops= array(
- '++' => '++',
- '--' => '--',
- );
-
- if ('!' === $un->op) { // FIXME: Use NotNode for this?
- $b->append('!');
- $this->emitOne($b, $un->expression);
- return;
- } else if ('-' === $un->op) {
- $b->append('-');
- $this->emitOne($b, $un->expression);
- return;
- } else if ('~' === $un->op) {
- $b->append('~');
- $this->emitOne($b, $un->expression);
- return;
- } else if (!$this->isWriteable($un->expression)) {
- $this->error('U400', 'Cannot perform unary '.$un->op.' on '.$un->expression->getClassName(), $un);
- return;
- }
- if ($un->postfix) {
- $this->emitOne($b, $un->expression);
- $b->append($ops[$un->op]);
- } else {
- $b->append($ops[$un->op]);
- $this->emitOne($b, $un->expression);
- }
- }
- /**
- * Emit ternary operator node
- *
- * Note: The following two are equivalent:
- * <code>
- * $a= $b ?: $c;
- * $a= $b ? $b : $c;
- * </code>
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.TernaryNode ternary
- */
- protected function emitTernary($b, $ternary) {
- $this->emitOne($b, $ternary->condition);
- $b->append('?');
- $this->emitOne($b, $ternary->expression ?: $ternary->condition);
- $b->append(':');
- $this->emitOne($b, $ternary->conditional);
- }
- /**
- * Emit comparison node
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.ComparisonNode cmp
- */
- protected function emitComparison($b, $cmp) {
- static $ops= array(
- '==' => '==',
- '===' => '===',
- '!=' => '!=',
- '!==' => '!==',
- '<=' => '<=',
- '<' => '<',
- '>=' => '>=',
- '>' => '>',
- );
- $this->emitOne($b, $cmp->lhs);
- $b->append(' '.$ops[$cmp->op].' ');
- $this->emitOne($b, $cmp->rhs);
- }
- /**
- * Emit continue statement
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.ContinueNode statement
- */
- protected function emitContinue($b, $statement) {
- $b->append('continue');
- }
- /**
- * Emit break statement
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.BreakNode statement
- */
- protected function emitBreak($b, $statement) {
- $b->append('break');
- }
- /**
- * Emit noop
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.NoopNode statement
- */
- protected function emitNoop($b, $statement) {
- // NOOP
- }
- /**
- * Emit with statement
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.WithNode with
- */
- protected function emitWith($b, $with) {
- $this->emitAll($b, $with->assignments);
- $this->emitAll($b, $with->statements);
- }
- /**
- * Emit statements
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.StatementsNode statements
- */
- protected function emitStatements($b, $statements) {
- $this->emitAll($b, (array)$statements->list);
- }
- /**
- * Emit foreach loop
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.ForeachNode loop
- */
- protected function emitForeach($b, $loop) {
- $b->append('foreach (');
- $this->emitOne($b, $loop->expression);
-
- // Assign key and value types by checking for loop expression's type
- // * var type may be enumerable
- // * any other type may define an overlad
- $t= $this->scope[0]->typeOf($loop->expression);
- if ($t->isVariable()) {
- $this->warn('T203', 'Enumeration of (var)'.$loop->expression->hashCode().' verification deferred until runtime', $loop);
- $vt= TypeName::$VAR;
- $kt= new TypeName('int');
- } else {
- $ptr= $this->resolveType($t);
- if (!$ptr->isEnumerable()) {
- $this->warn('T300', 'Type '.$ptr->name().' is not enumerable in loop expression '.$loop->expression->getClassName().'['.$loop->expression->hashCode().']', $loop);
- $vt= TypeName::$VAR;
- $kt= new TypeName('int');
- } else {
- $enum= $ptr->getEnumerator();
- $vt= $enum->value;
- $kt= $enum->key;
- }
- }
- $b->append(' as ');
- if (isset($loop->assignment['key'])) {
- $b->append('$'.$loop->assignment['key'].' => ');
- $this->scope[0]->setType(new VariableNode($loop->assignment['key']), $kt);
- }
- $b->append('$'.$loop->assignment['value'].') {');
- $this->scope[0]->setType(new VariableNode($loop->assignment['value']), $vt);
- $this->emitAll($b, (array)$loop->statements);
- $b->append('}');
- }
- /**
- * Emit do ... while loop
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.DoNode loop
- */
- protected function emitDo($b, $loop) {
- $b->append('do {');
- $this->emitAll($b, (array)$loop->statements);
- $b->append('} while (');
- $this->emitOne($b, $loop->expression);
- $b->append(');');
- }
- /**
- * Emit while loop
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.WhileNode loop
- */
- protected function emitWhile($b, $loop) {
- $b->append('while (');
- $this->emitOne($b, $loop->expression);
- $b->append(') {');
- $this->emitAll($b, (array)$loop->statements);
- $b->append('}');
- }
-
- /**
- * Emit components inside a for() statement
- *
- * @param xp.compiler.emit.Buffer b
- * @return xp.compiler.ast.Node[] nodes
- */
- protected function emitForComponent($b, array $nodes) {
- $s= sizeof($nodes)- 1;
- foreach ($nodes as $i => $node) {
- $this->emitOne($b, $node);
- $i < $s && $b->append(', ');
- }
- }
- /**
- * Emit for loop
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.ForNode loop
- */
- protected function emitFor($b, $loop) {
- $b->append('for (');
- $this->emitForComponent($b, (array)$loop->initialization);
- $b->append(';');
- $this->emitForComponent($b, (array)$loop->condition);
- $b->append(';');
- $this->emitForComponent($b, (array)$loop->loop);
- $b->append(') {');
- $this->emitAll($b, (array)$loop->statements);
- $b->append('}');
- }
-
- /**
- * Emit if statement
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.IfNode if
- */
- protected function emitIf($b, $if) {
- $b->append('if (');
- $this->emitOne($b, $if->condition);
- $b->append(') {');
- $this->emitAll($b, (array)$if->statements);
- $b->append('}');
- if ($if->otherwise) {
- $b->append('else {');
- $this->emitAll($b, (array)$if->otherwise->statements);
- $b->append('}');
- }
- }
- /**
- * Emit a switch case
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.CaseNode case
- */
- protected function emitCase($b, $case) {
- $b->append('case ');
- $this->emitOne($b, $case->expression);
- $b->append(': ');
- $this->emitAll($b, (array)$case->statements);
- }
- /**
- * Emit the switch default case
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.DefaultNode default
- */
- protected function emitDefault($b, $default) {
- $b->append('default: ');
- $this->emitAll($b, (array)$default->statements);
- }
- /**
- * Emit switch statement
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.SwitchNode switch
- */
- protected function emitSwitch($b, $switch) {
- $b->append('switch (');
- $this->emitOne($b, $switch->expression);
- $b->append(') {');
- $this->emitAll($b, (array)$switch->cases);
- $b->append('}');
- }
-
- /**
- * Emit a try / catch block
- *
- * Simple form:
- * <code>
- * try {
- * // [...statements...]
- * } catch (lang.Throwable $e) {
- * // [...error handling...]
- * }
- * </code>
- *
- * Multiple catches:
- * <code>
- * try {
- * // [...statements...]
- * } catch (lang.IllegalArgumentException $e) {
- * // [...error handling for IAE...]
- * } catch (lang.FormatException $e) {
- * // [...error handling for FE...]
- * }
- * </code>
- *
- * Try/finally without catch:
- * <code>
- * try {
- * // [...statements...]
- * } finally {
- * // [...finalizations...]
- * }
- * </code>
- *
- * Try/finally with catch:
- * <code>
- * try {
- * // [...statements...]
- * } catch (lang.Throwable $e) {
- * // [...error handling...]
- * } finally {
- * // [...finalizations...]
- * }
- * </code>
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.TryNode try
- */
- protected function emitTry($b, $try) {
- static $mangled= 'ииe';
- // Check whether a finalization handler is available. If so, because
- // the underlying runtime does not support this, add statements after
- // the try block and to all catch blocks
- $numHandlers= sizeof($try->handling);
- if ($try->handling[$numHandlers- 1] instanceof FinallyNode) {
- array_unshift($this->finalizers, array_pop($try->handling));
- $numHandlers--;
- } else {
- array_unshift($this->finalizers, null);
- }
-
- // If no handlers are left, create a simple catch-all-and-rethrow
- // handler
- if (0 == $numHandlers) {
- $rethrow= new ThrowNode(array('expression' => new VariableNode($mangled)));
- $first= new CatchNode(array(
- 'type' => new TypeName('lang.Throwable'),
- 'variable' => $mangled,
- 'statements' => $this->finalizers[0] ? array($this->finalizers[0], $rethrow) : array($rethrow)
- ));
- } else {
- $first= $try->handling[0];
- $this->scope[0]->setType(new VariableNode($first->variable), $first->type);
- }
- $b->append('try {'); {
- $this->emitAll($b, (array)$try->statements);
- $this->finalizers[0] && $this->emitOne($b, $this->finalizers[0]);
- }
-
- // First catch.
- $b->append('} catch('.$this->resolveType($first->type)->literal().' $'.$first->variable.') {'); {
- $this->scope[0]->setType(new VariableNode($first->variable), $first->type);
- $this->emitAll($b, (array)$first->statements);
- $this->finalizers[0] && $this->emitOne($b, $this->finalizers[0]);
- }
-
- // Additional catches
- for ($i= 1; $i < $numHandlers; $i++) {
- $b->append('} catch('.$this->resolveType($try->handling[$i]->type)->literal().' $'.$try->handling[$i]->variable.') {'); {
- $this->scope[0]->setType(new VariableNode($try->handling[$i]->variable), $try->handling[$i]->type);
- $this->emitAll($b, (array)$try->handling[$i]->statements);
- $this->finalizers[0] && $this->emitOne($b, $this->finalizers[0]);
- }
- }
-
- $b->append('}');
- array_shift($this->finalizers);
- }
- /**
- * Emit an automatic resource management (ARM) block
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.ArmNode arm
- */
- protected function emitArm($b, $arm) {
- static $mangled= 'ииe';
- static $ignored= 'ииi';
- $this->emitAll($b, $arm->initializations);
- // Manually verify as we can then rely on call target type being available
- if (!$this->checks->verify($arm, $this->scope[0], $this, true)) return;
- $b->append('$'.$mangled.'= NULL; try {');
- $this->emitAll($b, (array)$arm->statements);
- $b->append('} catch (Exception $'.$mangled.') {}');
- foreach ($arm->variables as $v) {
- $b->append('try { $')->append($v->name)->append('->close(); } catch (Exception $'.$ignored.') {}');
- }
- $b->append('if ($'.$mangled.') throw $'.$mangled.';');
- }
-
- /**
- * Emit a throw node
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.ThrowNode throw
- */
- protected function emitThrow($b, $throw) {
- $b->append('throw ');
- $this->emitOne($b, $throw->expression);
- }
- /**
- * Emit a finally node
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.FinallyNode finally
- */
- protected function emitFinally($b, $finally) {
- $this->emitAll($b, (array)$finally->statements);
- }
- /**
- * Emit a dynamic instance creation node
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.DynamicInstanceCreationNode new
- */
- protected function emitDynamicInstanceCreation($b, $new) {
- $b->append('new ')->append('$')->append($new->variable);
- $this->emitInvocationArguments($b, (array)$new->parameters);
-
- $this->scope[0]->setType($new, new TypeName('lang.Object'));
- }
- /**
- * Emit an instance creation node
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.InstanceCreationNode new
- */
- protected function emitInstanceCreation($b, $new) {
- // Anonymous instance creation:
- //
- // - Create unique classname
- // - Extend parent class if type is a class
- // - Implement type and extend lang.Object if it's an interface
- //
- // Do not register type name from new(), it will be added by
- // emitClass() during declaration emittance.
- $generic= null;
- if (isset($new->body)) {
- $parent= $this->resolveType($new->type, false);
- if (Types::INTERFACE_KIND === $parent->kind()) {
- $p= array('parent' => new TypeName('lang.Object'), 'implements' => array($new->type));
-
- // If the interface we implement is generic, we need to
- // make the generated class a generic instance.
- if ($new->type->components) {
- $components= array();
- foreach ($new->type->components as $component) {
- $components[]= $this->resolveType($component, false)->name();
- }
- $generic= array($parent->name(), null, $components);
- }
-
- } else if (Types::ENUM_KIND === $parent->kind()) {
- $this->error('C405', 'Cannot create anonymous enums', $new);
- return;
- } else {
- $p= array('parent' => $new->type, 'implements' => null);
- }
- $unique= new TypeName(strtr($parent->literal(), '\\', 'д').'ии'.strtr(uniqid(null, true), '.', 'и'));
- $decl= new ClassNode(0, null, $unique, $p['parent'], $p['implements'], $new->body);
- $decl->synthetic= true;
- $generic && $decl->generic= $generic;
- $ptr= new TypeDeclaration(new ParseTree(null, array(), $decl), $parent);
- $this->scope[0]->declarations[]= $decl;
- $this->scope[0]->setType($new, $unique);
- } else {
- $ptr= $this->resolveType($new->type);
- $this->scope[0]->setType($new, $new->type);
- }
-
- // If generic instance is created, use the create(spec, args*)
- // core functionality. If this a compiled generic type we may
- // do quite a bit better - but how do we detect this?
- if ($new->type->components && !$generic) {
- $b->append('create(\'new '.$ptr->name().'<');
- $s= sizeof($new->type->components)- 1;
- foreach ($new->type->components as $i => $component) {
- $b->append($this->resolveType($component)->name());
- $i < $s && $b->append(',');
- }
- $b->append('>\'');
- if ($new->parameters) {
- $b->append(',');
- $this->emitInvocationArguments($b, (array)$new->parameters, false);
- }
- $b->append(')');
- } else {
- $b->append('new '.$ptr->literal());
- $this->emitInvocationArguments($b, (array)$new->parameters);
- }
- }
-
- /**
- * Emit an assignment
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.AssignmentNode assign
- */
- protected function emitAssignment($b, $assign) {
- static $ops= array(
- '=' => '=',
- '~=' => '.=',
- '-=' => '-=',
- '+=' => '+=',
- '*=' => '*=',
- '/=' => '/=',
- '%=' => '%=',
- '|=' => '|=',
- '^=' => '^=',
- '&=' => '&=',
- '<<=' => '<<=',
- '>>=' => '>>=',
- );
- static $ovl= array(
- '~=' => 'concat',
- '-=' => 'minus',
- '+=' => 'plus',
- '*=' => 'times',
- '/=' => 'div',
- '%=' => 'mod',
- );
- $t= $this->scope[0]->typeOf($assign->variable);
- if ($t->isClass()) {
- $ptr= $this->resolveType($t);
- if ($ptr->hasOperator($assign->op{0})) {
- $o= $ptr->getOperator($assign->op{0});
-
- $this->emitOne($b, $assign->variable);
- $b->append('=');
- $b->append($ptr->literal());
- $b->append('::operatorии')->append($ovl[$assign->op])->append('(');
- $this->emitOne($b, $assign->variable);
- $b->append(',');
- $this->emitOne($b, $assign->expression);
- $b->append(')');
- $this->scope[0]->setType($assign, $o->returns);
- return;
- }
- }
-
- // First set type to void, emit assignment, then overwrite type with
- // right-hand-side's type. This is done in order to guard for checks
- // on uninitialized variables, which is OK during assignment.
- $this->scope[0]->setType($assign->variable, TypeName::$VOID);
- $this->emitOne($b, $assign->variable);
- $b->append($ops[$assign->op]);
- $this->emitOne($b, $assign->expression);
- $this->scope[0]->setType($assign->variable, $this->scope[0]->typeOf($assign->expression));
- }
- /**
- * Emit an operator
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.OperatorNode method
- */
- protected function emitOperator($b, $operator) {
- static $ovl= array(
- '~' => 'concat',
- '-' => 'minus',
- '+' => 'plus',
- '*' => 'times',
- '/' => 'div',
- '%' => 'mod',
- );
-
- $name= 'operatorии'.$ovl[$operator->symbol];
- $this->enter(new MethodScope($name));
- $return= $this->resolveType($operator->returns);
- $this->metadata[0][1][$name]= array(
- DETAIL_ARGUMENTS => array(),
- DETAIL_RETURNS => $return->name(),
- DETAIL_THROWS => array(),
- DETAIL_COMMENT => $operator->comment
- ? trim(preg_replace('/\n\s+\* ?/', "\n", "\n ".substr($operator->comment, 4, strpos($operator->comment, '* @')- 2)))
- : null
- ,
- DETAIL_ANNOTATIONS => array(),
- DETAIL_TARGET_ANNO => array()
- );
- array_unshift($this->method, $name);
- $this->emitAnnotations($this->metadata[0][1][$name], (array)$operator->annotations);
- $b->append('public static function ')->append($name);
- $signature= $this->emitParameters($b, (array)$operator->parameters, '{');
- $this->emitAll($b, (array)$operator->body);
- $b->append('}');
-
- array_shift($this->method);
- $this->leave();
-
- // Register type information
- $o= new Operator();
- $o->symbol= $operator->symbol;
- $o->returns= new TypeName($return->name());
- $o->parameters= $signature;
- $o->modifiers= $operator->modifiers;
- $this->types[0]->addOperator($o);
- }
- /**
- * Emit method parameters
- *
- * @param xp.compiler.emit.Buffer b
- * @param array<string, *>[] parameters
- * @param string delim
- * @return xp.compiler.TypeName[] the signature
- */
- protected function emitParameters($b, array $parameters, $delim) {
- $signature= array();
- $b->append('(');
- $s= sizeof($parameters)- 1;
- $defer= array();
- $usesGenerics= false;
- $genericParams= '';
- foreach ($parameters as $i => $param) {
- if (isset($param['assign'])) {
- if (null === ($field= $this->resolveType(new TypeName('self'))->getField($param['assign']))) {
- $this->error('F404', 'Method assignment parameter $this.'.$param['assign'].' references non-existant field');
- $t= TypeName::$VAR;
- } else {
- $t= $field->type;
- }
- $ptr= $this->resolveType($t);
- $param['name']= $param['assign'];
- $defer[]= '$this->'.$param['assign'].'= $'.$param['assign'].';';
- } else if (!$param['type']) {
- $t= TypeName::$VAR;
- $ptr= new TypeReference($t);
- } else {
- if (!$usesGenerics && $this->scope[0]->declarations[0]->name->isPlaceHolder($param['type'])) $usesGenerics= true;
- $t= $param['type'];
- $ptr= $this->resolveType($t);
- if (!$param['check'] || isset($param['vararg'])) {
- // No runtime type checks
- } else if ($t->isArray() || $t->isMap()) {
- $b->append('array ');
- } else if ($t->isClass() && !$this->scope[0]->declarations[0]->name->isPlaceHolder($t)) {
- $b->append($ptr->literal())->append(' ');
- } else if ('{' === $delim) {
- $defer[]= create(new Buffer('', $b->line))
- ->append('if (NULL !== $')->append($param['name'])->append(' && !is("'.$t->name.'", $')
- ->append($param['name'])
- ->append(')) throw new IllegalArgumentException("Argument ')
- ->append($i + 1)
- ->append(' passed to ".__METHOD__." must be of ')
- ->append($t->name)
- ->append(', ".xp::typeOf($')
- ->append($param['name'])
- ->append(')." given");')
- ;
- } else {
- // No checks in interfaces
- }
- }
- $signature[]= new Parameter($param['name'], new TypeName($ptr->name()), isset($param['default']) ? $param['default'] : null);
- $genericParams.= ', '.$t->compoundName();
- $this->metadata[0][1][$this->method[0]][DETAIL_ARGUMENTS][$i]= $ptr->name();
-
- if (isset($param['vararg'])) {
- $genericParams.= '...';
- if ($i > 0) {
- $defer[]= '$'.$param['name'].'= array_slice(func_get_args(), '.$i.');';
- } else {
- $defer[]= '$'.$param['name'].'= func_get_args();';
- }
- $this->scope[0]->setType(new VariableNode($param['name']), new TypeName($t->name.'[]'));
- break;
- }
-
- $b->append('$'.$param['name']);
- if (isset($param['default'])) {
- $b->append('= ');
- $resolveable= false;
- if ($param['default'] instanceof Resolveable) {
- try {
- $init= $param['default']->resolve();
- $b->append(var_export($init, true));
- $resolveable= true;
- } catch (\lang\IllegalStateException $e) {
- }
- }
- if (!$resolveable) {
- $b->append('NULL');
- $init= new Buffer('', $b->line);
- $init->append('if (func_num_args() < ')->append($i + 1)->append(') { ');
- $init->append('$')->append($param['name'])->append('= ');
- $this->emitOne($init, $param['default']);
- $init->append('; }');
- $defer[]= $init;
- }
- }
- $i < $s && !isset($parameters[$i+ 1]['vararg']) && $b->append(',');
-
- $this->scope[0]->setType(new VariableNode($param['name']), $t);
- }
- $b->append(')');
- $b->append($delim);
-
- foreach ($defer as $src) {
- $b->append($src);
- }
- if ($usesGenerics) {
- $this->metadata[0][1][$this->method[0]][DETAIL_ANNOTATIONS]['generic']['params']= substr($genericParams, 2);
- }
-
- return $signature;
- }
- /**
- * Emit annotations
- *
- * @param &var meta
- * @param xp.compiler.ast.AnnotationNode[] annotations
- */
- protected function emitAnnotations(&$meta, $annotations) {
- foreach ($annotations as $annotation) {
- $this->emitAnnotation($meta, $annotation);
- }
- }
- /**
- * Emit annotation
- *
- * @param &var meta
- * @param xp.compiler.ast.AnnotationNode lambda
- */
- protected function emitAnnotation(&$meta, $annotation) {
- $params= array();
- foreach ((array)$annotation->parameters as $name => $value) {
- if ($value instanceof ClassAccessNode) { // class literal
- $params[$name]= $this->resolveType($value->class)->name();
- } else if ($value instanceof Resolveable) {
- $params[$name]= $value->resolve();
- } else if ($value instanceof ArrayNode) {
- $params[$name]= array();
- foreach ($value->values as $element) {
- $element instanceof Resolveable && $params[$name][]= $element->resolve();
- }
- }
- }
- // Sort out where annotations should go
- if (isset($annotation->target)) {
- $ptr= &$meta[DETAIL_TARGET_ANNO][$annotation->target];
- } else {
- $ptr= &$meta[DETAIL_ANNOTATIONS];
- }
- // Set annotation value
- if (!$annotation->parameters) {
- $ptr[$annotation->type]= null;
- } else if (isset($annotation->parameters['default'])) {
- $ptr[$annotation->type]= $params['default'];
- } else {
- $ptr[$annotation->type]= $params;
- }
- }
-
- /**
- * Emit a lambda
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.compiler.ast.LambdaNode lambda
- * @see http://cr.openjdk.java.net/~mcimadamore/lambda_trans.pdf
- */
- protected function emitLambda($b, $lambda) {
- $unique= new TypeName('Lambdaии'.strtr(uniqid('', true), '.', 'и'));
-
- // Visit all statements, promoting local variable used within tp members
- $promoter= new LocalsToMemberPromoter();
- $parameters= $replaced= array();
- foreach ($lambda->parameters as $parameter) {
- $parameters[]= array('name' => $parameter->name, 'type' => TypeName::$VAR);
- $promoter->exclude($parameter->name);
- }
- $promoted= $promoter->promote($lambda);
-
- // Generate constructor
- $cparameters= $cstmt= $fields= array();
- foreach ($promoted['replaced'] as $name => $member) {
- $cparameters[]= array('name' => substr($name, 1), 'type' => TypeName::$VAR);
- $cstmt[]= new AssignmentNode(array(
- 'variable' => $member,
- 'expression' => new VariableNode(substr($name, 1)),
- 'op' => '='
- ));
- $fields[]= new FieldNode(array(
- 'name' => substr($name, 1),
- 'type' => TypeName::$VAR)
- );
- }
-
- // Generate an anonymous lambda class
- $decl= new ClassNode(0, null, $unique, null, null, array_merge($fields, array(
- new ConstructorNode(array(
- 'parameters' => $cparameters,
- 'body' => $cstmt
- )),
- new MethodNode(array(
- 'name' => 'invoke',
- 'parameters' => $parameters,
- 'body' => $promoted['node']->statements,
- 'returns' => TypeName::$VAR
- ))
- )));
- $decl->synthetic= true;
- $this->scope[0]->declarations[]= $decl;
-
- // Finally emit array(new [UNIQUE]([CAPTURE]), "method")
- $b->append('array(new '.$unique->name.'('.implode(',', array_keys($promoted['replaced'])).'), \'invoke\')');
- }
- /**
- * Emit a method
- *
- * @param xp.compiler.emit.Buffer b
- * @param xp.comp…
Large files files are truncated, but you can click here to view the full file