koffee

1.20.0 • Public • Published

banner

koffee is a clone of Coffeescript with a few enhancements.

Constructor shortcut

class C
    @: ->           

... is an optional shortcut for ...

class C          
    constructor: -> 

Negative indexing

s = "abcde"

s[-1]        # -> 'e'
"abcde"[-2]  # -> 'd'
('cde')[-3]  # -> 'c'
[1,2,3][-2]  # -> 2

a = -2
s[a]         # -> undefined

v[-n] is a shortcut for v[-n..-n][0] for number literals n. Passing variables with negative values still returns undefined.

for ... in

for i in 0..5
    (a for a in 1+2...8) 

Square brackets around ranges in for ... in loops are optional.

Console shortcuts

log 'hello'  # -> hello
warn 'world' # -> world
error '!'    # -> !

Simple shortcuts for log, warn and error methods of console.

By the way, did I mention that all koffee features are individually toggleable? E.g., this one can be deactivated by passing the commandline flag --no-console-shortcut to the koffee command or by setting a flag in the options when calling module methods:

koffee = require 'koffee'
koffee.compile code, feature:console_shortcut:false

BigInt

a = 12345678901234567890n 
log a*a  # -> 152415787532388367501905199875019052100n

Optional commata

Coffeescript has a very nice way of initializing arrays:

a = [
   1
   2
   3
]

If you decide to join these into a single line, you have a problem: for each of the lines a comma must be inserted. The same goes for objects that span over multiple lines.

In koffee, you don't need to insert commata after number or string primitives and POD structures. Those are all valid expressions:

a = [ 1 2 3 ]           
a = { b:1 c:2 d:3 }   
a =   b:1 c:2 d:3
a =   b:[ c:2 'd' 3 ]  
a = [ [1 2] [d:3] ]

log 'a:' a , 'd:' 3      # some commas make sense :-)

describe 'something' ->
    it 'should' ->
    
on 'event' ->
on 'event' @myCallback

We are probably reaching the limits of minimalism here :)

Meta If

koffee allows you to control which blocks of code get compiled into JavaScript:

if false               # this block won't be compiled to .js
    code to             # 
    exclude from        # 
    compilation         # elif true              # this branch is switched on, so
    log 'hello'         # + these two lines 
    log 'world'         # + will be compiledelse                   # another ignored block
    null                # 

You can provide code in the condition, which will be evaluated at compile time

if os.platform()=='darwin'then log 'mac'else process.exit 0

will compile to

console.log("mac");

on macOS, on other platforms it will produce

process.exit(0);

Macros

If the condition part of ▸if starts with a , a lookup will be made into the compile options meta map. If a matching entry is found, the provided coffeescript blocks can be modified arbitrarily before they are written to JavaScript. When the macro is only operating on one block of code, you can omit the leading ▸if.

The default map includes some simple but useful macros that demonstrate this feature.

                                # log file position and objectdbg 'my object' a:1 b:2        # -> file:1 my object { a: 1, b: 2 }assert 'message' condition     # log file position and message and exits if condition isn't truishassert condition               # similar, but with 'assertion failure!' as the message
                                
                                # log execution times  profile 'sum'                  # -> 8_4 1ms          line_column prefixprofile s1()               # -> 9_4 2ms          if not namedprofile s2()               # -> sum 3msaverage 123                    # execute f and g 123 times and report average time
    f()                         # -> 11_0 321μs
    g()
    
▸start 'a'                      # like ▸profile, but lets you control
f = ->end 'a'                 # when to start and stop timing
f()                             # -> a 824μstest 'info'                    # will only be compiled if --test flag is set
    myTest()                    # includes logs for each test block

The unicode symbol can be substituted by ~>.

▸doc

This is a slightly special macro, because it's first pass is hardcoded in the tokenizer. The body of the macro is wrapped in a skinny triple string before further tokenization. It can contain anything except a '''.

In normal operation, it is reduced to the empty string. But if koffee is called with the --doc argument, logs will be inserted instead.

doc 'title'
    my documentation ...
log 'some code'
▸doc
    we are done.

koffee --doc file will output

## title
my documentation ...
some code
we are done.

but koffee file will only print some code

Config arguments

Still here? Nice!

The last feature might be easier to understand with a little bit of motivation up front:

Let's assume we need a function f, whose behavior should be configurable via it's arguments. Let's also assume that the configuration will probably grow or change a lot over time. It makes sense to use a config dictionary as the argument:

f = (cfg = a:1, b:2) -> log cfg

The default values are nicely visible, let's use it:

f()           # -> { a: 1, b: 2 }        
f a:4, b:8    # -> { a: 4, b: 8 }        All good so far.
f b:8, a:4    # -> { b: 8, a: 4 }        We can even change the order, nice!
                                         
f b:8         # -> { b: 8 }              Oops!

Crap! We have to provide all the arguments? That sucks!

We need a better solution. Let's use the destructuring feature of Coffeescript:

f = ({a=1, b=2}) -> log {a, b}   

The arguments look a bit ugly, and we need to provide an empty dictionary for the default behavior. But hey, we got nice variable names inside f now and it works as intended:

f {}          # -> { a: 1, b: 2 }  
f a:4, b:8    # -> { a: 4, b: 8 }  
f      b:8    # -> { a: 1, b: 8 }  
f a:8         # -> { a: 8, b: 2 }

Shortcut

koffee provides a nifty shortcut for this use case:

f = (a:1, b:2) -> log {a, b}  # Look, ma! The uglyness is gone :-)

The default values can be omitted:

f = (a:1, b:) -> log {a, b}

f {}          # -> { a: 1, b: null }
f b:2         # -> { a: 1, b: 2 }

Inheritance and super

Let's see what happens if we use this feature for method arguments.

class Base
    @: (@a:'Base') -> log @

class A extends Base
    @: (@a:'A') -> super

new A {}      # -> A { a: 'A' }

Note that the default value of a in A is not overridden by the one in Base.

Here the behavior of a super call without brackets differs from that of Coffeescript. While the original applies the arguments unmodified (and thereby missing the destructuring assignment), koffee does internally modify the arguments by inserting

arguments[0] = _.defaults {a:@a}, arguments[0]

immediately before applying super.

This will throw at runtime, if _.defaults is not defined. koffee simply assumes that either underscore or lodash is used.

Compatibility

So far, the koffee compiler output should be compatible with the latest version of Coffeescript on the version 1 branch.

The added features only use syntax that was invalid in CS1, so koffee should be able to compile any valid CS1 code. CS2 code should compile as well, but this is untested.

Some features have been changed or removed:

  • literal coffeescript is gone
  • kake and koffee work slightly different than cake and coffee
  • internal option parser replaced with nopt

npm package Build Status downloads

/koffee/

    Package Sidebar

    Install

    npm i koffee

    Weekly Downloads

    28

    Version

    1.20.0

    License

    MIT

    Unpacked Size

    1.41 MB

    Total Files

    22

    Last publish

    Collaborators

    • monsterkodi