From 81d7f86b935cad1cd65c22b2681ba09920e2b549 Mon Sep 17 00:00:00 2001 From: Pavel Talanov Date: Wed, 16 Nov 2011 19:37:03 +0400 Subject: [PATCH] Worked on kotlinJsLib and supported trait extending other trait. --- .../k2js/translate/ClassTranslator.java | 30 +- .../org/jetbrains/k2js/translate/Namer.java | 8 +- .../jetbrains/k2js/test/KotlinLibTest.java | 18 +- .../jetbrains/k2js/test/TranslationTest.java | 5 + translator/testFiles/kotlinLib/cases/trait.js | 12 + translator/testFiles/kotlin_lib.js | 21 +- translator/testFiles/prototype.js | 388 ++++++++++++++++++ 7 files changed, 453 insertions(+), 29 deletions(-) create mode 100644 translator/testFiles/kotlinLib/cases/trait.js create mode 100644 translator/testFiles/prototype.js diff --git a/translator/src/org/jetbrains/k2js/translate/ClassTranslator.java b/translator/src/org/jetbrains/k2js/translate/ClassTranslator.java index 3857087688e..38c0987c336 100644 --- a/translator/src/org/jetbrains/k2js/translate/ClassTranslator.java +++ b/translator/src/org/jetbrains/k2js/translate/ClassTranslator.java @@ -31,34 +31,20 @@ public final class ClassTranslator extends AbstractTranslator { } @NotNull - public JsStatement translateClass(@NotNull JetClass classDeclaration) { - if (!classDeclaration.isTrait()) { - return translateAsClassWithState(classDeclaration); - } else { - return translateAsStatelessTrait(classDeclaration); - } - } - - @NotNull - private JsStatement translateAsStatelessTrait(@NotNull JetClass classDeclaration) { - JsObjectLiteral traitLiteral = translateClassDeclarations(classDeclaration); - return AstUtil.convertToStatement - (AstUtil.newAssignment(namespaceQualifiedClassNameReference(classDeclaration), traitLiteral)); - } - - @NotNull - private JsStatement translateAsClassWithState(@NotNull JetClass jetClassDeclaration) { - JsInvocation jsClassDeclaration = createMethodInvocation(); + public JsStatement translateClass(@NotNull JetClass jetClassDeclaration) { + JsInvocation jsClassDeclaration = classCreateMethodInvocation(jetClassDeclaration); addSuperclassReferences(jetClassDeclaration, jsClassDeclaration); addClassOwnDeclarations(jetClassDeclaration, jsClassDeclaration); return classDeclarationStatement(jetClassDeclaration, jsClassDeclaration); } @NotNull - private JsInvocation createMethodInvocation() { - JsInvocation jsClassDeclaration = new JsInvocation(); - jsClassDeclaration.setQualifier(Namer.creationMethodReference()); - return jsClassDeclaration; + private JsInvocation classCreateMethodInvocation(@NotNull JetClass jetClassDeclaration) { + if (jetClassDeclaration.isTrait()) { + return AstUtil.newInvocation(Namer.traitCreationMethodReference()); + } else { + return AstUtil.newInvocation(Namer.classCreationMethodReference()); + } } @NotNull diff --git a/translator/src/org/jetbrains/k2js/translate/Namer.java b/translator/src/org/jetbrains/k2js/translate/Namer.java index c792d03fd70..676ff9f2a48 100644 --- a/translator/src/org/jetbrains/k2js/translate/Namer.java +++ b/translator/src/org/jetbrains/k2js/translate/Namer.java @@ -58,14 +58,18 @@ public final class Namer { return name; } + //TODO: dummy public static JsNameRef classObjectReference() { - //TODO dummy return AstUtil.newQualifiedNameRef("Class"); } - public static JsNameRef creationMethodReference() { + public static JsNameRef classCreationMethodReference() { return AstUtil.newQualifiedNameRef("Class.create"); } + public static JsNameRef traitCreationMethodReference() { + return AstUtil.newQualifiedNameRef("Trait.create"); + } + } diff --git a/translator/test/org/jetbrains/k2js/test/KotlinLibTest.java b/translator/test/org/jetbrains/k2js/test/KotlinLibTest.java index bf589819f46..249a9ba7c2c 100644 --- a/translator/test/org/jetbrains/k2js/test/KotlinLibTest.java +++ b/translator/test/org/jetbrains/k2js/test/KotlinLibTest.java @@ -42,12 +42,26 @@ public class KotlinLibTest extends TranslationTest { @Test public void classObjectHasCreateMethod() throws Exception { - String objectName = "Class"; final Map> propertyToType = new HashMap>(); propertyToType.put("create", Function.class); - runPropertyTypeCheck(objectName, propertyToType); + runPropertyTypeCheck("Class", propertyToType); } + @Test + public void traitObjectHasCreateMethod() throws Exception { + final Map> propertyToType + = new HashMap>(); + propertyToType.put("create", Function.class); + runPropertyTypeCheck("Trait", propertyToType); + } + + @Test + public void createdTraitIsJSObject() throws Exception { + final Map> propertyToType + = new HashMap>(); + runRhinoTest(Arrays.asList(kotlinLibraryPath(), cases("trait.js")), + new RhinoPropertyTypesChecker("foo", propertyToType)); + } } diff --git a/translator/test/org/jetbrains/k2js/test/TranslationTest.java b/translator/test/org/jetbrains/k2js/test/TranslationTest.java index 600b54cc3b4..8263ca5c11b 100644 --- a/translator/test/org/jetbrains/k2js/test/TranslationTest.java +++ b/translator/test/org/jetbrains/k2js/test/TranslationTest.java @@ -56,6 +56,7 @@ public abstract class TranslationTest { abstract protected List generateFilenameList(String inputfile); + //TODO: refactor filename generation logic private String getOutputFilePath(String filename) { return getOutputDirectory() + convertToDotJsFile(filename); } @@ -68,6 +69,10 @@ public abstract class TranslationTest { return getInputDirectory() + filename; } + protected String cases(String filename) { + return getInputFilePath(filename); + } + protected void runFileWithRhino(String inputFile, Context context, Scriptable scope) throws Exception { FileReader reader = new FileReader(inputFile); context.evaluateReader(scope, reader, inputFile, 1, null); diff --git a/translator/testFiles/kotlinLib/cases/trait.js b/translator/testFiles/kotlinLib/cases/trait.js new file mode 100644 index 00000000000..f1efec21a20 --- /dev/null +++ b/translator/testFiles/kotlinLib/cases/trait.js @@ -0,0 +1,12 @@ +foo = {}; +(function(foo){ + foo.Test = Trait.create({addFoo:function(s){ + return s + 'FOO'; + } + }); + foo.ExtendedTest = Trait.create(foo.Test, {hooray:function(){ + return 'hooray'; + } + }); +} +(foo)); diff --git a/translator/testFiles/kotlin_lib.js b/translator/testFiles/kotlin_lib.js index 8c0e002e123..2e100bfc744 100644 --- a/translator/testFiles/kotlin_lib.js +++ b/translator/testFiles/kotlin_lib.js @@ -66,8 +66,6 @@ var Class = (function() { return function() { return ancestor[m].apply(this, arguments); }; })(property).wrap(method); - // value.valueOf = method.valueOf.bind(method); - // value.toString = method.toString.bind(method); } this.prototype[property] = value; } @@ -85,8 +83,25 @@ var Class = (function() { var Trait = (function() { + + function add(object, source) { + properties = Object.keys(source); + for (var i = 0, length = properties.length; i < length; i++) { + var property = properties[i]; + var value = source[property]; + object[property] = value; + } + return this; + } + function create() { - return new Class.create(arguments); + + result = {} + for (var i = 0, length = arguments.length; i < length; i++) + { + add(result, arguments[i]); + } + return result; } return { diff --git a/translator/testFiles/prototype.js b/translator/testFiles/prototype.js new file mode 100644 index 00000000000..8de2bd31b17 --- /dev/null +++ b/translator/testFiles/prototype.js @@ -0,0 +1,388 @@ +function $A(iterable) { + if (!iterable) return []; + if ('toArray' in Object(iterable)) return iterable.toArray(); + var length = iterable.length || 0, results = new Array(length); + while (length--) results[length] = iterable[length]; + return results; +} + +var emptyFunction = function() {} + +var Class = (function() { + + var IS_DONTENUM_BUGGY = (function(){ + for (var p in { toString: 1 }) { + if (p === 'toString') return false; + } + return true; + })(); + + function subclass() {}; + function create() { + var parent = null, properties = $A(arguments); + if (Object.isFunction(properties[0])) + parent = properties.shift(); + + function klass() { + this.initialize.apply(this, arguments); + } + + Object.extend(klass, Class.Methods); + klass.superclass = parent; + klass.subclasses = []; + + if (parent) { + subclass.prototype = parent.prototype; + klass.prototype = new subclass; + parent.subclasses.push(klass); + } + + for (var i = 0, length = properties.length; i < length; i++) + klass.addMethods(properties[i]); + + if (!klass.prototype.initialize) + klass.prototype.initialize = emptyFunction; + + klass.prototype.constructor = klass; + return klass; + } + + function addMethods(source) { + var ancestor = this.superclass && this.superclass.prototype, + properties = Object.keys(source); + + if (IS_DONTENUM_BUGGY) { + if (source.toString != Object.prototype.toString) + properties.push("toString"); + if (source.valueOf != Object.prototype.valueOf) + properties.push("valueOf"); + } + + for (var i = 0, length = properties.length; i < length; i++) { + var property = properties[i], value = source[property]; + if (ancestor && Object.isFunction(value) && + value.argumentNames()[0] == "$super") { + var method = value; + value = (function(m) { + return function() { return ancestor[m].apply(this, arguments); }; + })(property).wrap(method); + + value.valueOf = method.valueOf.bind(method); + value.toString = method.toString.bind(method); + } + this.prototype[property] = value; + } + + return this; + } + + return { + create: create, + Methods: { + addMethods: addMethods + } + }; +})(); + +var Trait = (function() { + + function create() { + var traitClass = Class.create.apply(Class, arguments) + return new traitClass; + } + + return { + create: create + }; +})(); + +(function() { + + var _toString = Object.prototype.toString, + NULL_TYPE = 'Null', + UNDEFINED_TYPE = 'Undefined', + BOOLEAN_TYPE = 'Boolean', + NUMBER_TYPE = 'Number', + STRING_TYPE = 'String', + OBJECT_TYPE = 'Object', + FUNCTION_CLASS = '[object Function]', + BOOLEAN_CLASS = '[object Boolean]', + NUMBER_CLASS = '[object Number]', + STRING_CLASS = '[object String]', + ARRAY_CLASS = '[object Array]', + DATE_CLASS = '[object Date]'; + + function Type(o) { + switch(o) { + case null: return NULL_TYPE; + case (void 0): return UNDEFINED_TYPE; + } + var type = typeof o; + switch(type) { + case 'boolean': return BOOLEAN_TYPE; + case 'number': return NUMBER_TYPE; + case 'string': return STRING_TYPE; + } + return OBJECT_TYPE; + } + + function extend(destination, source) { + for (var property in source) + destination[property] = source[property]; + return destination; + } + + function inspect(object) { + try { + if (isUndefined(object)) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : String(object); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + } + + function toJSON(value) { + return Str('', { '': value }, []); + } + + function Str(key, holder, stack) { + var value = holder[key], + type = typeof value; + + if (Type(value) === OBJECT_TYPE && typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + + var _class = _toString.call(value); + + switch (_class) { + case NUMBER_CLASS: + case BOOLEAN_CLASS: + case STRING_CLASS: + value = value.valueOf(); + } + + switch (value) { + case null: return 'null'; + case true: return 'true'; + case false: return 'false'; + } + + type = typeof value; + switch (type) { + case 'string': + return value.inspect(true); + case 'number': + return isFinite(value) ? String(value) : 'null'; + case 'object': + + for (var i = 0, length = stack.length; i < length; i++) { + if (stack[i] === value) { throw new TypeError(); } + } + stack.push(value); + + var partial = []; + if (_class === ARRAY_CLASS) { + for (var i = 0, length = value.length; i < length; i++) { + var str = Str(i, value, stack); + partial.push(typeof str === 'undefined' ? 'null' : str); + } + partial = '[' + partial.join(',') + ']'; + } else { + var keys = Object.keys(value); + for (var i = 0, length = keys.length; i < length; i++) { + var key = keys[i], str = Str(key, value, stack); + if (typeof str !== "undefined") { + partial.push(key.inspect(true)+ ':' + str); + } + } + partial = '{' + partial.join(',') + '}'; + } + stack.pop(); + return partial; + } + } + + function stringify(object) { + return JSON.stringify(object); + } + + function toQueryString(object) { + return $H(object).toQueryString(); + } + + function toHTML(object) { + return object && object.toHTML ? object.toHTML() : String.interpret(object); + } + + function keys(object) { + if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); } + var results = []; + for (var property in object) { + if (object.hasOwnProperty(property)) { + results.push(property); + } + } + return results; + } + + function values(object) { + var results = []; + for (var property in object) + results.push(object[property]); + return results; + } + + function clone(object) { + return extend({ }, object); + } + + function isElement(object) { + return !!(object && object.nodeType == 1); + } + + function isArray(object) { + return _toString.call(object) === ARRAY_CLASS; + } + + var hasNativeIsArray = (typeof Array.isArray == 'function') + && Array.isArray([]) && !Array.isArray({}); + + if (hasNativeIsArray) { + isArray = Array.isArray; + } + + function isHash(object) { + return object instanceof Hash; + } + + function isFunction(object) { + return _toString.call(object) === FUNCTION_CLASS; + } + + function isString(object) { + return _toString.call(object) === STRING_CLASS; + } + + function isNumber(object) { + return _toString.call(object) === NUMBER_CLASS; + } + + function isDate(object) { + return _toString.call(object) === DATE_CLASS; + } + + function isUndefined(object) { + return typeof object === "undefined"; + } + + extend(Object, { + extend: extend, + inspect: inspect, + toQueryString: toQueryString, + toHTML: toHTML, + keys: Object.keys || keys, + values: values, + clone: clone, + isElement: isElement, + isArray: isArray, + isHash: isHash, + isFunction: isFunction, + isString: isString, + isNumber: isNumber, + isDate: isDate, + isUndefined: isUndefined + }); +})(); + + +Object.extend(Function.prototype, (function() { + var slice = Array.prototype.slice; + + function update(array, args) { + var arrayLength = array.length, length = args.length; + while (length--) array[arrayLength + length] = args[length]; + return array; + } + + function merge(array, args) { + array = slice.call(array, 0); + return update(array, args); + } + + function argumentNames() { + var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1] + .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '') + .replace(/\s+/g, '').split(','); + return names.length == 1 && !names[0] ? [] : names; + } + + function bind(context) { + if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; + var __method = this, args = slice.call(arguments, 1); + return function() { + var a = merge(args, arguments); + return __method.apply(context, a); + } + } + + function bindAsEventListener(context) { + var __method = this, args = slice.call(arguments, 1); + return function(event) { + var a = update([event || window.event], args); + return __method.apply(context, a); + } + } + + function curry() { + if (!arguments.length) return this; + var __method = this, args = slice.call(arguments, 0); + return function() { + var a = merge(args, arguments); + return __method.apply(this, a); + } + } + + function delay(timeout) { + var __method = this, args = slice.call(arguments, 1); + timeout = timeout * 1000; + return window.setTimeout(function() { + return __method.apply(__method, args); + }, timeout); + } + + function defer() { + var args = update([0.01], arguments); + return this.delay.apply(this, args); + } + + function wrap(wrapper) { + var __method = this; + return function() { + var a = update([__method.bind(this)], arguments); + return wrapper.apply(this, a); + } + } + + function methodize() { + if (this._methodized) return this._methodized; + var __method = this; + return this._methodized = function() { + var a = update([this], arguments); + return __method.apply(null, a); + }; + } + + return { + argumentNames: argumentNames, + bind: bind, + bindAsEventListener: bindAsEventListener, + curry: curry, + delay: delay, + defer: defer, + wrap: wrap, + methodize: methodize + } +})());