npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

safely-expressed

v1.0.0

Published

A compiler toolbox through which a sandboxed execution of javascript like expressions can be executed. Implemented with moo and nearley.

Downloads

10

Readme

Safely expressed

A compiler toolbox through which a sandboxed execution of javascript like expressions can be executed. Implemented with moo and nearley.

Compiler generates javascript code and instantiates new Function(scope, generated code). Only whitelisted properties are allowed, no access to globals. The this keyword is a reference to the scope object passed to the function. All globals are references to the corresponding property off the scope object.

Warning

Use at own risk, there is no garanty this is a bullet proof solution.

Usage

import { createCompiler } from 'safely-expressed'

const compile = createCompiler()
const run = compile(`a + b`)
expect(run({ a: 'b', b: 's' })).toBe('bs')

Supported Language constructs

  • Nested template string: `a{`b{c}`}d`
  • Tagged template string (requires transform): tag`a{b}c`
  • MemberExpression: a.b, a['b'] // only white listed and own properties are allowed (src/runtime/whitelist)
  • CallExpression: a(...), a.b(...), a['b'](...)
  • BinaryExpressions: |, &, ^, =, ==, ===, !=, !==, <>, >, >=, <, <=, +, -, *, /, %, >>, <<, >>>, ~, **
  • LogicalExpressions: &&, ||
  • UnaryExpressions: !, ~, +, -
  • ConditionalEXpression: a > b ? a : b
  • arrow functions: (...) => expression
  • function declarations: (...) => expression;
  • arrays: [], [a + b]
  • objects: {a}, {a: 'a'}, {[a + b]: 'ab'}
  • select: returns one of WHEN or DEFAULT expression SELECT(expression) { WHEN expression THEN expression DEFAULT expression }
  • String: 'a', "a", `a`, tag`a`
  • Number: 10, 1.2, -10, -1.2
  • Regexp: /[a-z]/im.test('a')
  • Literals: THIS, NULL, TRUE, FALSE
  • Range (requires range overload): [10...20]

For more details see src/grammar.ne

Examples

import {
  createCompiler,
  overloadRangeExpression,
  overloadBinaryExpression,
  overloadMemberExpression,
  overloadMethodExpression,
  createRuntime,
  Range,
  whitelist
} from 'safely-expressed'
import assert from 'assert'

const compile = createCompiler()
const compiled = compile(`a + b`)
assert.equal(compiled({ a: 'b', b: 's' }), 'bs')

const compile2 = createCompiler(
  [
    overloadMemberExpression('$get'),
    overloadMethodExpression('$get'),
    overloadRangeExpression('$range'),
    overloadBinaryExpression({
      '=': '$equals',
      '>': '$gt',
      '>=': '$gte',
      '<': '$lt',
      '<=': '$lte',
      IN: '$in',
      BETWEEN: '$between'
    })
  ],
  createRuntime({ whitelist, Range })
)

const compiled2 = compile2(`
  prijs IN [10...15]
`)
assert.equal(compiled2({ prijs: 9 }), false, '9 IN [10...15]')
assert.equal(compiled2({ prijs: 10 }), true, '10 IN [10...15]')
assert.equal(compiled2({ prijs: 15 }), true, '15 IN [10...15]')
assert.equal(compiled2({ prijs: 16 }), false, '16 IN [10..15')

const compiled3 = compile2(`prijs BETWEEN [10...15]`)
assert.equal(compiled3({ prijs: 10 }), false, '10 BETWEEN [10...15]')
assert.equal(compiled3({ prijs: 11 }), true, '11 BETWEEN [10...15]')
assert.equal(compiled3({ prijs: 14 }), true, '14 BETWEEN [10...15]')
assert.equal(compiled3({ prijs: 15 }), false, '15 BETWEEN [10...15]')

const compiled4 = compile2('(prijs > 15 && prijs < 20) || prijs = 10')
assert.equal(compiled4({ prijs: 9 }), false, '(9 > 15 && 9 < 20) || 9 = 10')
assert.equal(compiled4({ prijs: 10 }), true, '(10 > 15 && 10 < 20) || 10 = 10')
assert.equal(compiled4({ prijs: 11 }), false, '(11 > 15 && 11 < 20) || 11 = 10')
assert.equal(compiled4({ prijs: 15 }), false, '(15 > 15 && 15 < 20) || 15 = 10')
assert.equal(compiled4({ prijs: 16 }), true, '(16 > 15 && 16 < 20) || 16 = 10')
assert.equal(compiled4({ prijs: 19 }), true, '(19 > 15 && 19 < 20) || 19 = 10')
assert.equal(compiled4({ prijs: 20 }), false, '(20 > 15 && 20 < 20) || 20 = 10')

const compiled5 = compile2(`
  console.log("Hello world")
`)
compiled5({ console: { log: function(...args) { console.log(...args) } } }) // outputs Hello world

const compiled6 = compile2(`
  ({}).constructor
`)
assert.equal(compiled6({ console: { log: function(...args) { console.log(...args) } } }), undefined, '({}).constructor')

const compiled7 = compile2(`
  min(a, b) => a < b ? a : b; // function expression
  min(v1, v2)
`)
assert.equal(compiled7({ v1: 5, v2: 10 }), 5, 'min(v1, v2)')
assert.equal(compiled7({ v1: 10, v2: 5 }), 5, 'min(v1, v2)')

const compiled8 = compile2(`
  { a: v1 + v2, b: v1 - v2 }
`)
assert.deepEqual(compiled8({ v1: 10, v2: 5 }), { a: 15, b: 5 }, '{ a: v1 + v2, b: v1 - v2 }')

const compiled9 = compile2(`
  [v1 + v2, v1 - v2]
`)
assert.deepEqual(compiled9({ v1: 10, v2: 5 }), [15, 5], '[v1 + v2, v1 - v2]')

const compiled10 = compile2(`
  v3 IN [v1 + v2...v1 - v2]
`)
assert.deepEqual(compiled10({ v1: 10, v2: 5, v3: 8 }), true, '[v1 + v2, v1 - v2]')

const compiled11 = compile2('`Welcome { to + ` The {wo + rld}` }`')
assert.equal(compiled11({ to: 'To', wo: 'Wo', rld: 'rld' }), 'Welcome To The World', '`Welcome { to + ` The {wo + rld}` }`')

Included overloads

Overloads allow to transform the ast before code generation and are used for example to overload operators, implement tagged templates or add a range datatype.

overloadBinaryExpression({'+': '$add', ...})

Transforms a + b into $add(a, b)

overloadLogicalExpression({'&&': '$and', ...})

Transforms a && b into $and(a, b)

overloadMemberExpression('$get', [computed])

  • computed is false or undefined

    transform a.b into $get(a, 'b') transform a.b(arg) into $get(a, 'b', [arg])

  • computed is true or undefined

    transform a['b'] into $get(a, 'b') transform a'b' into $get(a, 'b', [arg])

overloadRangeExpression('$range')

Transforms (a TO b) into $range(a, b)

overloadTaggedTemplateString()

Transforms tag`a{b}c{d}` into tag(['a', 'c', ''], b, d)

overloadUnaryExpression({'!': $not})

Transforms !a into $not(a)