~kylep/pipes

Reimplement send with less recursion
Move around runtime code

refs

master
browse  log 

clone

read-only
https://git.sr.ht/~kylep/pipes
read/write
git@git.sr.ht:~kylep/pipes

You can also use your local clone with git send-email.

#PIPES

Yet another experimental lang about piping data around

Pipes includes as a foundational construct, pattern matching and rewriting.

Check out the playground here: https://stuff.kyleperik.com/pipes/

Note, there is a save button, which saves the program to a registry server so they can be shared, but only if you are authenticated. If you would like to be able to use this, contact me and I'll create an account for you. You can also spin up your own service from this repository to run your own registry.

Otherwise the programs are cached in the browser local storage.

#Examples

Fibonacci

Velocity

Line Drawing

Basic snake game

Fizzbuzz

Logo

Basic text adventure

#Overview

Pipes statements define what it should select, and what to generate with it.

'marco': 'polo';

This basic snippet selects on any 'marco' input, and spits out 'polo' in response.

In reality, things are a bit more complicated than that, since you may have multiple sources of data incoming, not just strings.

The basic implementation of pipes includes devices to handle basic string input and output. To select a device, simply specify the type in your match/result statements.

['input', 'marco', state]: ['log', 'polo'];

Now this is a working program. Type 'marco' in the input textbox and enter, and you will see 'polo' logged below.

#References

To passthrough values received from input, don't specify a value for a key to give assign it to a reference, which can be used in the result.

['input', value, state]: ['log', value];

#Piping

Specifying 'input' and 'log' can get a bit verbose. You can simplify the core rules by abstracting out the extraction to and from devices by including multiple stages.

['input', value, state]: value

| 'marco': 'polo'

| value: ['log', value];

Each stage selects for criteria, and has it's own result which is passed to the following stage to process.

You can include multiple rules within each step using grouping.

['input', value, state]: value

| (
  'marco': 'polo';
  'dog': 'woof';
)

| value: ['log', value];

#Definitions

Definitions enable you to package a set of rules into a reusable pattern, making use more succinct.

def input
  ['input', value, state]: value;

def log
  value: ['log', value];

input | 'marco': 'polo' | log;

#Implied Equality

When matching patterns, if you use the same variable name, this will automatically apply a conditional that those values are the same.

For example:

[a, a]: a;

Matches [1, 1] but not [1, 2].

#Object Keys

Objects keys are matched similar to js, where the key can be written without quotes.

{key: value}

This interprets "key" as a static string match.

Pipes also supports self-named key matches

{key}

In this case, key will match, and be assigned to the name "key".

Pipes also supports variable keys, when wrapping in parens.

For example, this matches the variable key.

{(key): value}

In doing so, this will potentially generate additional matches for each potential.

For example, this program:

{(key): value}: [key, value];

will match like so:

{a: 1, b: 2, c: 3} -> ['a', 1], ['b', 2], ['c', 3]

#Primitive Pipes

Pipes supports a number of primitives to enable math and boolean logic, and more.

  • add - [...n] - sums n
  • mul - [...n] - multiplies n
  • sub - [a, b] - subtracts b from a
  • div - [a, b] - divides a by b
  • mod - [a, b] - takes modulo of a
  • sin/cos - n - takes sin/cos of a * pi * 2
  • ceil/floor/round - n - takes ceil/floor/round of n
  • eq/gt/lt/gte/lte - [a, b] - compares numbers a and b
    • equals / greater than / less than
    • greater than or equal / less than or equal
  • not - b - gives the opposite boolean of b
  • split - [s, p] - splits string s by p into a list of segments
  • each - [...x] - produces an event for every x

#Spread Operator

Objects and lists support pulling a variable number of elements, for both matches and the result, similar to js, using 3 dots.

This example matches a list with at least 3 elements, and generates a list containing any additional elements (may be an empty list)

[a, b, c, ...rest]: rest;

Similarly, this matches an object with keys a, b and c, and outputs a object containing all other keys.

{a, b, c, ...rest}: rest;

In both causes the operator only works at the end.

The spread operator also works in the result too.

This outputs the object, replacing the a key with the value 1:

obj: {...obj, a: 1};

This outputs a new list with 1 at the end.

list: [...list, 1];

#Bundle Operator

Pipes is unique in that every pipe has the potential to generate from any given event, any number of resulting events.

This makes things like mapping and filtering simpler, but loses the ability to track over time which events were a result of some upstream event.

For example, it's easy to increment numbers embedded in a list.

| each | x: [x, 1] | add;
# [1, 2], [2, 3] -> 2, 3, 3, 4

But this loses the original structure, if you wanted this instead

[2, 3], [3, 4]

This is what the bundle operator @ is for.

| @( each | x: [x, 1] | add );
# [1, 2], [2, 3] -> [2, 3], [3, 4]

When a pipe is prepended with @, this cases any events which result from this pipe to go into a list. Even if a given event results in no events from the pipe, it will still result in one result which is an empty list.

#Devices

These devices are changing as I'm experimenting and moving things around. This specification is not core to the language, but is just a baseline to enable some usefulness to the language for now.

In general, input devices take this format, as a 3 element tuple:

[type, data, state]

  • type - a string indicating the event type
  • data - data in any format associated with the event
  • state - the entire current payload of state (more on this later)

#Text Input

Input event matching the form: ['input', line, state]

  • line is a string containing a line entered into the textbox.

Analogous to standard in.

#Logging

Output event matching the form: ['log', message]

  • message is a string which is printed to the log.

#Drawing

Output event matching the form: ['draw', {type, ...data}]

Draws an element to screen

  • type is the type of element to draw
  • data includes anything needed for the type to draw
#Circle

To draw a circle ['draw', {type: 'circle', radius, pos: {x, y}]

#Line

To draw a circle ['draw', {type: 'line', start: {x, y}, end: {x, y}]

#Clear

To clear the screen ['draw', {type: 'clear'}]

#Store

Output event matching the form ['store', value, ...path]

Stores values to a given key in a path.

['store', value] will store value as the entire state. ['store', value, key] stores a value at a key. ['store', value, ...path] store a value at the path.

#Mouse

Input event matching the form ['mousemove', {x, y}, state] ['mouseup', nil, state] ['mousedown', nil, state]

#Keys

Input event matching the form ['keypress', key, state] ['keyup', key, state] ['keydown', key, state]

#Tick

Input matching the form ['tick', nil, state]

Do not follow this link