Skip to content

Commit

Permalink
Merge pull request #5 from jneuendorf/master
Browse files Browse the repository at this point in the history
Class method support, usage in the browser
  • Loading branch information
arximboldi committed Feb 27, 2017
2 parents 354e5bf + eed2318 commit 4d05651
Show file tree
Hide file tree
Showing 9 changed files with 308 additions and 83 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
coffeelint.json
coverage/
doc/
lib/
node_modules/
npm-debug.log
out/
test/heterarchy.spec.js
tmp/
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ node_js:
before_script:
- npm install
script:
- npm run-script lint
- npm run-script test
- npm run-script test-coverage
after_script: "cat ./coverage/lcov.info | ./node_modules/.bin/coveralls"
deploy:
Expand Down
64 changes: 59 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@ heterarchy

Cooperative multiple inheritance for CoffeeScript, à-la Python.

<a href="http://badge.fury.io/js/heterarchy"><img src="https://badge.fury.io/js/heterarchy.svg"/></a>
<a href="https://travis-ci.org/arximboldi/heterarchy"><img src="https://travis-ci.org/arximboldi/heterarchy.svg"/></a>
<a href="https://coveralls.io/r/arximboldi/heterarchy"><img src="https://coveralls.io/repos/arximboldi/heterarchy/badge.svg"/></a>
<a href="http://badge.fury.io/js/heterarchy">
<img src="https://badge.fury.io/js/heterarchy.svg"/>
</a>
<a href="https://travis-ci.org/arximboldi/heterarchy">
<img src="https://travis-ci.org/arximboldi/heterarchy.svg"/>
</a>
<a href="https://coveralls.io/r/arximboldi/heterarchy">
<img src="https://coveralls.io/repos/arximboldi/heterarchy/badge.svg"/>
</a>

Adds multiple inheritance support to CoffeeScript (and JavaScript).
It uses the C3 linearization algorithm as described in the [famous
Dylan paper](http://192.220.96.201/dylan/linearization-oopsla96.html).
Dylan paper](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.19.3910&rep=rep1&type=pdf).

Example
-------
Expand Down Expand Up @@ -50,7 +56,7 @@ Documentation
### API

* [heterarchy][heterarchy]
<br/>GitHub: [heterarchy.litcoffe](https://github.com/arximboldi/heterarchy/blob/master/heterarchy.litcoffee)
<br/>GitHub: [heterarchy.litcoffee](https://github.com/arximboldi/heterarchy/blob/master/heterarchy.litcoffee)

### Tests

Expand All @@ -68,6 +74,54 @@ install the library with:

> npm install heterarchy
### Usage in the browser

After cloning or downloading the repository
you can build all necessary files with

> make
This will create `browser.heterarchy.js` (and `heterarchy.js`)
in the `lib/` folder.
Including that file in the browser will result in a global
`heterarchy` variable that contains the normally exported functions.

Since the library requires `underscorejs`
you must include it before `heterarchy`.
The functions that are used from `underscorejs` are
(in case you don't need the entire library):

```
head, tail, map, find, some, without, isEmpty,
every, memoize, reject, isEqual, reduce
```

#### Example

```html
<script type="text/javascript" src="path/to/underscore.js"></script>
<script type="text/javascript" src="path/to/browser.heterarchy.js"></script>
<script type="text/javascript" src="my_app.coffee"></script>
```

where `my_app.coffee` could be

```coffee
class A
method: -> "A"

class B extends A
method: -> "B > #{super}"

class C extends A
method: -> "C > #{super}"

# note the heterarchy namespace
class D extends heterarchy.multi B, C
method: -> "D > #{super}"
```


License
-------

Expand Down
35 changes: 35 additions & 0 deletions coffeelint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"indentation" : {
"level" : "warn",
"value" : 4
},
"line_endings" : {
"value" : "unix",
"level" : "error"
},
"max_line_length": {
"value": 80,
"level": "warn"
},
"arrow_spacing": {
"level": "error"
},
"no_this": {
"level": "error"
},
"prefer_english_operator": {
"level": "error"
},
"space_operators" : {
"level": "warn"
},
"spacing_after_comma": {
"level": "error"
},
"ensure_comprehensions": {
"level": "ignore"
},
"no_nested_string_interpolation": {
"level": "ignore"
}
}
75 changes: 50 additions & 25 deletions heterarchy.litcoffee
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,46 @@ heterarchy
==========

> This file is part of [Heterarchy](http://sinusoid.es/heterarchy).
> - **View me [on a static web](http://sinusoid.es/heterarchy/heterarchy.html)**
> - **View me [on GitHub](https://github.com/arximboldi/heterarchy/blob/master/heterarchy.litcoffee)**
> - **View me [on a static web][sinusoid]**
> - **View me [on GitHub][gh]**

[sinusoid]: http://sinusoid.es/heterarchy/heterarchy.html
[gh]: https://github.com/arximboldi/heterarchy/blob/master/heterarchy.litcoffee

Adds multiple inheritance support to CoffeeScript (and JavaScript).
It uses the C3 linearization algorithm as described in the [famous
Dylan paper](http://192.220.96.201/dylan/linearization-oopsla96.html).

Flexible usage
--------------

Like [Underscore.js](http://underscorejs.org/) the module can be used
both with [Node.js](https://nodejs.org/en/) as well as in the browser.
Therefore, global variables are set accordingly.

# node.js
if typeof global is "object" and global?.global is global
root = global
exports = module.exports
_ = require "underscore"
# browser
else
root = window
exports = window.heterarchy = {}
_ = window._
Utilities
---------

`Underscore.js` is used to save lots of common-problem code and
`assert` is used upon an invalid inheritance hierarchy.

{head, tail, map, find, some, without, isEmpty, every, memoize, reject,
partial, isEqual, reduce} = require 'underscore'
isEqual, reduce} = _
assert = (value, error) ->
assert = (value, errorMessage) ->
if not value
throw new Error(if error? then error else "Assertion failed")
throw new Error(errorMessage)
Multiple inheritance
--------------------
Expand Down Expand Up @@ -53,7 +80,7 @@ maintained, implying `multi X, Y is multi X, Y`.
This takes a list of classes representing a hierarchy (from most to
least derived) and generates a single-inheritance hierarchy that
behaves like a class that would be have such a hierarchy.
behaves like a class that would have such a hierarchy.

generate = memoize (linearization) ->
next = head linearization
Expand All @@ -63,25 +90,23 @@ behaves like a class that would be have such a hierarchy.
class Result extends generate tail linearization
__mro__: linearization
constructor: reparent next, @, next::constructor
copyOwn next, @
copyOwn next::, @::, partial reparent, next, @
This utility lets us copy own properties of a *from* object that are
not own properties of a *to* object into the *to* object, optionally
transformed via a projection function.

copyOwn = (from, to, project = (x) -> x) ->
for own key, value of from
if not to.hasOwnProperty key
to[key] = project value
to
# 1. Fill up missing class attributes and
# 2. Adjust class methods (so the MRO is used).
# Those are already part of the class because when extending
# all class attributes are copied to `this` by CoffeeScript.
for own key, value of next
@[key] = reparent next, @, value
# fill up missing instance attributes
for own key, value of next::
if not @::hasOwnProperty key
@::[key] = reparent next, @, value
Methods in CoffeeScript call super directly, so we have to change the
`__super__` attribute of the original class during the scope of the
method so it calls the right super of the linearization. Also,
method so it calls the right super of the linearization. Also,
programmers don't call super in constructor of root classes --indeed
doing so would rise an error-- so we have to inject such a call when
there are classes after these in the linearization. The **reparent**
there are classes after these in the linearization. The **reparent**
function takes care of all these and given an original class, and the
new class that is replacing it a linearized heterarchy, returns a
wrapped copy of a value of the former that is suitable for replacing
Expand Down Expand Up @@ -130,7 +155,7 @@ The **mro** function returns the method resolution order
> ```

It returns the original classes that were mixed in when used with
mult-inherited classes:
multi-inherited classes:

> ```coffee
> class A
Expand Down Expand Up @@ -168,7 +193,7 @@ mult-inherited classes:
"Uint32Array"
"Float32Array"
"Float64Array"
# keyed Keyed collections
# Keyed collections
"Map"
"Set"
"WeakMap"
Expand All @@ -185,7 +210,7 @@ mult-inherited classes:
"Proxy"
]
javaScriptClasses = reduce javaScriptClassNames, (classes, name) ->
classes[global[name]] = global[name]
classes[root[name]] = root[name]
classes
, {}
isJavaScriptClass = (cls) ->
Expand All @@ -194,7 +219,7 @@ mult-inherited classes:
exports.mro = mro = (cls) ->
if not cls? or not cls::?
[]
else if not cls::hasOwnProperty '__mro__'
else if not cls::hasOwnProperty "__mro__"
result = [cls].concat mro inherited(cls)
cls::__mro__ = result unless isJavaScriptClass cls
result
Expand Down Expand Up @@ -223,7 +248,7 @@ object, not the next class in the MRO, as in:
The **hierarchy** returns the CoffeeScript hierarchy of classes of a
given class, including the class itself. For multiple inherited
classes, it may return speciall classes that were generated to produce
classes, it may return special classes that were generated to produce
the flattening, as in:

> ```coffee
Expand Down
51 changes: 32 additions & 19 deletions makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@
# Author: Juan Pedro Bolívar Puente <raskolnikov@es.gnu.org>
#

NODE_BIN = node_modules/.bin

NODEJS = node
NPM = npm
COFFEE = $(NODE_BIN)/coffee
DOCCO = $(NODE_BIN)/docco
MOCHA = $(NODE_BIN)/mocha
ISTANBUL = $(NODE_BIN)/istanbul
COFFEELINT = $(NODE_BIN)/coffeelint

SCRIPTS = \
NODE_BIN = node_modules/.bin

NODEJS = node
NPM = npm
COFFEE = $(NODE_BIN)/coffee
DOCCO = $(NODE_BIN)/docco
MOCHA = $(NODE_BIN)/mocha
MOCHA_PHANTOM = $(NODE_BIN)/mocha-phantomjs
ISTANBUL = $(NODE_BIN)/istanbul
COFFEELINT = $(NODE_BIN)/coffeelint
PHANTOM_JS = `which phantomjs`

SCRIPTS = \
lib/heterarchy.js \

DOCS = \
DOCS = \
doc/index.html \
doc/heterarchy.html \
doc/test/heterarchy.spec.html
Expand All @@ -34,19 +36,19 @@ doc: $(DOCS)

lib/%.js: %.litcoffee
@mkdir -p $(@D)
$(COFFEE) -c -p $< > $@
$(COFFEE) --compile --print $< > $@

lib/%.js: %.coffee
@mkdir -p $(@D)
$(COFFEE) -c -p $< > $@
$(COFFEE) --compile --print $< > $@

lib/%.js: %.js
@mkdir -p $(@D)
cp -f $< $@

doc/index.html: README.md
@mkdir -p $(@D)
$(DOCCO) -t docco/docco.jst -c docco/docco.css -o $(@D) $<
$(DOCCO) -t docco/docco.jst -c docco/docco.css -o $(@D) $<
mv $(@D)/README.html $@
cp -rf docco/public $(@D)

Expand All @@ -73,16 +75,27 @@ clean:
install:
$(NPM) install

test:
$(MOCHA) --compilers coffee:coffee-script/register
test: all
$(MOCHA) --compilers coffee:coffee-script/register `find test -name *.coffee`
@$(COFFEE) --compile test/heterarchy.spec.coffee
@# if phantomjs is actually installed (not just as a node module) use that
@# because there is a bug on macOS with the node module
@if [[ $(PHANTOM_JS) = "" ]]; then \
echo "$(MOCHA_PHANTOM) test/heterarchy.browser.html"; \
$(MOCHA_PHANTOM) test/heterarchy.browser.html; \
else \
echo "$(MOCHA_PHANTOM) -p $(PHANTOM_JS) test/heterarchy.browser.html"; \
$(MOCHA_PHANTOM) -p $(PHANTOM_JS) test/heterarchy.browser.html; \
fi

lint:
$(COFFEELINT) --literate heterarchy.litcoffee
$(COFFEELINT) test/heterarchy.spec.coffee

test-coverage:
test-coverage: all
$(MOCHA) --compilers coffee:coffee-script/register \
--require coffee-coverage/register-istanbul
--require coffee-coverage/register-istanbul \
`find test -name *.coffee`
$(ISTANBUL) report text lcov

travis: lint test-coverage
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@
"coveralls": "2.10.0",
"docco": ">=0.6",
"istanbul": "0.3.5",
"mocha": "^2.2.5"
"mocha": "^2.2.5",
"mocha-phantomjs": "^4.1.0"
},
"scripts": {
"test": "make test",
"test-coverage": "make test-coverage",
"lint": "make lint",
"travis": "make travis",
"prepublish": "make",
"pretest": "make"
Expand Down
Loading

0 comments on commit 4d05651

Please sign in to comment.