You can think of Coffee-AHK
as a dialect of CoffeeScript
. It is compatible with existing AHK
code and adds new features like anonymous functions
, destructuring assignment
, and even package management support
. It is also a subset of CoffeeScript
that correctly compiles to JavaScript
code and runs on all platforms.
Latest version: 0.0.52
Coffee-AHK
at the top, compiled AHK
output at the bottom.
# assignment:
number = 42
opposite = true
# conditions:
if opposite then number = -42
# functions:
square = (x) -> x * x
# arrays:
list = [1, 2, 3, 4, 5]
# object:
math =
root: Math.sqrt
square: square
cube: (x) -> x * square x
# splats:
race = (winner, runners...) ->
print winner, runners
global number := 42
global opposite := true
if (opposite) {
number := -42
}
global square := Func("ahk_3")
global list := [1, 2, 3, 4, 5]
global math := {root: Math.sqrt, square: square, cube: Func("ahk_2")}
global race := Func("ahk_1").Bind(print)
ahk_1(print, winner, runners*) {
print.Call(winner, runners)
}
ahk_2(x) {
return x * square.Call(x)
}
ahk_3(x) {
return x * x
}
# install locally for a project:
pnpm install coffee-ahk
First, the basics: Coffee-AHK
uses significant whitespace to delimit blocks of code. You don't need to use semicolons ;
to terminate expressions, terminating the line will do just as well (although semicolons can still be used to fit multiple expressions on a single line). Instead of using curly braces {}
to enclose blocks of code in functions, if-statements, switches, and try/catch, use indentation.
You don't need to use parentheses to call a function when passing arguments. The implicit call wraps forward to the end of the line or block expression.
console.log sys.inspect object
→ console.log(sys.inspect(object));
Functions are defined by an optional list of parameters in parentheses, an arrow, and the function body. The empty function looks like this: ->
.
Note that all functions starting with an uppercase letter are treated as built-in functions.
square = (x) -> x * x
cube = (x) -> square(x) * x
global square := Func("ahk_2")
global cube := Func("ahk_1")
ahk_1(x) {
return square.Call(x) * x
}
ahk_2(x) {
return x * x
}
Functions may also have default values for arguments, which are used if the incoming argument is missing (undefined
).
fill = (container, liquid = 'coffee') ->
return "Filling the #{container} with #{liquid}..."
global fill := Func("ahk_1")
ahk_1(container, liquid := "coffee") {
return "Filling the " . (container) . " with " . (liquid) . "..."
}
Like JavaScript and many other languages, Coffee-AHK
supports strings delimited by the "
or '
characters. Coffee-AHK also supports string interpolation within "
-quoted strings using #{ ... }
. Single quoted strings are literal. You can even use interpolation in object keys.
author = 'Wittgenstein'
quote = "A picture is a fact. -- #{ author }"
sentence = "#{ 22 / 7 } is a decent approximation of π"
global author := "Wittgenstein"
global quote := "A picture is a fact. -- " . (author) . ""
global sentence := "" . (22 / 7) . " is a decent approximation of π"
Multiline strings are allowed in Coffee-AHK
. Lines are separated by a single space unless they end with a backslash. Indentation is ignored.
mobyDick = 'Call me Ishmael. Some years ago --
never mind how long precisely -- having little
or no money in my purse, and nothing particular
to interest me on shore, I thought I would sail
about a little and see the watery part of the
world...'
global mobyDick := "Call me Ishmael. Some years ago -- never mind how long precisely -- having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world..."
html = '''
<strong>
cup of coffee-ahk
</strong>
'''
global html := "<strong>cup of coffee-ahk</strong>"
Double-quoted block strings, like other double-quoted strings, allow interpolation.
The Coffee-AHK
literals for objects and arrays look a lot like their JavaScript cousins. If each property is listed on a separate line, the commas are optional. Objects can be created using indentation instead of explicit curly braces, similar to YAML
.
song = ['do', 're', 'mi', 'fa', 'so']
singers = {Jagger: 'Rock', Elvis: 'Roll'}
bitlist = [
1, 0, 1
0, 0, 1
1, 1, 0
]
kids =
brother:
name: 'Max'
age: 11
sister:
name: 'Ida'
age: 9
global song := ["do", "re", "mi", "fa", "so"]
global singers := {Jagger: "Rock", Elvis: "Roll"}
global bitlist := [1, 0, 1, 0, 0, 1, 1, 1, 0]
global kids := {brother: {name: "Max", age: 11}, sister: {name: "Ida", age: 9}}
Coffee-AHK
has a shortcut for creating objects when you want the key to be set with a variable of the same name. Note that the {
and }
are required for this shortcut.
name = 'Michelangelo'
mask = 'orange'
weapon = 'nunchuks'
turtle = {name, mask, weapon}
output = "#{turtle.name} wears an #{turtle.mask} mask. Watch out for his #{turtle.weapon}!"
global name := "Michelangelo"
global mask := "orange"
global weapon := "nunchuks"
global turtle := {name: name, mask: mask, weapon: weapon}
global output := "" . (turtle.name) . " wears an " . (turtle.mask) . " mask. Watch out for his " . (turtle.weapon) . "!"
In Coffee-AHK
comments are indicated by the character #
to the end of the line, or from ###
to the next occurrence of ###
.
if
/else
statements can be written without parentheses or braces. As with functions and other block expressions, multiline conditionals are delimited by indentation.
if singing then mood = greatlyImproved
if happy and knowsIt
clapsHands()
chaChaCha()
else
showIt()
if friday
date = sue
else date = jill
if (singing) {
mood := greatlyImproved
}
if (happy && knowsIt) {
clapsHands.Call()
chaChaCha.Call()
} else {
showIt.Call()
}
if (friday) {
date := sue
} else {
date := jill
}
gold = 'unknown'
silver = 'unknown'
rest = 'unknown'
awardMedals = (first, second, others...) ->
gold = first
silver = second
rest = others
contenders = [
'Michael Phelps'
'Liu Xiang'
'Yao Ming'
'Allyson Felix'
'Shawn Johnson'
'Roman Sebrle'
'Guo Jingjing'
'Tyson Gay'
'Asafa Powell'
'Usain Bolt'
]
awardMedals contenders...
alert "
Gold: #{gold}
Silver: #{silver}
The Field: #{rest.join ', '}
"
global gold := "unknown"
global silver := "unknown"
global rest := "unknown"
global awardMedals := Func("ahk_1")
global contenders := ["Michael Phelps", "Liu Xiang", "Yao Ming", "Allyson Felix", "Shawn Johnson", "Roman Sebrle", "Guo Jingjing", "Tyson Gay", "Asafa Powell", "Usain Bolt"]
awardMedals.Call(contenders*)
alert.Call(" Gold: " . (gold) . " Silver: " . (silver) . " The Field: " . (rest.join.Call(", ")) . " ")
ahk_1(first, second, others*) {
gold := first
silver := second
rest := others
}
# Eat lunch.
eat = (food) -> "#{food} eaten."
for food in ['toast', 'cheese', 'wine']
eat food
# Fine five course dining.
courses = ['greens', 'caviar', 'truffles', 'roast', 'cake']
menu = (i, dish) -> "Menu Item #{i}: #{dish}"
for dish, i in courses
menu i + 1, dish
# Health conscious meal.
foods = ['broccoli', 'spinach', 'chocolate']
for food in foods
if food isnt 'chocolate' then eat food
global eat := Func("ahk_2")
for __index_for__, food in ["toast", "cheese", "wine"] {
eat.Call(food)
}
global courses := ["greens", "caviar", "truffles", "roast", "cake"]
global menu := Func("ahk_1")
for i, dish in courses {
i := i - 1
menu.Call(i + 1, dish)
}
global foods := ["broccoli", "spinach", "chocolate"]
for __index_for__, food in foods {
if (food != "chocolate") {
eat.Call(food)
}
}
ahk_1(i, dish) {
return "Menu Item " . (i) . ": " . (dish) . ""
}
ahk_2(food) {
return "" . (food) . " eaten."
}
Use of
to signal comprehension over the properties of an object instead of the values in an array.
yearsOld = max: 10, ida: 9, tim: 11
ages = []
for child, age of yearsOld
ages.Push "#{child} is #{age}"
global yearsOld := {max: 10, ida: 9, tim: 11}
global ages := []
for child, age in yearsOld {
ages.Push("" . (child) . " is " . (age) . "")
}
# Econ 101
if @studyingEconomics
while supply > demand
buy()
until supply > demand
sell()
# Nursery Rhyme
num = 6
lyrics = []
while num
num = num - 1
lyrics.Push "#{num} little monkeys, jumping on the bed. One fell out and bumped his head."
if (this.studyingEconomics) {
while (supply > demand) {
buy.Call()
}
while !(supply > demand) {
sell.Call()
}
}
global num := 6
global lyrics := []
while (num) {
num := num - 1
lyrics.Push("" . (num) . " little monkeys, jumping on the bed. One fell out and bumped his head.")
}
For readability, the until
keyword is equivalent to while not
, and the loop
keyword is equivalent to while true
.
Coffee-AHK
compiles is
to ==
and isnt
to !=
.
You can use not
as an alias for !
.
For logic, and
compiles to &&
, and or
compiles to ||
.
Instead of a newline or semicolon, then
can be used to separate conditions from expressions in while
, if
/else
, and switch
/when
statements.
As in YAML
, on
and yes
are the same as boolean true
, while off
and no
are boolean false
.
unless
can be used as the inverse of if.
As a shortcut for this.property
you can use @property
.
To simplify mathematical expressions, **
can be used for exponentiation, and //
does the division of floors.
All together now:
Coffee-AHK |
AHK |
---|---|
is |
== |
isnt |
!= |
not |
! |
and |
&& |
or |
|| |
true , yes , on |
true |
false , no , off |
false |
@ , this |
this |
a ** b |
a ** b |
a // b |
a // b |
if ignition is on then launch()
if band isnt SpinalTap then volume = 10
unless answer is no then letTheWildRumpusBegin()
if car.speed < limit then accelerate()
print inspect "My name is #{@name}"
if (ignition == true) {
launch.Call()
}
if (band != SpinalTap) {
volume := 10
}
if !(answer == false) {
letTheWildRumpusBegin.Call()
}
if (car.speed < limit) {
accelerate.Call()
}
print.Call(inspect.Call("My name is " . (this.name) . ""))
Leading .
closes all open calls, allowing for simpler chaining syntax.
$ 'body'
.click (e) ->
$ '.box'
.fadeIn 'fast'
.addClass 'show'
.css 'background', 'white'
$.Call("body").click.Call(Func("ahk_1").Bind($)).css.Call("background", "white")
ahk_1($, e) {
$.Call(".box").fadeIn.Call("fast").addClass.Call("show")
}
Just like JavaScript
, Coffee-AHK
has destructuring assignment syntax. When you assign an array or object literal to a value, Coffee-AHK
breaks it up and matches both sides against each other, assigning the values on the right to the variables on the left. In the simplest case, it can be used for parallel assignment:
theBait = 1e3
theSwitch = 0
[theBait, theSwitch] = [theSwitch, theBait]
global theBait := 1000
global theSwitch := 0
global __array__ := [theSwitch, theBait]
theBait := __array__[1]
theSwitch := __array__[2]
But it's also useful for dealing with functions that return multiple values.
weatherReport = (location) ->
# Make an Ajax request to fetch the weather...
return [location, 72, 'Mostly Sunny']
[city, temp, forecast] = weatherReport 'Berkeley, CA'
global weatherReport := Func("ahk_1")
global __array__ := weatherReport.Call("Berkeley, CA")
global city := __array__[1]
global temp := __array__[2]
global forecast := __array__[3]
ahk_1(location) {
return [location, 72, "Mostly Sunny"]
}
Since AHK
is not case-sensitive, please do not use this way: item = new Item()
. Instead, it should be written like this: item2 = new Item()
.
class Animal
constructor: (name) -> @name = name
move: (meters) ->
alert @name + " moved #{meters}m."
class Snake extends Animal
move: ->
alert 'Slithering...'
super.move 5
class Horse extends Animal
move: ->
alert 'Galloping...'
super.move 45
sam = new Snake 'Sammy the Python'
tom = new Horse 'Tommy the Palomino'
sam.move()
tom.move()
class Animal {
__New(name) {
this.name := name
}
move := Func("ahk_3").Bind(alert).Bind(this)
}
class Snake extends Animal {
move := Func("ahk_2").Bind(alert).Bind(this)
}
class Horse extends Animal {
move := Func("ahk_1").Bind(alert).Bind(this)
}
global sam := new Snake("Sammy the Python")
global tom := new Horse("Tommy the Palomino")
sam.move.Call()
tom.move.Call()
ahk_1(alert, this) {
alert.Call("Galloping...")
base.move.Call(45)
}
ahk_2(alert, this) {
alert.Call("Slithering...")
base.move.Call(5)
}
ahk_3(alert, this, meters) {
alert.Call(this.name + " moved " . (meters) . "m.")
}
switch day
when 'Mon' then go work
when 'Tue' then go relax
when 'Thu' then go iceFishing
when 'Fri', 'Sat'
if day is bingoDay
go bingo
go dancing
when 'Sun' then go church
else go work
switch day {
case "Mon": {
go.Call(work)
}
case "Tue": {
go.Call(relax)
}
case "Thu": {
go.Call(iceFishing)
}
case "Fri", "Sat": {
if (day == bingoDay) {
go.Call(bingo)
go.Call(dancing)
}
}
case "Sun": {
go.Call(church)
}
default: {
go.Call(work)
}
}
try
allHellBreaksLoose()
catsAndDogsLivingTogether()
catch error
print error
finally
cleanUp()
try {
allHellBreaksLoose.Call()
catsAndDogsLivingTogether.Call()
} catch error {
print.Call(error)
} finally {
cleanUp.Call()
}
import './local-file.coffee'
import 'js-shim.ahk' # via npm
import fn from './source/fn'
import data from './data.json'
import data2 from './data.yaml
If you ever need to insert snippets of AHK
into your Coffee-AHK
, you can use backticks to pass it straight through.
hi = ->
msg = 'Hello AHK'
`MsgBox, % msg`
global hi := Func("ahk_1")
ahk_1() {
msg := "Hello AHK"
MsgBox, % msg
}