Weblang
The Weblang low code programming language lets you write safe, portable and efficient code with minimal logic. Created to let users run code on your server, but can be extended to run anything anywhere.
Features
- Extendable dynamic runtime
- Written in Javascript, can run anywhere
- Easy to learn, very minimal logic
- Safe and secure
- File and network access prohibited by default
- Allows running of user defined code on your server
- JSON compatible
- Write code using YAML syntax
- Easily transform to and from JSON
Ready to try it out?
Install
npm i weblang
Usage
let weblang = require('weblang')
let code = '=hello: world'
let state = await weblang.init().run(code)
How it works
Generally, setting a variable starts with =
, variable lookup starts with $
, and functions start with @
.
Variables have dynamic types, just as with YAML. All variables are global, there is no scope, not even inside if and else blocks.
Functions are added through extensions. Even the core functionality can be overridden, Weblang is meant to be extended.
Set and Get variables
Variables are stored in state.vars
:
- Setting a variable starts with
=
- Getting a variable starts with
$
# Set string variable
=hello: world
# Set number variable
=number: 1
# Set bool variable
=hello: true
# Set object variable
=hello:
a: 1
b: 2
# Set object one liner syntax
=hello: { a: 1, b: 2 }
# Set array variable
=hello:
- 1
- 2
# Set array one liner syntax
=hello: [1, 2]
# Set array index
=hello[0]: 3
# Set variable from other variable
=hello: world
=bye: $hello
# Set object value from other variable
=hello: world
=bye:
name: $hello
# Set array index from other variable
=hello: world
=bye:
- $hello
# Set variable, nested, dot notation
=hello.name: world
# Delete variable, making $hello undefined
=hello: null
# Delete value from object
=hello:
name: null
# Delete value from object, dot notation
=hello.name: null
# Delete array index, dot notation
=hello[0]: null
# Set variable from object, dot notation
=hello:
name:
deep: 1
=bye: $hello.name.deep
# Set variable from array, dot notation
=hello:
- 1
- 2
=bye: $hello[0]
# Set variable from object array, dot notation
=hello:
- name: nils
=bye: $hello[0].name
# Non existing variables are empty strings
=bye: $hello
# Set literal '$', prevents var lookup
=bye: $$hello
# Setting the same object variable merges the values
=hello: { a: 1 }
=hello: { b: 2 }
@log: $hello
# { a: 1, b: 2 }
If then else
Minimal logic is achieved through @if, @then and @else:
# If with then
@if:
$hello:
name:
eq: nils
@then:
=hello.name: hans
# Multiple checks
@if:
$hello:
name:
eq: nils
$req:
pathname:
eq: /hello
@then:
=hello.name: hans
# Checks works with dot notation as well
@if:
$hello.name.eq: nils
@then:
=hello.name: hans
# If then else
@if:
$hello:
name:
eq: hans
@then:
=hello.name: guro
@else:
=hello.name: kari
The validations inside the if-section are from the d8a validations:
required: true # Can not be undefined
eq: 5 # Equal to
ne: 5 # Not equal to
gt: 5 # Greater than
lt: 5 # Less than
gte: 5 # Greater than or equal to
lte: 5 # Less than or equal to
in: # Must be in list
- 1
- 2
- 3
nin: # Must not be in list
- 1
- 2
- 3
length: 5 # Length of string must be
min: 5 # Minimum length of string
max: 5 # Maximum length of string
match: /regex/ # Must match regex
is: boolean # Must be true or false
is: string # Must be a string
is: number # Must be a number, integer or decimal (float)
is: integer # Must be an integer
is: decimal # Must be a decimal number
is: date # Must be a date
is: id # Must be an id
is: object # Must be an object
is: array # Must an array
is: email # Must be an email address
is: url # Must be a URL
is: undefined # Must be undefined
is: null # Must be null
# Multiple types
is: [string, number]
# Use 'isnt' as the opposite of 'is'
isnt: null
# Works for all of the ones in 'is'
isnt: string
isnt: number
isnt: [email, id]
Return
The @return command sets a variable in state.return
. Using @return causes execution to be halted.
# Return a string
@return: hello
# Return a string variable
=hello: world
@return: $hello
# Return an object variable
=hello:
name: world
@return: $hello
# Return an array variable
=hello:
- 1
- 2
@return: $hello
# Return a variable, dot notation
=hello:
name:
baner: 1
@return: $hello.name
Vars
You can prefill the state with your own variables:
let req = { pathname: '/hello' }
let state = await weblang
.init({ vars: { req }})
.run(code)
Extensions
Weblang can (and should) be extended with your own commands.
Define an extension function like this:
// Extension function called db
function db({
state, // the runner's state with vars and return
code, // the actual code sent to weblang, untouched
tree, // the syntax tree like object, with ids
branch, // the current object being processed
node, // the key of the current branch
current, // the value of the current branch
key, // the setter key, usually starts with '='
id, // the internal id of the node
run, // the run function that runs your code
opt, // the options passed to weblang
expand, // the expander function used internally
load, // the loader, converts yml string to object
get, // use this to get variables and run pipes
set, // use this to set variables
ok // the validation function used for if tests
}) {
// Example use of set
set('=internal', 'hello')
// Whatever you return will be in your setter
return { id: '1' }
}
Write some code like this:
var code = '@db: user/create'
then run the code like this, while also adding the extension:
let state = await weblang.init({ ext: { db } }).run(code)
To set the result of the function, use the extension variable setter syntax:
=result@db: user/create
and the result
variable will be available in state.vars.result
.
Pipes
Variables can be run through pipes, which are functions that transform a value.
If the pipe does not exist, it is ignored.
# Use pipes with string
=hello: hello | upcase
# Use pipes with variables
=hello: hello
=bye: $hello | upcase
# Use pipes with return
@return: hello | capitalize
# Multiple pipes
@return: hello | upcase | downcase | capitalize
# Pipe parameters
@return: list | join delimiter=+ max=5
You can add your own pipes, or replace the built in ones, using the pipes option:
// Add a pipe named 'hello'
let state = await weblang
.init({
pipes: {
hello: function({ val }) {
if (typeof val != 'string') return val
return 'hello ' + val
}
}
})
.run(code)
and then use it like this:
@return: world | hello
The pipes receive all the same variables as with extensions.
License
MIT Licensed. Enjoy!