• Stars
    star
    122
  • Rank 292,031 (Top 6 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created over 5 years ago
  • Updated almost 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A small DSL/interpreter that can be used to evaluate simple expressions

Angu

A small, zero-dependency library that can be used to build and evaluate mini-languages in the browser or in NodeJS. You have complete control over every operation performed, and this library takes care of the nitty gritty of the parsing and such. Comes with TypeScript typings (usage optional).

We can use this to create a simple in-browser calculator to evaluate things like:

10 + 4 / 2 * (3 - 1)

Or we can use it to manipulate table cells by evaluating something like:

MEAN(A1:A5) + SUM(C1:D2) - 10

Or we can build a small expression-based language that looks like:

foo = 2;
bar = 4;
wibble = foo * bar + pow(2, 10);
foo + bar + wibble

Or a range of other things.

In each case, we define the operators (optionally with precedence and associativity) and functions available to the program and exactly what they do with their arguments, and this library takes care of the rest.

Complete examples can be found here.

Installation

You can install the latest release from npm:

npm install angu

Basic Usage

First, you define a Context which determines how expressions will be evaluated. For a simple calculator, we might define something like the following:

import { evaluate } from 'angu'

const ctx = {
    // We provide these operators:
    scope: {
        '-': (a, b) => a.eval() - b.eval(),
        '+': (a, b) => a.eval() + b.eval(),
        '/': (a, b) => a.eval() / b.eval(),
        '*': (a, b) => a.eval() * b.eval(),
    },
    // And define the precedence to be as expected
    // (first in array => evaluated first)
    precedence: [
        ['/', '*'],
        ['-', '+']
    ]
}

Then, you can evaluate expressions in this context:

const r1 = evaluate('2 + 10 * 4', ctx)
assert.equal(r1.value, 42)

We can also provide locals at eval time:

const r1 = evaluate('2 + 10 * four', ctx, { four: 4 })
assert.equal(r1.value, 42)

If something goes wrong evaluating the provided string, an error will be returned. All errors returned contain position information ({ pos: { start, end}, ... }) describing the beginning and end of the string that contains the error. Specific errors contain other information depending on their kind.

More examples can be found here.

Details

Primitives

Angu supports the following literals:

  • booleans ('true' or 'false')
  • numbers (eg +1.2, -3, .9, 100, 10.23, -100.4, 10e2). Numbers have a string version of themselves stored (as well as a numeric one) so that we can wrap things like big number libraries if we like. The string version applies some normalisation which can help other libraries consume the numbers:
    • The exponent is normalised to a lowercase 'e'.
    • Leading '+' is removed.
    • A decimal point, if provided, is always prefixed with a number ('0' if no number is given)
  • strings (strings can be surrounded in ' or ", and \'s inside a string escape the delimiter and themselves)

Otherwise, it relies on operators and function calls to transform and manipulate them.

Operators

Any of the following characters can be used to define an operator:

!£$%^&*@#~?<>|/+=;:.-

Operators can be binary (taking two arguments) or unary. Unary operators cannot have a space between themselves and the expression that they are operating on.

Operators not defined in scope will not be parsed. This helps the parser properly handle multiple operators (eg binary and unary ops side by side), since it knows what it is looking for.

Some valid operator based function calls (assuming the operators are in scope):

1+2/3
1 + 2
1 + !2

Functions/variables

Functions/variables must start with an ascii letter, and can then contain an ascii letter, number or underscore.

Some valid function calls:

foo()
foo(bar)
foo(1,2)

If the function takes exactly two arguments, and is also listed in the precedence list, it can be used infix too, like so (there must be spaces separating the function name from the expressions on either side):

1 foo 2

All values passed to functions on scope have the Value type. One can call .eval() on them to evaluate them and return the value that that results in. Some other methods are also available:

  • Value.kind(): Return the kind of the Value ("string" | "number" | "variable" | "bool" | "functioncall").
  • Value.pos(): Return the start and end index of the original input string that this Value was parsed from.
  • Value.toString(): (or String(Value)) gives back a string representation of the value, useful for debugging.
  • Value.name(): Gives back the "name" of the value. This is the function/variable name if applicable, else true/false for bools, the string contents for strings, or the numeric representation for numbers.

See the examples for more, particularly workingWithVariables.ts.

More Repositories

1

weave

A simple CLI router for wiring together several sources behind a single HTTP endpoint
Rust
141
star
2

yap

Yet Another Parser library for Rust. A lightweight, dependency free, parser combinator inspired set of utility methods to help with parsing strings and slices.
Rust
127
star
3

wasm-fractal

A Rust (compiled to WASM) + JS multi-threaded in-browser fractal generator
JavaScript
77
star
4

seamless

An opinionated Rust library for creating simple JSON APIs that communicate over HTTP
Rust
24
star
5

fuss

A Functional CSS Preprocessor
Rust
14
star
6

git-backup

A tool to help you backup your git repositories from services like GitHub
Rust
12
star
7

jsdw.me

My homepage
Haskell
10
star
8

hs-commander

A nested command parsing tool for Haskell
Haskell
7
star
9

cpp-sequitur

A C++ implementation of the sequitur compression algorithm
C++
5
star
10

talklicker

A small Haskell+Elm event scheduling app
CSS
4
star
11

advent-of-code-2018

Solutions for Advent of Code 2018
Rust
3
star
12

depends

A small, versatile, and thread safe dependency injection library for Go
Go
3
star
13

highscore

A simple app for helping to track high scores
Rust
3
star
14

tl-asset-browser

A basic Elm implementation of a Third Light asset browser
JavaScript
3
star
15

folder-content-aggregator

A quick Go/Rust implementation of a robust aggregated folder watching tool
Rust
2
star
16

anagrammer

Find line-anagrams in an ASCII formatted text file (excluding lines where all words match)
Rust
2
star
17

undigits

A quick app to solve the NYTimes Digits puzzle
TypeScript
2
star
18

advent-of-code-2022

Rust
2
star
19

vault-inject

A utility for automatically pulling secrets out of vault and injecting them into shell commands
Rust
2
star
20

advent-of-code-2019

Solutions to AoC 2019
Rust
2
star
21

promiseWorker

Wrapping JS Web Workers into Promises for quick inline concurrent JS in modern browsers.
JavaScript
1
star
22

slackup

A quick tool to download/backup conversations from slack
Rust
1
star
23

hs-hipchat-to-websocket

Connects hipchat protocol with a simple websocket one. Provides addon information so that hipchat can install them. Multiple addons supported via config.
Haskell
1
star
24

boundvariable

My shot as the ICFP '06 programming challenge
Rust
1
star
25

gh-tools-project-sync

Sync Tools Team milestones to project boards
Rust
1
star
26

js-compression-machine

Compress files right in your browser and then decompress them again. An experiment in compression algorithms.
JavaScript
1
star
27

hs-websocket-bot2

Version 2 of my Websocket based Chatbot. Cleaner and better than the original.
Haskell
1
star