// ex: set sts=4 sw=4 expandtab: /** * Class definition analyzer. * Copyright (c) 2008-2013 Madis Janson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package yeti.lang.compiler; import java.util.List; import java.util.ArrayList; import yeti.renamed.asmx.Opcodes; final class MethodDesc extends YetiType { Binder[] arguments; String[] names; JavaClass.Meth method; Node[] m; boolean isStatic; MethodDesc(JavaClass.Meth method, Node argList, Scope scope) { this.method = method; Node[] args = ((XNode) argList).expr; arguments = new Binder[args.length / 2]; names = new String[arguments.length]; for (int i = 0, j = 0; i < arguments.length; ++i, j += 2) { String name = args[j + 1].sym(); for (int k = 0; k < i; ++k) if (name == names[k]) { throw new CompileException(args[j + 1], "Duplicate argument name (" + name + ")"); } names[i] = name; arguments[i] = method.addArg(JavaType.typeOfName(args[j].sym(), scope)); } } Scope bindScope(Scope scope, JavaClass regField, Scope fields[]) { for (int i = 0; i < arguments.length; ++i) { scope = new Scope(scope, names[i], arguments[i]); if (regField != null) { Binder field = regField.addField(arguments[i].getRef(0), false, null); fields[0] = new Scope(fields[0], names[i], field); } } return scope; } private void check(YType t, Node node, String packageName) { while (t.type == YetiType.JAVA_ARRAY) t = t.param[0]; if (t.type == YetiType.JAVA && t.javaType.description.charAt(0) == 'L') t.javaType.resolve(node).checkPackage(node, packageName); } void check(Node argList, String packageName) { Node[] args = ((XNode) argList).expr; for (int i = 0; i < method.arguments.length; ++i) { check(method.arguments[i], args[i], packageName); } if (m != null) // constructors don't have m check(method.returnType, m[0], packageName); } void init(Scope mscope, int depth) { Scope bodyScope = bindScope(mscope, null, null); if (bodyScope == mscope) bodyScope = new Scope(bodyScope, null, null); bodyScope.closure = method; // for bind var collection method.code = YetiAnalyzer.analyze(m[3], bodyScope, depth); if (JavaType.isAssignable(m[3], method.returnType, method.code.type, true) < 0) { try { unify(method.code.type, method.returnType); } catch (TypeException ex) { throw new CompileException(m[3], "Cannot return " + method.code.type + " as " + method.returnType); } } } static final class LocalClassBinding extends ClassBinding { private List proxies; private LocalClassBinding next; private BindRef[] captures; LocalClassBinding(YType classType) { super(classType); } BindRef[] getCaptures() { if (captures == null) throw new IllegalStateException("Captures not initialized"); return captures; } ClassBinding dup(List proxies) { LocalClassBinding r = new LocalClassBinding(type); r.proxies = proxies; if (captures != null) { r.proxy(captures); } else { r.next = next; next = r; } return r; } private void proxy(BindRef[] captures) { if (captures.length == 0 || proxies.size() == 0) { this.captures = captures; return; } BindRef[] ca = new BindRef[captures.length]; System.arraycopy(captures, 0, ca, 0, captures.length); // proxys were collected in reverse order (inner-first) for (int i = proxies.size(); --i >= 0;) { Closure c = (Closure) proxies.get(i); for (int j = 0; j < ca.length; ++j) { ca[j] = c.refProxy(ca[j]); } } this.captures = ca; proxies = null; } void init(BindRef[] captures) { LocalClassBinding cb = this; while ((cb = cb.next) != null) cb.proxy(captures); this.captures = captures; } } /* * (:class Foo (:argument-list int x) (:extends Object (:arguments)) * (foo = x) * (:method String getBlaah (:argument-list int y) (:concat (x + y)))) */ static JavaClass defineClass(XNode cl, boolean topLevel, Scope[] scope_, int depth) { Scope scope = new Scope(scope_[0], null, null); String className = cl.expr[0].sym(); String packageName = scope.ctx.packageName; Compiler cctx = scope.ctx.compiler; if (!topLevel) { className = cctx.createClassName(null, scope.ctx.className, className); } else if (packageName != null && packageName.length() != 0) { className = packageName + '/' + className; } cctx.addClass(className, null, cl.line); JavaClass c = new JavaClass(className, topLevel, cl.line); scope.closure = c; // to proxy super-class closures ClassBinding parentClass = null; Node[] extend = ((XNode) cl.expr[2]).expr; Node[] superArgs = null; Node superNode = cl; // collect parent class / interfaces (extends) List interfaces = new ArrayList(); for (int i = 0; i < extend.length; i += 2) { ClassBinding cb = resolveFullClass(extend[i].sym(), scope, true, extend[i]); JavaType jt = cb.type.javaType.resolve(extend[i]); jt.checkPackage(extend[i], packageName); Node[] args = ((XNode) extend[i + 1]).expr; if (jt.isInterface()) { if (args != null) throw new CompileException(extend[i + 1], "Cannot give arguments to interface"); interfaces.add(jt.className()); } else if (parentClass != null) { throw new CompileException(extend[i], "Cannot extend multiple non-interface classes (" + parentClass.type.javaType.dottedName() + " and " + jt.dottedName() + ')'); } else { parentClass = cb; superArgs = args; superNode = extend[i]; } } if (parentClass == null) parentClass = new ClassBinding(OBJECT_TYPE); parentClass.type.javaType.resolve(cl); c.init(parentClass, (String[]) interfaces.toArray(new String[interfaces.size()])); scope = new Scope(scope_[0], cl.expr[0].sym(), null); LocalClassBinding binding = new LocalClassBinding(c.classType); scope.importClass = binding; scope_[0] = scope; MethodDesc consDesc = new MethodDesc(c.constr, cl.expr[1], scope); // method defs List methods = new ArrayList(); for (int i = 3; i < cl.expr.length; ++i) { String kind = cl.expr[i].kind; if (kind != "method" && kind != "static-method" && kind != "abstract-method") continue; Node[] m = ((XNode) cl.expr[i]).expr; YType returnType = m[0].sym() == "void" ? UNIT_TYPE : JavaType.typeOfName(m[0].sym(), scope); JavaClass.Meth meth = c.addMethod(m[1].sym(), returnType, kind, m.length > 3 ? m[3].line : 0); MethodDesc md = new MethodDesc(meth, m[2], scope); if (kind == "abstract-method") continue; md.m = m; if ((md.isStatic = kind != "method") && !topLevel) { throw new CompileException(cl.expr[i], "Static methods are " + "allowed only in classes defined in the module top-level"); } methods.add(md); } try { c.close(); } catch (JavaClassNotFoundException ex) { throw new CompileException(cl, ex); } consDesc.check(cl.expr[1], scope.ctx.packageName); for (int i = 0, cnt = methods.size(); i < cnt; ++i) { MethodDesc md = (MethodDesc) methods.get(i); md.check(md.m[2], scope.ctx.packageName); } c.classType.javaType.checkAbstract(); // constructor arguments Scope staticScope = new Scope(scope, null, null); staticScope.closure = c; Scope[] localRef = { staticScope }; Scope consScope = consDesc.bindScope(staticScope, c, localRef); Scope local = localRef[0]; if (superArgs == null) superArgs = new Node[0]; Code[] initArgs = YetiAnalyzer.mapArgs(0, superArgs, consScope, depth); JavaType.Method superCons = JavaType.resolveConstructor(superNode, parentClass.type, initArgs, false) .check(superNode, packageName, Opcodes.ACC_PROTECTED); c.superInit(superCons, initArgs, superNode.line); local = new Scope(local, "super", c.superRef); // field defs for (int i = 3; i < cl.expr.length; ++i) { if (cl.expr[i] instanceof Bind) { Bind bind = (Bind) cl.expr[i]; if (bind.property) { throw new CompileException(bind, "Class field cannot be a property"); } Binder binder; Code code; if (bind.expr.kind == "lambda" && bind.name != "_") { Function lambda = new Function(new YType(depth + 1)); lambda.selfBind = binder = c.addField(lambda, bind.var, null); // binding this for lambdas is unsafe, but useful Scope funScope = new Scope(local, "this", c.self); if (!bind.noRec) funScope = new Scope(funScope, bind.name, binder); YetiAnalyzer.lambdaBind(lambda, bind, funScope, depth + 1); code = lambda; } else { code = YetiAnalyzer.analyze(bind.expr, local, depth + 1); binder = c.addField(code, bind.var, bind.name); if (bind.type != null) YetiAnalyzer.isOp(bind, bind.type, code, scope, depth); } if (bind.name != "_") { local = bind(bind.name, code.type, binder, bind.var ? RESTRICT_ALL : code.polymorph ? RESTRICT_POLY : 0, depth, local); } } } local = new Scope(local, "this", c.self); // analyze method bodies for (int i = 0, cnt = methods.size(); i < cnt; ++i) { MethodDesc md = (MethodDesc) methods.get(i); md.init(md.isStatic ? staticScope : local, depth); } binding.init(c.getCaptures()); return c; } }