Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel is required to process package imports. If you need a different preprocessor remove all packages first.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML Settings

Here you can Sed posuere consectetur est at lobortis. Donec ullamcorper nulla non metus auctor fringilla. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec id elit non mi porta gravida at eget metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

HTML

            
              <div id=app>
  <div class=param>
    <code>
      a: {{a}}
    </code>
  </div>
  <div class=param>
    <code>
      b: {{b}}
    </code>
  </div>
  <div class=result>
    <code>
      a+b: {{a + b}}
    </code>
  </div>
  <div class=time>
    TIME: {{time}}
  </div>
</div>
            
          
!

CSS

            
              .param {
  background: #777;
  color: white;
  padding: 10px;
}
.result {
  background: black;
  color: green;
  padding: 10px;
}
.time {
  background: #ccc;
  color: black;
  padding: 10px;
  font-family: monospace
}
body {
  margin: 0;
}
            
          
!

JS

            
              class Stream {
  constructor(tokens) {
    this.tokens = tokens
    this.pos = 0
  }
  
  get next() {
    return this.tokens[this.pos++] || ''
  }
  
  cancel() {
    this.pos--
  }
  
  get done() {
    return this.pos >= this.tokens.length
  }
  
  get rest() {
    return this.tokens.slice(this.pos)
  }
  
  transaction(fn) {
    const rpos = this.pos
    const ret = fn()
    if (ret === failure) {
      this.pos = rpos
      return failure
    }
    return ret
  }
}

function preventRec(fn) {
  const stack = []
  return stream => {
    if (stack.includes(stream.pos)) {
      return ignore
    }
    stack.push(stream.pos)
    const ret = fn(stream)
    stack.pop()
    return ret
  }
}

const failure = Symbol('failure')
const ignore = Symbol('ignore')

const eq = str => stream => stream.transaction(() => stream.next == str ? ignore : failure)

const re = regex => fn => stream => stream.transaction(() => {
  const ret = stream.next.match(regex)
  if (!ret) return failure
  return fn(ret)
})

const assembly = (...parsers) => stream => stream.transaction(() => {
  console.log('assembly')
  let result = ignore
  for (const parser of parsers) {
    result = parser(result)(stream)
    if (result == failure)
      return failure
  }
  return result
})

const chain = (...parsers) => fn => stream => stream.transaction(() => {
  const arr = []
  for (const parser of parsers) {
    const result = parser(stream)
    if (result == failure)
      return failure
    if (result != ignore)
      arr.push(result)
  }
  return fn(...arr)
})

const choice = (...parsers) => stream => {
  for (const parser of parsers) {
    const result = parser(stream)
    if (result != failure)
      return result
  }
  return failure
}

const repeatRange = parser => (low, high = Number.MAX_SAFE_INTEGER) => stream => stream.transaction(() => {
  const arr = []
  let i = 0
  for (; i < high; i++) {
    const result = parser(stream)
    if (result == failure)
      break
    if (result != ignore)
      arr.push(result)
  }
  if (i < low || i > high) return failure
  return arr
})

const repeat = parser => number => repeatRange(parser)(number, number)

const many = parser => repeatRange(parser)(1)

const zeroOrMany = parser => repeatRange(parser)(0)

const maybe = parser => repeatRange(parser)(0, 1)

const must = parser => repeatRange(parser)(1, 1)

const packer = parser => fn => stream => {
  const ret = parser(stream)
  if (ret === failure)
    return failure
  return fn(ret)
}

const $log = (...param) => stream => {
  //console.log(...param, stream.rest)
  return ignore
}

const $lazy = fn => a => b => fn(a)(b)

let Parser
Parser = (() => store => {
  const number = re(/^\d+(\.\d+)?$/)(x => ({op: 'immi', value: parseFloat(x[0])}))
  const string = re(/^'((?:\\'|[^'])+)'$/)(x => ({op: 'imms', value: JSON.parse(`"${x[1].replace('"', '\\"')}"`)}))
  const name = re(/^\w+$/)(x => x[0])
  let expression
  const variable = chain(
    name,
    zeroOrMany(chain(
      eq('.'),
      name
    )(x => x))
  )((x, xs) => {
    const p = [x, ...xs]
    store.add(p.join('.'))
    return {op: 'arg', path: p}
  })
  const func_call = chain(
    preventRec(s => expression(s)),
    eq('('),
    maybe(
      chain(
        s => expression(s),
        zeroOrMany(
          chain(
            eq(','),
            s => expression(s)
          )(x => x)
        )
      )((x, xs) => [x, ...xs])
    ),
    eq(')')
  )((func, params) => ({op: 'call', func, params: params[0] || []}))
  const factor = choice(
    number,
    string,
    chain(
      eq('('),
      s => expression(s),
      eq(')')
    )(ex => ex),
    func_call,
    variable
  )
  const term = choice(
    chain(
      factor,
      many(
        chain(
          re(/^[*/%]$/g)(x => x[0]),
          factor
        )((...x) => x)
      )
    )((x, xs) => xs.reduce((a, [op, b]) => ({op, a, b}), x)),
    factor
  )
  expression = choice(
    chain(
      term,
      many(
        chain(
          re(/^[+-]$/g)(x => x[0]),
          term
        )((...x) => x)
      )
    )((x, xs) => xs.reduce((a, [op, b]) => ({op, a, b}), x)),
    term
  )
  return expression
})()

class Compiler {
  constructor(){
    this.store = new Set
    this.parser = Parser(this.store)
  }
  
  compile(code) {
    return this.pass3(this.pass2(this.pass1(code)))
  }
  
  tokenizer(str) {
    return str.match(/(?:'(?:\\'|[^'])+'|[\+\-\*\/\.\(\),]|\w+)/g)
  }
  
  pass1(code) {
    return this.parser(new Stream(this.tokenizer(code)))
  }
  
  pass2(ast) {
    const opmap = {
      '+': (a, b) => a + b,
      '-': (a, b) => a - b,
      '*': (a, b) => a * b,
      '/': (a, b) => a / b,
    }
    let simp
    simp = snode => {
      const node = Object.assign({}, snode)
      if (node.op in opmap) {
        node.a = simp(node.a)
        node.b = simp(node.b)
        if (node.a.op == 'immi' && node.b.op == 'immi')
          return {op: 'immi', value: opmap[node.op](node.a.value, node.b.value)}
      } else if (node.op == 'call') {
        node.params = node.params.map(param => simp(param))
      }
      return node
    }
    return simp(ast)
  }
  
  pass3(ast) {
    console.log(JSON.stringify(ast))
    const opmap = {
      '+': node => load => [...load(node.a), 'PUSH', ...load(node.b), 'SWAP', 'POP_', 'ADD_'],
      '-': node => load => [...load(node.a), 'PUSH', ...load(node.b), 'SWAP', 'POP_', 'SUB_'],
      '*': node => load => [...load(node.a), 'PUSH', ...load(node.b), 'SWAP', 'POP_', 'MUL_'],
      '/': node => load => [...load(node.a), 'PUSH', ...load(node.b), 'SWAP', 'POP_', 'DIV_'],
      'immi': node => load => [`IMMI ${node.value}`],
      'imms': node => load => [`IMMS ${node.value}`],
      'arg': node => load => [`ARG_ ${node.path[0]}`, ...node.path.slice(1).map(x => `PROP ${x}`)],
      'call': node => load => [...node.params.map(param => [...load(param), 'PUSH']).reduce((p, c) => [...p, ...c], []), `IMMI ${node.params.length}`, 'SWAP', ...load(node.func), 'CALL']
    }
    let walk
    walk = node => opmap[node.op](node)(walk)
    return walk(ast)
  }
}

class VM {
  load(code) {
    this.code = code
    return this
  }
  
  execute(variable) {
    let r0, r1
    const stack = []
    const opmap = {
      'ADD_': rest => r0 = r0 + r1,
      'SUB_': rest => r0 = r0 - r1,
      'MUL_': rest => r0 = r0 * r1,
      'DIV_': rest => r0 = r0 / r1,
      'IMMI': rest => r0 = parseFloat(rest),
      'IMMS': rest => r0 = rest,
      'ARG_': rest => r0 = variable[rest],
      'PROP': rest => r0 = r0[rest],
      'CALL': rest => r0 = r0(...[...Array(parseInt(r1))].map(() => stack.pop()).reverse()),
      'PUSH': rest => stack.push(r0),
      'POP_': rest => r0 = stack.pop(),
      'SWAP': rest => {
        const tmp = r0
        r0 = r1
        r1 = tmp
      }
    }
    for (const item of this.code) {
      const instruction = item.substring(0, 4).toUpperCase()
      const rest = item.substring(5)
      console.log(instruction, rest, {r0, r1, stack})
      opmap[instruction](rest)
    }
    return r0
  }
}

// const execute = (code, variable) => {
//   const compiler = new Compiler()
//   const vm = new VM()
//   return vm.load(compiler.compile(code)).execute(variable)
// }

const bindViewToData = (el, data) => {
  let triggered = false
  function doBind(record) {
    const [x, ...xs] = record.split('.').reverse()
    const tx = xs.reverse().reduce((o, c) => o[c], data)
    let cv = tx[x]
    Object.defineProperty(tx, x, {
      get() {
        return cv
      },
      set(value) {
        cv = value
        if (!triggered)
          setTimeout(update, 0)
        triggered = true
      }
    })
  }
  const compiler = new Compiler()
  const res = prepareBindViewToData(el, compiler)
  function update() {
    triggered = false
    const vm = new VM()
    res.forEach(({el, code: {text, dynamic}}) => el.data = text.map((t, i) => i < dynamic.length ? t + vm.load(dynamic[i]).execute(data) : t).join(''))
  }
  if (res.length > 0) {
    console.log(compiler)
    Array.from(compiler.store.keys()).forEach(doBind)
    update()
  }
}

const reg = /{{.*?}}/g

function prepareBindViewToData(el, compiler) {
  console.log(el)
  if (el instanceof Text){
    const [text, dynamic] = [el.data.split(reg), (el.data.match(reg) || []).map(code => compiler.compile(code))]
    if (dynamic.length == 0) return []
    return [{
      el,
      code: {text, dynamic}
    }]
  } else return Array.from(el.childNodes).reduce((p, c) => [...p, ...prepareBindViewToData(c, compiler)], [])
}

const appData = {
  a: 10,
  b: 20,
  time: new Date()
}

bindViewToData(document.querySelector("#app"), appData)

setInterval(() => {
  appData.b += 5
  appData.time = new Date()
}, 1000)
            
          
!
999px

Console