-
Notifications
You must be signed in to change notification settings - Fork 0
Extensions
These are some possible extensions to the language.
The mutation
directive allows block names to be redefined and collections to be mutated.
kesh 2021 + mutation
Names in lexical scope may be marked as variable with let
and redefined with set
.
let mutated: false
set mutated: true
Object fields may also be marked as variable, and redefined using dot notation.
joe: [ let name: "Joe", age: 27 ]
set joe.name: "Joseph"
The entire object may be marked as mutable with the *
unary operator.
joe: *[ name: "Joe", age: 27 ]
set joe.age: 28
set joe.nickname: "Joey"
Arrays and other indexed collections may also be marked as mutable.
people: *array[]
people.push joe
Keyed collections are always mutable.
Multiple object fields can be redefined in one go by unpacking to the object.
set joe.(name, age): ('Joey', 28)
The above line achieves the same as:
(name, age): ('Joey', 28)
set joe.name: name
set joe.age: age
Unpacking also works with objects and arrays.
set joe.[name, age]: [ name: 'Joey', age: 28 ]
set joe.(name, age): ['Joey', 28]
It's also possible to unpack all of another object's enumerable fields with the spread/rest operator ...
.
set joe.[...]: [ name: 'Joey', age: 28 ]
This is equivalent to ECMAScript's Object.assign
.
Within a set
's expression, the placeholder operator _
refers to the name's existing value, enabling augmented assignment.
let count: 0
set count: _ + 1
--> #number 1
Existential assignment is also useful.
set options.duration: _ ? 100 -- provide fallback by setting to 100 only if #none
One can also use helper functions increment
and decrement
on mutable #number
s.
increment count
--> #number 2
decrement count
--> #number 1
The fp
directive enables compose and pipe operators, and imports all functions from rambda
into the module scope.
kesh 2021 + fp
The forward >>
and backward <<
composition operators compose unary functions.
square >> negate >> print -- forward composition
print << negate << square -- backward composition
The pipe |>
operator allow values to be piped into expressions.
The ^
placeholder is used to place the piped value inside the right-hand expression.
Pipes are equivalent to the TC39 proposal for smart pipelines.
answer: [1, 2, 3, 4]
|> sum -- unary function application
|> ^ * 4 + 2 -- expression
--> #number 42
Piping into a composed function:
answer |> square >> negate
--> #number -6.48074069840786
The placeholder
directive enables extra functionality for the _
placeholder.
When unpacking, it can be used to ignore a value.
kesh 2021 + placeholder
(_, answer): (true, 42)
The globals
directive imports all fields of the global
object into the module scope.
kesh 2021 + globals + mutation
set global.answer: 42
answer
--> #number 42
Math.sqrt answer
--> #number 6.48074069840786
The return
directive enables the return
statement for explicit or early termination of functions.
kesh 2021 + return
sqrt: (number) ->
if number < 0 then return
-- todo: calculate the square root of number
The async
directive enables the async
and await
keywords.
kesh 2021 + async + globals
sleep: async (seconds) ->
Promise (resolve) -> set-timeout(resolve, seconds * 1000ms)
print "Hold up"
await sleep 1s
print "Hey!"
The match
directive enables pattern matching using the match
keyword.
kesh 2021 + match
stage: match person.age
0 -> 'infant'
1 | 2 -> 'toddler'
3..17 -> 'kid'
18..59 -> 'adult'
60..123 -> 'senior'
124.. -> 'kurzweil'
#any -> 'invalid'
The loop
directive enables imperative loop statements with support for break
and continue
statements.
kesh 2021 + loop + async + mutation
-- infinite loop
loop
await client.listen()
-- repeat a block x times
loop 3
print "hey"
-- loop over an iterable's elements
loop 1..10 as number
if number mod 3 = 0 then continue
print number
-- loop over a collection's own enumerable fields
loop score as (player, points)
print("{ player.name }: { points } points")
-- traditional while loop
loop while everything = 'ok'
print "It's all good"
-- untraditional until loop
loop until done
-- todo: process something
if nothing-to-do then set done: true
-- traditional for loop
loop for { let i: 0, i < 9, increment i }
print i
loop
iterates an iterable or a #number
by default.
loop while
and loop until
loops on the basis of a predicate.
loop for
is equivalent to ECMAScript's for
loop, except using a block to specify iteration parameters.
Loops are statements, not expressions, and don't return a value. However, they can be named, making it possible to break
or continue
a specific loop by name. Yes, this happens to be identical to ECMAScript's label
statement.
outer: loop {
print "Entered the outer loop"
inner: loop {
print "Entered the inner loop"
-- This would break only the inner loop
--break
-- This breaks the outer loop
break outer
print "This point will never be reached"
}
print "Exited the outer loop"
}
--> #text "Entered the outer loop"
--> #text "Entered the inner loop"
--> #text "Exited the outer loop"
The generator
directive enables generator functions, which are regular function producing values with the yield
keyword.
kesh 2021 + generator + loop + mutation
id-maker: gen () ->
let index: 0
loop
yield index
increment index
generator: id-maker()
generator.next().value --> 0
generator.next().value --> 1
generator.next().value --> 2
The yield-from
keyword delegates to another generator or iterable, equivalent to ECMAScript's yield*
.
func-1: () \-> yield 42
func-2: () \-> yield-from func-1()
generator: func-2()
generator.next().value --> 42
The with
directive enables with
statements for generators, similar to Python's with
statement.
This is useful in a number of scenarios. A common one is to ensure that a resource is released after use.
kesh 2021 + with
open: async gen (path) \-> -- async non-returning generator function
file: await fs.open(path)
print "file opened"
yield file
file.close()
print "file closed"
with await open("./hello.txt") as file
print file.contents
--> #text "file opened"
--> #text "Hello, world!"
--> #text "file closed"
The regex
directive enables regular expressions as an embedded language.
kesh 2021 + regex
Regular expressions are written as XRegExp expressions within code or text literals tagged with regex
.
Code literals compile down to native RegExp
objects.
re`(?im)^[a-z]+$` -- leading mode modifier
re('gi')`\b(?<word>[a-z]+)\s+\k<word>\b` -- flags as argument
Text literals are evaluated at runtime, which require the XRegExp
module to be automatically imported.
hours: re'1[0-2]|0?[1-9]'
minutes: re'(?<minutes>[0-5][0-9])'
time: re"\\b { hours } : { minutes } \\b" -- interpolation
The exception
directive enables exceptions and try…avert
statements.
The fail
keyword is used to "throw" or "raise" an exception.
The avert
keyword is used to "catch" an exception thrown/raised within a try
block.
kesh 2021 + exception
check: (roentgen) ->
if roentgen > 3 then fail Error "Dangerously high radiation levels"
try { check 3.6 } avert (cause)
console.info Error("Well, that's not great, but it's not horrifying", [cause])
As the keywords imply, an exception will cause the program to fail unless avert
ed.
As the example implies, a fail should not be handled carelessly.
The contract
directive enables Eiffel like require
and ensure
statements within functions.
Each of the statements evaluate one (inline) or more (block) conditions (expressions) that must be fulfilled.
For the ensure
statement, result
refers to the return value of the function.
kesh 2021 + contract + fp
sqrt-int: (x) ->
require x >= 0
ensure
result * result <= x
(result + 1) * (result + 1) > x
x |> sqrt >> floor
The defer
directive enables the defer
statement, similar to that of Go.
kesh 2021 + defer + loop
count: () ->
print 'counting'
loop 1..3 as number
defer print number
print 'done'
count()
--> #text "counting"
--> #text "done"
--> #number 3
--> #number 2
--> #number 1
The order
directive enables explicit order of operations for arithmetic operators, also known as PEMDAS or BODMAS.
It activates a source code formatter that enforces order of operations by inserting parens, respecting any existing groups.
This improves readability by removing ambiguity when order of operations is expected.
kesh 2021 + order
-- before save:
print 1 + 2 * 3 - 4 / 5
print (1 + 2) * 3 - 4 / 5
-- after save:
print 1 + (2 * 3) - (4 / 5) --> 6.2
print (1 + 2) * 3 - (4 / 5) --> 8.2
There's no implicit order of operations.
Without order
enabled, mathematical expressions are evaluated left-to-right, with parens used for grouping.
print 1 + 2 * 3 - 4 / 5 --> 1
print 1 + 2 * 3 - (4 / 5) --> 8.2
Directives may also be grouped into a named profile. The following are provided by the compiler.
The standard
profile enables all the standard extensions for the active version of the language.
kesh 2021 standard
Some candidates for inclusion:
The web
profile imports window
from the global
object.
kesh 2021 web
alert "Hello, web!"
To open the print dialog, window.print()
must be used, as print
is an alias for console.log
in module scope.
The strict
profile enforces certain restrictions.
kesh 2021 strict
The archaic
profile enables obsolete keywords that went out of fashion a long time ago.
kesh 2021 archaic
const answer: 42
function query(question): {
if (~question) {
return answer
} else {
throw new Error('No question, no answer')
}
}
Please don't.
The define
directive enables definition of language features such as keywords and operators.
It could be done something like this (nothing here is written in stone):
kesh 2021 + define
define statement 'return': (exp) -> …
define postfix-operator '?': (lhs) -> …
define binary-operator '?': (lhs, rhs) -> …
define statement 'loop': (exp) -> …
This programming language only exists as a design. I'm unlikely to ever get the chance to write a compiler for it.
Feel free to steal any parts of it that you like. Ideas are better stolen than forgotten. (They're not my ideas anyway.)