Quirrel4.41.0

Coming from JavaScript

The two languages look alike, so the differences are easy to miss. Braces, for, while, function, closures, class, try/catch, ?., ??, string interpolation and async/await have the same meaning. The differences are inside common expressions: there is no undefined, integers and floats are separate types, and reading a key that is not there is an error.

Values and bindings

JavaScriptQuirrel
let x = 1, rebindablelocal x = 1 - rebindable; let x = 1 - binds once
const x = 1let x = 1; const MAX = 10 * 5, folded at compile time
varnothing
null and undefinednull alone
one number typeinteger and float; 7 / 2 is 3, 7 / 2.0 is 3.5
x === yx == y; there is no loose equality to avoid
a template literal, ${b} inside$"a {b}"
(x) => x * 2@(x) x * 2
function (x) { }the same
typeof xtype(x), or typeof x, giving "integer", "table", "instance", ...
x ?? y, x?.y, a?.[i], f?.()x ?? y, x?.y, a?[i], f?()
!x, &&, ||the same, and they keep the operand value
x++, +=, <<, &the same
NaN, Infinityneither one; 1 / 0 throws
String(x), Number(s)x.tostring(), s.tointeger(), s.tofloat()
// commentthe same

Objects and arrays

JavaScriptQuirrel
{ a: 1 }{ a = 1 }, a table, with = inside; { [expr] = 1 } for a computed key
{ x, y } shorthand{ x, y }, the same
o.miss gives undefinedit throws; o?.miss gives null, and ?? fills in
o.fresh = vo.fresh <- v; plain = writes a slot that is already there
delete o.ko.$rawdelete("k"); the delete operator is off by default
"k" in o, o.hasOwnProperty(k)"k" in o, o.rawin(k)
Object.keys, values, entrieso.keys(), o.values(), o.topairs()
Object.assign({}, a, b){ ...a, ...b }, or a.__merge(b); a.__update(b) updates in place
Object.freeze(o)freeze(o), and it reaches the tables inside
{ ...o }, [...a]the same, one level deep; a table also takes a class or an instance, and a null source adds nothing
{ ...a, ...b }, [...a, ...b]the same, and a later key wins
structuredClone(o)nothing; clone o copies one level
const { a, b } = olet { a, b } = o, and a key that is not there throws unless it has a default
const { a, ...rest } = olet { a } = o; there is no rest element
a.lengtha.len()
a.push(v), a.pop()a.append(v), a.pop()
a.shift(), a.unshift(v)a.remove(0), a.insert(0, v)
a.indexOf(v)a.indexof(v), and null rather than -1
a.includes(v)a.contains(v)
a.forEach(f)a.each(f)
a.map, a.filter, a.reducethe same names, and the callback is (value, index)
a.find, a.findIndexa.findvalue, a.findindex
a.concat(b)a.extend(b); + does not join two arrays
a.join(",")",".join(a), which is a string method
a.slice(1, 3), a.slice(-2)the same
s.toUpperCase(), s.trim()s.toupper(), s.strip()
s.startsWith(p), s.includes(p)s.startswith(p), s.contains(p)
s.replace(a, b)s.replace(a, b), and it replaces every match
/re/ literalregexp("re") from string
JSON.parse, JSON.stringifynot in the core library
Map, Seta table

Control flow and functions

JavaScriptQuirrel
for (const v of a)foreach (v in a)
for (const k in o)foreach (k, v in o), which gives both
for (let i = 0; ...)for (local i = 0; ...); let is not allowed in a for header
switchif (a) ... else if (b) ...; switch is deprecated and off by default, see control flow
try / catch / finallytry { } catch (e) { }; no exception classes, and no finally
throw new Error(m)throw m; any value can be thrown, and catch takes them all
async and awaitthe same words; see async and await
function* g() with yieldany function that contains yield
f(...args)f.acall([this, a, b])
f.call(o, x), f.apply(o, args)f.call(o, x), f.acall([o, x])
f.bind(o)f.bindenv(o)
argumentsvargv, in a function f(...)
a default parameterfunction f(a, b = 1) { }, the same

Classes

JavaScriptQuirrel
class A extends Bclass A(B) { }, with one base class only
constructor()constructor()
super.m(), super()base.m(), base.constructor()
a field assigned in the constructordeclare it in the class body; an instance takes no new slots later
static x = 1static x = 1
get x(), set x(v)the _get and _set metamethods
toString()_tostring
new A()A(); there is no new
a instanceof Athe same
a prototype patched at run timea class is locked as soon as it is used

Modules

JavaScriptQuirrel
import { x } from "m"from "m" import x
import * as m from "m"import "m" as m
require("m")require("m"), at run time; import "m" at compile time
export default vreturn v at the end of the file
export const a, breturn freeze({ a, b })

Traps

examples/pages/coming-from/javascript.nut
let squad = { name = "alpha" }

// a slot that is not there is an error, not undefined
try {
  println(squad.hp)
} catch (e) {
  println(e)
}
println(squad?.hp ?? 100)

// two integers divide as integers, and a division by zero throws
println(7 / 2)
local zero = 0    // local: a let would fold to 0 and the analyzer would flag the division
try {
  println(1 / zero)
} catch (e) {
  println(e)
}

// for gives every closure the same variable, foreach gives each one its own
let fromFor = []
for (local i = 0; i < 3; i++)
  fromFor.append(@() i)

let fromForeach = []
foreach (v in [0, 1, 2])
  fromForeach.append(@() v)

println($"for:     {fromFor[0]()}{fromFor[1]()}{fromFor[2]()}")
println($"foreach: {fromForeach[0]()}{fromForeach[1]()}{fromForeach[2]()}")
Output:
the index 'hp' (type='string') does not exist
100
3
integer division by zero
for:     333
foreach: 012

What is not there

See also