tumble/src/parser.coffee

95 lines
2.9 KiB
CoffeeScript
Raw Normal View History

2013-04-27 20:53:31 +00:00
_ = require 'underscore'
class Contexter
constructor: (@content) ->
@stack = [@content]
@templates = {}
@depth = 0
has_any: (name) ->
2016-06-29 14:34:18 +00:00
# Scan the parse stack from more recent to most distant,
# return the reference that contains this name.
_.find @stack, (o) -> _.has(o, name)
2013-04-27 20:53:31 +00:00
has_any_one: (name) ->
2016-06-29 14:34:18 +00:00
# Returns the most recent key seen on this stack, if any.
2013-04-27 20:53:31 +00:00
p = @has_any(name)
if p then p[name] else null
has: (name) ->
2016-06-29 14:34:18 +00:00
# Returns references ONLY from the most recent context.
2013-04-27 20:53:31 +00:00
if @stack[0][name]? then @stack[0][name] else null
get: (name, alt = '') ->
2016-06-29 14:34:18 +00:00
# Scalars only
2013-04-27 20:53:31 +00:00
p = @has_any_one(name)
2016-06-29 14:34:18 +00:00
return p if p and (_.isString(p) or _.isNumber(p))
return @render(name)
2013-04-27 20:53:31 +00:00
once: (obj, cb) ->
2016-06-29 14:34:18 +00:00
# Create a new context, execute the block associated with that
# context, pop the context, and return the production.
2013-04-27 20:53:31 +00:00
@stack.unshift obj
2016-06-29 14:34:18 +00:00
@depth++
throw new Error('recursion-error') if @depth > 10
r = cb @
2013-04-27 20:53:31 +00:00
@stack.shift()
2016-06-29 14:34:18 +00:00
@depth--
2013-04-27 20:53:31 +00:00
r
2016-06-29 14:34:18 +00:00
when: (name, cb) ->
# Execute and return this specified block if and only if the
# requested context is valid.
2013-04-27 20:53:31 +00:00
p = @has_any_one(name)
2016-06-29 14:34:18 +00:00
if p then cb(@) else ''
if: (name, cb) ->
# Execute and return this specifiecd block if and only if the
# requested context is valid AND current
p = @has(name)
if p then cb(@) else ''
2013-04-27 20:53:31 +00:00
block: (name, cb) ->
2016-06-29 14:34:18 +00:00
# Execute and return this specified block if and only if the
# requested context is valid and entrant.
2013-04-27 20:53:31 +00:00
p = @has_any_one(name)
if p and _.isObject(p) then @once(p, cb) else ''
many: (name, cb) ->
2016-06-29 14:34:18 +00:00
# Execute and return this specified block for each element of
# the specified context if and only if the requested context
# is valid and is iterable.
2013-04-27 20:53:31 +00:00
ps = @has(name)
2016-06-29 14:34:18 +00:00
return "" unless (ps and _.isArray(ps))
2013-04-27 20:53:31 +00:00
(_.map ps, (p) => @once(p, cb)).join('')
2016-06-29 14:34:18 +00:00
template: (name, cb) ->
# Store the specified block under a name. No production.
2013-04-27 20:53:31 +00:00
@templates[name] = cb
2016-06-29 14:34:18 +00:00
return ""
2013-04-27 20:53:31 +00:00
render: (name) ->
2016-06-29 14:34:18 +00:00
if @templates[name]? and _.isFunction(@templates[name])
@depth++
throw new Error('recursion-error') if @depth > 10
ret = @templates[name](@)
@depth--
ret
else
""
2013-04-27 20:53:31 +00:00
# This is really the compiler at this point...
2013-04-27 20:53:31 +00:00
module.exports = (ast, data) ->
context = new Contexter(data)
cmd = (o) ->
switch o.unit
when 'variable' then (context) -> context.get(o.name)
when 'text' then (context) -> o.content
when 'block' then (context) -> context[o.type] o.name, (context) ->
(cmd(p)(context) for p in o.content).join("")
(cmd(o)(context) for o in ast.content).join("")