Skip to content

Latest commit

 

History

History
660 lines (533 loc) · 14.8 KB

documentation.md

File metadata and controls

660 lines (533 loc) · 14.8 KB

Documentation

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

Overview

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
}

Installation

# install locally for a project:
pnpm install coffee-ahk

Language Reference

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 objectconsole.log(sys.inspect(object));

Functions

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) . "..."
}

Strings

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.

Objects and Arrays

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) . "!"

Comments

In Coffee-AHK comments are indicated by the character # to the end of the line, or from ### to the next occurrence of ###.

If, Else, Unless, and Conditional Assignment

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
}

Splats, or Rest Parameters

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
}

Loops

# 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.

Operators and Aliases

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) . ""))

Chained Calls

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")
}

Destructuring Assignment

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"]
}

Class

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/When/Else

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/Catch/Finally

try
  allHellBreaksLoose()
  catsAndDogsLivingTogether()
catch error
  print error
finally
  cleanUp()
try {
  allHellBreaksLoose.Call()
  catsAndDogsLivingTogether.Call()
} catch error {
  print.Call(error)
} finally {
  cleanUp.Call()
}

Modules

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

Native AHK

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
}