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!
- Interactive Playground
- Reference
- Operators –
Promise.race @# api_call(), timeout(ms) - Keyword Operators –
if 0 == arr.length :: «block» - Uncommon Operators
- Idioms
- Integrations
- Operators –
- Ecosystem
- Projects Using JSY
Start with rollup-plugin-jsy 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', dataThere 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 + bThe double colon :: in the preceding example opens a brace {, then closes the matching brace } when the indentation level matches that of the line where it was opened. The indented block is the return statement.
Commas are implicit at the first indent under any @-prefixed operator.
// JSY
console.log @
"the"
answer, "is"
42Translated 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")The :: operator wraps the indented block in curly braces {«block»}.
function add( a, b ) ::
return a + bTranslated to JavaScript:
function add( a, b ) {
return a + b
}The @ operator on its own wraps the indented block in parentheses («block»), where commas are implicit.
// JSY
console.log @
add @ 2, 3Translated to JavaScript:
// JavaScript
console.log(
add( 2, 3 )
)The @{} 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 @ bodyTranslated to JavaScript:
// JavaScript
fetch( 'http://api.example.com', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify( body )
})The @: 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 @ bodyTranslated to JavaScript:
// JavaScript
request({
url: 'http://api.example.com',
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify( body )
})The @[] 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.0Translated to JavaScript:
// JavaScript
const tri = [
[0.0, 1.0, 0.0],
[-1.0, -1.0, 0.0],
[1.0, -1.0, 0.0]
]The @# 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')
])The @=> 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();The @:: 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 @ eIs valid on its own; however, to declare more than one argument it requires the "opener" @\, eg.:
// JSY
something @:
evt: @\ e, f => this.handle @ e, fA variation of this is commonly seen in callback style, such as web servers:
// JSY
router.get @ '/endpoint', @\ request, response => response.send @ 'Hello, JSY!'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 =>
expressionTranslated 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)The ::! 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.')) })()}The ::!> 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.
// 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 1Translated 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);// 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 @: eaTranslated 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})
}// 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.')
}// 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.dobfunction 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, vforce += G * @ ( m1 * m2 ) / ( d * d )$ 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-jsyin 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.
- jsy-lang/vim-jsy
- Basic VIM/GVIM support for the JSY JavaScript dialect. Extends the builtin VIM javascript syntax, making it much less advanced than extensions like othree/yajs
- jsy-lang/prism-jsy
- Basic Prism support for the JSY JavaScript dialect.
- Hacked together JSY CodeMirror mode from the JavaScript mode.
Most JavaScript hightlighters work okay, but could certainly be better.
-
Highlighter Libraries:
- for highlight.js
- for Pygments
-
Code Editors:
- for CodeMirror
- for VIM / GVIM
- for Sublime – use the babel-sublime plugin
- for Atom – use the language-babel plugin
- for VSCode – use the sublime-babel-vscode or the vscode-language-babel plugin
- 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.