Awesome
JSY Language Documentation
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
Quick Start
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
Reference
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.
Implicit Leading Commas
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")
Operators
::
Double Colon – Code Blocks
The ::
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 Functions
The @
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 – Hashes
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 @ 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 Hash
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 @ body
Translated to JavaScript:
// JavaScript
request({
url: 'http://api.example.com',
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify( body )
})
@[]
At Brackets - Lists
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.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 List
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')
])
@=>
At Arrow – Arrow Function Expression with No Arguments
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();
@::
At Block – Arrow Function Block with No Arguments
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()}) ) }) )
Arrow functions with Arguments
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 Expressions
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.')) })()}
::!>
and @!>
- Bang Async Immediately Invoked Expressions
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.') })()}
Generators
TODO
Keyword Operators
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()
}
Operators (Uncommon Use Cases)
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 |
---|---|---|
::{} | :: | :: |
::[] | @[] | |
::() | @ | |
::@ | ::() | @ |
@() | @ | @ |
JSY Idioms
A few examples of why JSY is awesome.
Idiom #1 – Arrow Function Returning Hash
res.data.map @
personData => @:
fullName: `${ personData.info.fName } ${ personData.info.lName }`
dateOfBirth: moment @ personData.info.dob
Idiom #2 – Arrow Function Compose
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
Idiom #3 - Order of Operations Laziness
force += G * @ ( m1 * m2 ) / ( d * d )
JSY Integration with Other Tools
Using jsy-node
with NodeJS
Command Line
$ npm install -g jsy-node
$ jsy-node some-script.jsy
Mocha
$ mocha --require jsy-node/all some-unittest.jsy
Ecosystem
Pure JavaScript Transpiler (stable)
-
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 Transpiler (newer, beta)
-
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
Misc Babel-based Tools
-
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.
Syntax Highlighters
- 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.
Real Syntax Highlighters Needed!
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
Projects using JSY
- shanewholloway/ projects
Thanks!
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.
Licenses
<a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"> <img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" /> </a><br/> Documentation is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International License</a>.Examples and source code are licensed under BSD 2-Clause.