~kylep/pipes

19389d016345ef35479e01ce29a64847c1d87aa7 — Kyle Perik 2 months ago e0f71e1
Add example, fixes, bools, bundles, not
4 files changed, 48 insertions(+), 20 deletions(-)

M README.md
M src/device.js
M src/parser.js
M src/runtime.js
M README.md => README.md +2 -0
@@ 24,6 24,8 @@ These links point point to urls which encode the full text of the program, so th
TODO: This uses mousemove to tick. Will reintroduce tick as soon as I can make errors stop shooting out like a firehose during editing.
[Basic snake game](https://kyleperik.com/pipes/#ZGVmIGxvZyBtOgogIFsnbG9nJywgbV07CmRlZiBzZXRzdGF0ZSBzdGF0ZTogCiAgWydzZXRfc3RhdGUnLCBzdGF0ZV07CmRlZiBpbnB1dCBbJ2lucHV0JywgbSwgc106IG07CmRlZiBkcmF3IHggOiBbJ2RyYXcnLCB4XTsKZGVmIGNsZWFyCiAgeCA6IHt0eXBlOiAnY2xlYXInfSB8IGRyYXc7CgpkZWYgbW9kICgKICBbbiwgbV0gPyAoWwogICAgKFtuLCAwXSB8IGd0ZSksCiAgICAoW24sIG1dIHwgbHQpLAogIF0gfCBhbmQpOiBuOwogIFtuLCBtXSA/IChbbiwgbV0gfCBndGUpOgogIFsoW24sIG1dIHwgc3ViKSwgbV0gfCBtb2Q7CiAgW24sIG1dID8gKFtuLCAwXSB8IGx0KToKICBbKFtuLCBtXSB8IGFkZCksIG1dIHwgbW9kOwopOwpkZWYgc2NyZWVud3JhcCB7eCwgeX06CnsKICB4OiAoW3gsIDIwMF0gfCBtb2QpLAogIHk6IChbeSwgMjAwXSB8IG1vZCksCn07CgpkZWYgdmVjYWRkIFsKICB7eDogeGEsIHk6IHlhfSwgCiAge3g6IHhiLCB5OiB5Yn0KXTogewogIHg6IChbeGEsIHhiXSB8IGFkZCksCiAgeTogKFt5YSwgeWJdIHwgYWRkKQp9OwpkZWYgdmVjbXVsCiAgW3t4LCB5fSwgc10gOiB7CiAgICB4OiAoW3gsIHNdIHwgbXVsKSwKICAgIHk6IChbeSwgc10gfCBtdWwpLAogIH07CgpkZWYgc2xpY2UgKAogIFtwYXJ0cywgbGVuZ3RoXTogWwogICAgcGFydHMsIGxlbmd0aCwgW10KICBdIHwgc2xpY2U7CgogIFtwYXJ0cywgMCwgcmVzdWx0XTogcmVzdWx0OwoKICBbW10sIGxlbmd0aCwgcmVzdWx0XSAKICA/IChbbGVuZ3RoLCAwXSB8IGd0KTogcmVzdWx0OwoKICBbW2hlYWQsIC4uLnBhcnRzXSwgbGVuZ3RoLCByZXN1bHRdCiAgPyAoW2xlbmd0aCwgMF0gfCBndCk6IFsKICAgcGFydHMsCiAgIChbbGVuZ3RoLCAxXSB8IHN1YiksCiAgIFsuLi5yZXN1bHQsIGhlYWRdCiAgXSB8IHNsaWNlOwopOwoKZGVmIGNpcmNsZQogIHBvcyA6IHsKICAgIHR5cGU6ICdjaXJjbGUnLAogICAgcmFkaXVzOiAxMCwKICAgIHBvcwogIH0gfCBkcmF3OwoKZGVmIGVhY2ggKAogIFtwYXJ0LCAuLi5yZXN0XTogcmVzdCB8IGVhY2g7CiAgW3BhcnQsIC4uLnJlc3RdOiBwYXJ0OwopOwoKWydpbml0JywgbnVsbCwgc106IHsKICBwYXJ0czogW3sgeDogNTAsIHk6IDUwIH1dLAogIGRpcmVjdGlvbjogeyB4OiAxLCB5OiAwIH0sCiAgbGVuZ3RoOiAyMCwKfSB8IHNldHN0YXRlOwoKWydrZXlwcmVzcycsIGtleSwgc3RhdGVdOiBrZXkKfCAoCiAgJ2EnOiB7IHg6IC0xLCB5OiAwIH07CiAgJ2QnOiB7IHg6IDEsIHk6IDAgfTsKICAndyc6IHsgeDogMCwgeTogLTEgfTsKICAncyc6IHsgeDogMCwgeTogMSB9OwopCnwgZGlyZWN0aW9uOiB7IGRpcmVjdGlvbiB9Cnwgc2V0c3RhdGU7CgpbJ21vdXNlbW92ZScsIG51bGwsIHN0YXRlXTogc3RhdGUKfCB7cGFydHM6IFtoZWFkLCAuLi5wYXJ0c10sIGRpcmVjdGlvbiwgbGVuZ3RofTogewogIHBhcnRzOiAoW1sKICAgIChbCiAgICAgIGhlYWQsCiAgICAgIChbZGlyZWN0aW9uLCAzXSB8IHZlY211bCkKICAgIF0gfCB2ZWNhZGQpIHwgc2NyZWVud3JhcCwKICAgIGhlYWQsCiAgICAuLi5wYXJ0cwogIF0sIGxlbmd0aF0gfCBzbGljZSkKfSB8ICgKICBzZXRzdGF0ZTsKICBjbGVhcjsKICB7cGFydHN9OiBwYXJ0cwogIHwgZWFjaCB8IGNpcmNsZTsKKTs=)

[Fizzbuzz](https://kyleperik.com/pipes/#aW5wdXQgfCByYW5nZSB8IGZpenpidXp6IHwgbG9nOwoKZGVmIGZpenpidXp6Cm46IFsKICBuLAogIChbbiwgM10gfCBkaXZpc2libGUpLAogIChbbiwgNV0gfCBkaXZpc2libGUpLApdCnwgKAogIFtuLCB0cnVlLCB0cnVlXTogJ2ZpenpidXp6JzsKICBbbiwgdHJ1ZSwgZmFsc2VdOiAnZml6eic7CiAgW24sIGZhbHNlLCB0cnVlXTogJ2J1enonOwogIFtuLCBmYWxzZSwgZmFsc2VdOiBuOwopOwoKZGVmIGRpdmlzaWJsZSBtb2QgfCBuOiBbbiwgMF0gfCBlcTsKCgpkZWYgcmFuZ2UgbiA/KFtuLCAwXSB8IGd0KTogW24sIDFdCnwgc3ViIAp8ICgKICBuOiBuOwogIHJhbmdlOwopOwoKZGVmIGlucHV0CiAgWydpbnB1dCcsIHZhbHVlLCBzXTogdmFsdWU7CgpkZWYgbG9nCiAgdmFsdWU6IFsnbG9nJywgdmFsdWVdOwoK)

## Overview

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

M src/device.js => src/device.js +1 -1
@@ 74,7 74,7 @@ function processEvent (payload) {
  if (!runningProgram) {
    return
  }
  send([...payload, state], runningProgram.patterns, runningProgram.definitions).forEach(handle)
  run([...payload, state], runningProgram.patterns, runningProgram.definitions).forEach(handle)
}

function draw (element) {

M src/parser.js => src/parser.js +26 -16
@@ 163,6 163,10 @@ function parsePatternFragment (tokens) {
    if (!isNaN(number)) {
      return { type: LIT, value: number }
    }
    const bool = {'true': true, 'false': false}[token]
    if (typeof bool !== 'undefined') {
      return { type: LIT, value: bool };
    }
    // This is a single token, which we assume is a reference
    return {
      type: REF,


@@ 171,33 175,39 @@ function parsePatternFragment (tokens) {
  }
}

function processPipeToken (r, tokens) {
function getPipeResult(tokens) {
  if (tokens.length !== 1) {
    console.error(r)
    throw Error(`Only one token allowed for pipe: ${JSON.stringify(tokens)}`)
  }
  token = tokens[0]
  token = tokens[0];
  if (typeof token === 'object') {
    if (token.type === 'parens') {
      return {
        ...r,
        mode: 'pending',
        pendingPatterns: r.pendingPatterns.concat([
          {
            type: GROUP,
            patterns: parseGroups(token.tokens).patterns
          }
        ])
        type: GROUP,
        patterns: parseGroups(token.tokens).patterns
      }
    }
  }
  return {
    type: REF,
    value: token
  }
}

function processPipeToken (r, allTokens) {
  const bundle = allTokens[0] === '@';
  const tokens = bundle ? allTokens.slice(1) : allTokens;
  const result = bundle ? {
    type: BUNDLE,
    value: getPipeResult(tokens)
  } : getPipeResult(tokens);
  return {
    ...r,
    mode: 'pending',
    pendingPatterns: r.pendingPatterns.concat([{
      type: REF,
      value: token
    }])
    pendingPatterns: r.pendingPatterns.concat([
      result
    ])
  }
}



@@ 206,7 216,7 @@ function processPatternMatch(r, tokens) {
    ...r,
    mode: 'pending',
    match: null,
    opeartion: null,
    condition: null,
    pendingPatterns: r.pendingPatterns.concat([{
      type: PATTERN,
      match: r.match,


@@ 339,7 349,7 @@ function parseGroups (tokenGroups) {
}

function parse (body) {
  const tokenRegex = /({|}|\[|\]|\n|'|,|:|;|\?|\s+|[A-Za-z]+)/g;
  const tokenRegex = /({|}|\[|\]|\n|'|,|:|;|\?|@|\s+|[A-Za-z]+)/g;
  const allTokens = body.split(tokenRegex).filter(token => token)

  const tokenGroups = groupTokens(allTokens)

M src/runtime.js => src/runtime.js +19 -3
@@ 6,6 6,7 @@ const REF = 'reference'
const REST = 'rest'
const GROUP = 'group'
const PATTERN = 'pattern'
const BUNDLE = 'bundle'

function checkMatch (data, match, refs=[]) {
  if (match.type === LIT) {


@@ 66,12 67,21 @@ function checkMatch (data, match, refs=[]) {
  throw new Error(`Match type not recognized ${match.type}`)
}

function mod(a, b) {
  return Math.abs(a % b)
}

function handleOp(result, refs, defs) {
  // TODO: Better way to handle any arity ops in a standard way
  if (result.op === 'not') {
    return buildResult(result.operands, refs, defs).flatMap(a => !a)
  }
  const func = {
    add: (a, b) => a + b,
    sub: (a, b) => a - b,
    mul: (a, b) => a * b,
    div: (a, b) => a / b,
    mod: (a, b) => mod(a, b),
    // TODO: check equality by value, not by reference
    eq: (a, b) => a === b,
    gt: (a, b) => a > b,


@@ 150,6 160,9 @@ function handlePipe (data, pipe, definitions) {
    return send(data, definition, definitions)
  } else if (pipe.type === GROUP) {
    return send(data, pipe.patterns, definitions)
  } else if (pipe.type === BUNDLE) {
    const result = handlePipe(data, pipe.value, definitions)
    return [result]
  } else if (pipe.type === PATTERN) {
    const match = checkMatch(data, pipe.match)
    if (!match.matched) {


@@ 183,14 196,17 @@ function getOpDef (op) {
const standardDefinitions = [
  'add', 'sub', 'mul', 'div',
  'eq', 'gt', 'lt', 'gte',
  'and', 'or'
  'and', 'or', 'not', 'mod'
].reduce((r, op) => ({...r, [op]: [[getOpDef(op)]]}), {})

// Run a payload through the program
function send (data, patterns, definitions) {
  const allDefinitions = {...definitions, ...standardDefinitions}
  return patterns.flatMap(pipes => pipes.reduce((r, pipe) => {
    return r.flatMap(d => handlePipe(d, pipe, allDefinitions))
    return r.flatMap(d => handlePipe(d, pipe, definitions))
  }, [data]))
}

function run (data, patterns, definitions) {
  const allDefinitions = {...definitions, ...standardDefinitions}
  return send(data, patterns, allDefinitions)
}