neft
Version:
Universal Platform
281 lines (233 loc) • 8.26 kB
text/coffeescript
'use strict'
babel = require 'babel-core'
utils = require 'src/utils'
bindingParser = require 'src/binding/parser'
Renderer = require 'src/renderer'
nmlAst = require './nmlAst'
{BABEL_OPTIONS} = require 'lib/module-cache'
PRIMITIVE_TYPE = 'primitive'
PUBLIC_BINDING_VARIABLES =
__proto__: null
document: true
app: true
PUBLIC_BINDING_IDS =
__proto__: null
windowItem: true
RESERVED_MAIN_IDS =
__proto__: null
New: true
{BINDING_THIS_TO_TARGET_OPTS} = bindingParser
BINDING_PARSER_OPTS =
modifyBindingPart: (elem) ->
# Prefix all Renderer.* direct access by the namespace accessible in style file
if Renderer[elem[0]]
elem.unshift 'Neft', 'Renderer'
elem
transformByBabel = (body) ->
body = "(function(){#{body}})"
body = babel.transform(body, BABEL_OPTIONS).code
body = /{([^]*)}/m.exec(body)?[1]
body
class Stringifier
constructor: (@ast, @path) ->
@lastUID = 0
@objects = nmlAst.forEachLeaf
ast: @ast, onlyType: nmlAst.OBJECT_TYPE, includeGiven: true,
includeValues: true, deeply: true
@ids = @objects.map (elem) -> elem.id
@publicIds = @ids.filter (elem) -> elem[0] isnt '_'
getNextUID: ->
"_r#{@lastUID++}"
isBindingPublicVariable: (id) =>
PUBLIC_BINDING_VARIABLES[id] or utils.has(@publicIds, id)
isBindingPublicId: (id) =>
id is 'this' or PUBLIC_BINDING_IDS[id] or
@isBindingPublicVariable(id) or Renderer[id]?
stringifyObject: (ast) ->
opts = @getObjectOpts ast
opts or ast.id
stringifyAttribute: (ast) ->
{value} = ast
if Array.isArray(value)
@stringifyAttributeListValue value
else if value?.type
@stringifyAnyObject value
else if @isAnchor(ast)
@anchorToString value
else if bindingParser.isBinding(value)
@bindingToString value
else
value
stringifyAttributeListValue: (values) ->
types = values.map (value) -> value.type
useObject = utils.has types, nmlAst.ATTRIBUTE_TYPE
useObject ||= utils.has types, nmlAst.FUNCTION_TYPE
useObject ||= utils.has types, PRIMITIVE_TYPE
if useObject
@stringifyOpts values
else
children = (@stringifyAnyObject child for child in values)
"[#{children.join ', '}]"
stringifyFunction: (ast) ->
# arguments
args = ''
if args and String(ast.params)
args += ","
args += String(ast.params)
# code
code = transformByBabel ast.code
"`function(#{args}){#{code}}`"
createClass: (ast) ->
changes = []
body = []
for child in ast.body
switch child.type
when nmlAst.ATTRIBUTE_TYPE, nmlAst.FUNCTION_TYPE
changes.push child
else
body.push child
object =
type: nmlAst.OBJECT_TYPE
name: 'Class'
id: @getNextUID()
body: [
{
type: nmlAst.ATTRIBUTE_TYPE
name: 'changes'
value: changes
},
body...
]
@objects.push object
@ids.push object.id
object
stringifyCondition: (ast) ->
object = @createClass ast
object.body.unshift
type: nmlAst.ATTRIBUTE_TYPE
name: 'when'
value:
type: PRIMITIVE_TYPE
value: @bindingToString ast.condition, BINDING_THIS_TO_TARGET_OPTS
@stringifyObject object
stringifySelect: (ast) ->
object = @createClass ast
object.body.unshift
type: nmlAst.ATTRIBUTE_TYPE
name: 'document.query'
value: ast.query
@stringifyObject object
stringifyPrimitiveType: (ast) ->
ast.value
stringifyAnyObject: (ast) ->
switch ast.type
when nmlAst.ATTRIBUTE_TYPE
@stringifyAttribute ast
when nmlAst.FUNCTION_TYPE
@stringifyFunction ast
when nmlAst.OBJECT_TYPE
@stringifyObject ast
when nmlAst.CONDITION_TYPE
@stringifyCondition ast
when nmlAst.SELECT_TYPE
@stringifySelect ast
when PRIMITIVE_TYPE
@stringifyPrimitiveType ast
else
throw new Error "Unknown NML object '#{ast.type}'"
getObjectOpts: (ast) ->
ids = []
attributes = []
functions = []
children = []
for child in ast.body
switch child.type
when nmlAst.ID_TYPE
ids.push child
when nmlAst.ATTRIBUTE_TYPE
attributes.push child
when nmlAst.FUNCTION_TYPE
functions.push child
when nmlAst.OBJECT_TYPE, nmlAst.CONDITION_TYPE, nmlAst.SELECT_TYPE
children.push child
when nmlAst.PROPERTY_TYPE, nmlAst.SIGNAL_TYPE
null
else
throw new Error "Unknown object type '#{child.type}'"
opts = []
for elem in ids
opts.push
type: PRIMITIVE_TYPE
name: 'id'
value: "\"#{elem.value}\""
unless utils.isEmpty(attributes)
opts.push attributes...
unless utils.isEmpty(functions)
opts.push functions...
unless utils.isEmpty(children)
opts.push
type: nmlAst.ATTRIBUTE_TYPE
name: 'children'
value: children
unless utils.isEmpty(opts)
"_RendererObject.setOpts(#{ast.id}, #{@stringifyOpts opts})"
bindingToString: (value, opts = 0) ->
binding = bindingParser.parse value, @isBindingPublicId, opts,
BINDING_PARSER_OPTS, @isBindingPublicVariable
func = "`function(){return #{binding.hash}}`"
"[#{func}, [#{binding.connections}]]"
isAnchor: (ast) ->
ast.name.indexOf('anchors.') is 0
anchorToString: (value) ->
JSON.stringify value.split '.'
getIdsObject: (ast) ->
elems = []
for key in @ids
elems.push "\"#{key}\": #{key}"
"{#{elems.join ', '}}"
stringifyOpts: (opts) ->
results = ("\"#{opt.name}\": #{@stringifyAnyObject opt}" for opt in opts)
"{#{results.join ', '}}"
getItemsCreator: ->
result = ""
for child in @objects
result += "#{child.id} = #{child.name}.New()\n"
if @path
result += "#{child.id}._path = \"#{@path}\"\n"
result
getItemsProperties: ->
result = ""
for child in @objects
properties = nmlAst.forEachLeaf ast: child, onlyType: nmlAst.PROPERTY_TYPE
for property in properties
result += "_RendererObject.createProperty(#{child.id}, \"#{property.name}\")\n"
result
getItemsSignals: ->
result = ""
for child in @objects
signals = nmlAst.forEachLeaf ast: child, onlyType: nmlAst.SIGNAL_TYPE
for signal in signals
result += "_RendererObject.createSignal(#{child.id}, \"#{signal.name}\")\n"
result
stringify: ->
if RESERVED_MAIN_IDS[@ast.id]
throw new Error "Reserved NML id '#{@ast.id}'"
result = ""
itemCode = @getObjectOpts @ast
# create items
result += @getItemsCreator()
result += @getItemsProperties()
result += @getItemsSignals()
# put main item
if itemCode
result += "#{itemCode}\n"
# call onReady
for child in @objects
if not Renderer[child.name] or 'onReady' of Renderer[child.name]::
result += "#{child.id}.onReady.emit()\n"
# return
result += "objects: #{@getIdsObject @ast}\n"
result += "item: #{@ast.id}"
result
exports.stringify = (ast, path) ->
new Stringifier(ast, path).stringify()