JSY is an indented (offside) JavaScript dialect. We believe indentation is
better at describing code blocks instead of painstakingly matching open/close
sections {} () []
.
Think modern JavaScript — ES6, ES2018 — indented similar to Python or CoffeeScript.
Inspired by Wisp, JSY primarily operates as a scanner-pass syntax
transformation to change indented (offside) code into the corresponding
open/close matching token code. Thus the internal scanning parser only has to
be aware of /* comments */
and "string literals"
rules to successfully
transform code. Thus, as a JavaScript dialect, JSY automatically keeps pace with modern JavaScript editions!
Promise.race @# api_call(), timeout(ms)
if 0 == arr.length :: «block»
Start with rollup-plugin-jsy-lite or use the playground!
Sample JSY code:
// JSY
const apiUrl = 'http://api.example.com'
class ExampleApi extends SomeBaseClass ::
constructor( credentials ) ::
const apiCall = async ( pathName, body ) => ::
const res = await fetch @ `${apiUrl}/${pathName}`, @{}
method: 'POST'
headers: @{}
'Content-Type': 'application/json'
body: JSON.stringify @ body
return await res.json()
Object.assign @ this, @{}
add: data => apiCall @ 'add', data
modify: data => apiCall @ 'send', data
retrieve: data => apiCall @ 'get', data
There are at-based (@
), double colon-based (::
), and keyword operators (if
, for
, while
, etc.). All operators wrap until the indentation is equal to or farther out than the current line, similar to Python or CoffeeScript. We refer to this as the indented block. For example:
// JSY
function add( a, b ) ::
return a + b
The double colon ::
in the preceding example opens a brace {
when used, then closes the matching brace }
when the indentation level matches that of the line it was first used on. The indented block is the return statement.
Commas are implicit at the first indent under any @
-prefixed operator.
// JSY
console.log @
"the"
answer, "is"
42
Translated to JavaScript:
// JavaScript
console.log(
"the"
, answer, "is"
, 42 )
Explicit commas are respected.
// JSY
console.log @
"or use"
, "explicit commas"
Translated to JavaScript:
// JavaScript
console.log(
"or use"
, "explicit commas")
::
Double Colon – Code BlocksThe ::
operator wraps the indented block in curly braces {«block»}
.
function add( a, b ) ::
return a + b
Translated to JavaScript:
function add( a, b ) {
return a + b
}
@
At – Calling FunctionsThe @
operator on its own wraps the indented block in parentheses («block»)
, where commas are implicit.
// JSY
console.log @
add @ 2, 3
Translated to JavaScript:
// JavaScript
console.log(
add( 2, 3 )
)
@{}
At Braces – HashesThe @{}
operator wraps the indented block in curly braces {«block»}
, where commas are implicit.
// JSY
fetch @ 'http://api.example.com', @{}
method: 'POST'
headers: @{}
'Content-Type': 'application/json'
body: JSON.stringify @ body
Translated to JavaScript:
// JavaScript
fetch( 'http://api.example.com', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify( body )
})
@:
At Colon – Calling with HashThe @:
operator wraps the indented block in parentheses wrapping curly braces ({«block»})
, where commas are implicit.
// JSY
request @:
url: 'http://api.example.com'
method: 'POST'
headers: @{}
'Content-Type': 'application/json'
body: JSON.stringify @ body
Translated to JavaScript:
// JavaScript
request({
url: 'http://api.example.com',
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify( body )
})
@[]
At Brackets - ListsThe @[]
operator wraps the indented block in square brackets [«block»]
, where commas are implicit.
// JSY
const tri = @[]
@[] 0.0, 1.0, 0.0
@[] -1.0, -1.0, 0.0
@[] 1.0, -1.0, 0.0
Translated to JavaScript:
// JavaScript
const tri = [
[0.0, 1.0, 0.0],
[-1.0, -1.0, 0.0],
[1.0, -1.0, 0.0]
]
@#
At Pound – Calling with a ListThe @#
operator wraps the indented block in parentheses wrapping square brackets ([«block»])
, where commas are implicit.
// JSY
Promise.all @#
fetch @ 'http://api.example.com/dosomething'
fetch @ 'http://api.example.com/dosomethingelse'
fetch @ 'http://api.example.com/dosomethingmore'
Translated to JavaScript:
// JavaScript
Promise.all([
fetch('http://api.example.com/dosomething'),
fetch('http://api.example.com/dosomethingelse'),
fetch('http://api.example.com/dosomethingmore')
])
@=>
At Arrow – Arrow Function Expression with No ArgumentsThe @=>
operator wraps the indented block in parentheses and begins an arrow function (()=> «block»)
.
The @=>>
operator wraps the indented block in parentheses and begins an async arrow function (async ()=> «block»)
.
// JSY
const call = fn => fn()
call @=> ::
console.log @ 'Do cool things with JSY!'
Translated to JavaScript:
// JavaScript
const call = fn => fn()
call( () => {
console.log('Do cool things with JSY!')
})
Asynchronous:
// JSY
const fn = @=>> await dothings()
Translated to JavaScript:
// JavaScript
const fn = async () => await dothings();
@::
At Block – Arrow Function Block with No ArgumentsThe @::
operator wraps the indented block in parentheses and begins an arrow function block (()=> { «block» })
.
The @::>
operator wraps the indented block in parentheses and begins an async arrow function block (async ()=> { «block» })
.
// JSY
describe @ 'example test suite', @::
it @ 'some test', @::>
const res = await fetch @ 'http://api.example.com/dosomething'
await res.json()
Translated to JavaScript:
// JavaScript
describe('example test suite', (() => {
it('some test', (async () => {
const res = await fetch('http://api.example.com/dosomething')
await res.json()}) ) }) )
Keep in mind JSY does not change anything about the special case lambda expression with a single argument, for example:
// JSY
something @:
evt: e => this.handle @ e
Would still require the "opener" @\
in order to declare more than one argument, eg.:
// JSY
something @:
evt: @\ e, f => this.handle @ e, f
Other arrow expressions:
// JSY
// async
const fn = async e => await handle @ e
// multiple arguments
const fn_body = @\ a, b, c ::
body
const fn_body = @\ ...args =>
expression
// first argument object destructure
const fn_obj_body = @\: a, b, c ::
body
const fn_obj_expr = @\: a, b, c =>
expression
// first argument array destructure
const fn_arr_body = @\# a, b, c ::
body
const fn_arr_expr = @\# a, b, c =>
expression
Translated to JavaScript:
// JavaScript
// async
const fn = async e => await handle(e)
// multiple arguments
const fn_body = (( a, b, c ) => {
body})
const fn_body = (( ...args ) =>
expression)
// first argument object destructure
const fn_obj_body = (({ a, b, c }) => {
body})
const fn_obj_expr = (({ a, b, c }) =>
expression)
// first argument array destructure
const fn_arr_body = (([ a, b, c ]) => {
body})
const fn_arr_expr = (([ a, b, c ]) =>
expression)
::!
and @!
- Bang Immediately Invoked ExpressionsThe ::!
operator wraps the indented block in a function, then invokes it {(() => {«block»})()}
in a block with no return value.
The @!
operator wraps the indented block in a function, then invokes it (() => {«block»})()
as an assignable expression.
// JSY
const a_value = @!
const a = 1
const b = 2
return a + b
::!
console.log @ 'Starting server...'
startServer @ '1337', ()=> console.log @ 'Server online.'
Translated to JavaScript:
// JavaScript
const a_value = ((() => {
const a = 1
const b = 2
return a + b })())
{(()=>{
console.log('Starting server...')
startServer('1337', ()=> console.log('Server online.')) })()}
::!>
and @!>
- Bang Async Immediately Invoked ExpressionsThe ::!>
operator wraps the indented block in an async function, then invokes it {(async ()=>{«block»})()}
in a block with no return value.
The @!>
operator wraps the indented block in an async function, then invokes it (async ()=>{«block»})()
as an assignable expression.
// JSY
const promise_value = @!>
const a = await Promise.resolve @ 1
const b = await Promise.resolve @ 2
return a + b
::!>
console.log @ 'Starting server...'
await new Promise @\ resolve ::
startServer @ '1337', resolve
console.log @ 'Server online.'
Translated to JavaScript:
// JavaScript
const promise_value = ((async () => {
const a = await Promise.resolve(1)
const b = await Promise.resolve(2)
return a + b})())
{(async ()=>{
console.log('Starting server...')
await new Promise (( resolve ) => {
startServer('1337', resolve) })
console.log('Server online.') })()}
TODO
For keywords with expressions, when not followed by a paren, everything between the keyword and the double colon ::
is captured as the keyword expression. When followed by a paren, parses as normal JavaScript.
No special parsing is done for keywords without expressions.
if
/else
, while
, and do
/while
// JSY
if a > b ::
console.log @ 'JSY is the best!'
else if a < b ::
console.log @ 'JSY rocks!'
else ::
console.log @ 'JSY is still awesome!'
while 0 != q.length ::
console.log @ q.pop()
do ::
console.log @ 'It is a song that never ends...'
while 1
Translated to JavaScript:
// JavaScript
if (a > b) {
console.log('JSY is the best!')
} else if (a < b) {
console.log('JSY rocks!')
} else {
console.log('JSY is still awesome!')
}
while (0 != q.length) {
console.log(q.pop())
}
do {
console.log("It is a song that never ends...");
} while (1);
for
// JSY
for let i = 0; i < 10; i++ ::
console.log @: i
for const val of [1,'two',0xa] ::
console.log @: val
for await const ea of someAsyncGenerator() ::
console.log @: ea
Translated to JavaScript:
// JavaScript
for (let i = 0; i < 10; i++) {
console.log({i})
}
for (const val of [1,'two',0xa]) {
console.log({val})
}
for await (const ea of someAsyncGenerator()) {
console.log({ea})
}
try
, catch
, and finally
// JSY
try ::
if 0.5 > Math.random() ::
throw new Error @ 'Oops!'
catch err ::
console.error @ err
finally ::
console.log @ 'Finally.'
Translated to JavaScript:
// JavaScript
try {
if (0.5 > Math.random()) {
throw new Error('Oops!')
}
} catch (err) {
console.error(err)
} finally {
console.log('Finally.')
}
switch
// JSY
switch command ::
case 'play':
player.play()
break
case 'pause':
player.pause()
break
default:
player.stop().eject()
Translated to JavaScript:
// JavaScript
switch (command) {
case 'play':
player.play()
break
case 'pause':
player.pause()
break
default:
player.stop().eject()
}
Uncommon use cases for ::
-prefixed operators require explicit commas, whereas the @
-prefixed operators allow for the implicit use of commas.
Uncommon Operator | Alias for | Use Instead |
---|---|---|
::{} |
:: |
:: |
::[] |
@[] |
|
::() |
@ |
|
::@ |
::() |
@ |
@() |
@ |
@ |
A few examples of why JSY is awesome.
res.data.map @
personData => @:
fullName: `${ personData.info.fName } ${ personData.info.lName }`
dateOfBirth: moment @ personData.info.dob
function double(x) :: return x + x
function add(x, y) :: return x + y
const clamp = (min, max, score) =>
Math.max @ min,
Math.min @ max, score
const calcScore = v => @
v = double @ v
v = add @ 7, v
v = clamp @ 0, 100, v
force += G * @ ( m1 * m2 ) / ( d * d )
jsy-node
with NodeJS$ npm install -g jsy-node
$ jsy-node some-script.jsy
$ mocha --require jsy-node/all some-unittest.jsy
rollup-plugin-jsy-lite – Rollup JSY syntax transpiler to standard JavaScript — without Babel
parcel-plugin-jsy (beta) – Parcel 1.x JSY syntax transpiler to standard JavaScript — without Babel
parcel-transform-jsy (beta) – Parcel 2.x JSY syntax transpiler to standard JavaScript — without Babel
jsy-transpile (stable) – Offside (indention) JSY syntax transpiler to standard JavaScript — without Babel
jsy-node (beta) – Register runtime require handler for Offside (indention) JSY syntax transpiler to standard JavaScript.
babel-plugin-jsy-lite (newer, beta) – Babel 6.x & 7.x and jsy-transpile offside (indention) Javascript syntax extension.
babel-plugin-offside-js (older, stable) – Babel 6.x and Babylon offside (indention) Javascript syntax extension.
babel-preset-jsy (stable) – Babel 6.x preset for offside-based javascript syntax building on babel-preset-env
rollup-plugin-jsy-babel (stable)
– Babel configuration for using babel-preset-jsy
in Rollup
jsy-rollup-bundler – JSY-oriented rollup bundling build chain for Web UI projects.
babel-convert-jsy-from-js – Convert JavaScript, Babel or Babylon AST into offside indented JSY formatted source code.
Most JavaScript hightlighters work okay, but could certainly be better.
Highlighter Libraries:
Code Editors:
shanewholloway/ projects
Special thanks to Robert Sirois for making this documentation happen!
Thanks to Brian Brown for inspiring, pushing, and supporting JSY's creation.
Thanks to Brandon Brown and Larry King for intensive use and feedback on JSY.
Documentation is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
Examples and source code are licensed under BSD 2-Clause.