here-be / snapdragon

snapdragon is an extremely pluggable, powerful and easy-to-use parser-renderer factory.

Home Page:https://github.com/here-be

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

TypeError: expected an object, function, or array

rbecheras opened this issue · comments

I got this error using latest version of snapdragon

/home/remi/app/node_modules/snapdragon/node_modules/define-property/index.js:18
    throw new TypeError('expected an object, function, or array');
    ^

TypeError: expected an object, function, or array
    at defineProperty (/home/remi/app/node_modules/snapdragon/node_modules/define-property/index.js:18:11)
    at Parser.eos (/home/remi/app/node_modules/snapdragon/lib/parser.js:434:9)
    at Parser.parse (/home/remi/app/node_modules/snapdragon/lib/parser.js:536:20)
    at Object.<anonymous> (/home/remi/app/src/lib/index.js:15:20)
    at Module._compile (module.js:652:30)
    at loader (/home/remi/app/node_modules/babel-register/lib/node.js:144:5)
    at Object.require.extensions.(anonymous function) [as .js] (/home/remi/app/node_modules/babel-register/lib/node.js:154:7)
    at Module.load (module.js:565:32)
    at tryModuleLoad (module.js:505:12)
    at Function.Module._load (module.js:497:3)

using this parser:

function parser () {
  const pos = this.position()
  const match = this.match(/^\s*(if)\s*\(\s*(.*?)\s*\)\s*(?=then)/)
  const prev = this.prev()

  if (!match) return

  const ifNode = pos(this.node({
    type: 'ConditionnalState.if',
    val: match[1],
    condition: match[2]
  }))

  const conditionnalStateNode = pos(this.node({
    type: 'ConditionnalState',
    nodes: [ifNode]
  }))

  this.push('ConditionnalState', conditionnalStateNode)
  prev.nodes.push(conditionnalStateNode)
}

but if I change it for the following

function parser () {
  const pos = this.position()
  const match = this.match(/^\s*(if)\s*\(\s*(.*?)\s*\)\s*(?=then)/)
    // const prev = this.prev()

    if (!match) return

    const ifNode = pos(this.node({
      type: IfConditionnalStateParser.type,
      val: match[1],
      condition: match[2]
    }))

    // const conditionnalStateNode = pos(this.node({
    //   type: 'ConditionnalState',
    //   nodes: [ifNode]
    // }))

    // this.push('ConditionnalState', conditionnalStateNode)
    // prev.nodes.push(conditionnalStateNode)

    return ifNode
}

Here, there is no error.

I can't understand why that error.

It is the bundled eos() parser that throw TypeError because prev.parent is undefined.

define(prev.parent, 'escaped', true);

But I don't know why/when node should/must have a parent property.

I see that the condition for that line is hasOpenAndClose(prev) but I dont know hat to do with that.

I'm trying to create an AST to parse plantuml files (for activity diagrams only for now).

Here is the actual tree I get (when I use the second version of the mentionned parser, the first make eos() throw

Node {
  type: 'root',
  errors: [],
  nodes:
   [ Node { type: 'bos', val: '' },
     Node {
       type: 'Uml',
       title: 'Activité Eolienne',
       nodes:
        [ Node {
            type: 'Activity',
            nodes:
             [ Node { type: 'StaticState', val: 'Je veux installer une éolienne' },
               Node {
                 type: 'ConditionnalState.if',
                 val: 'if',
                 condition: 'Hauteur >= 12m (mât + nacelle)' },
               Node { type: 'ConditionnalState.then', val: 'then' },
               Node { type: 'Space', val: ' ' },
               Node { type: 'ConditionnalState.condition.open', val: '(' },
               Node { type: 'All', val: 'OUI)' },
               Node { type: 'NewLine', val: '\n' },
               Node { type: 'StaticState', val: 'CERFA = PC' },
               Node { type: 'Space', val: '  ' },
               Node { type: 'Activity.stop', val: 'stop' },
               Node { type: 'NewLine', val: '\n' },
               Node { type: 'ConditionnalState.else', val: 'else' },
               Node { type: 'Space', val: ' ' },
               Node { type: 'ConditionnalState.condition.open', val: '(' },
               Node { type: 'All', val: 'NON)' },
               Node { type: 'NewLine', val: '\n' },
               Node {
                 type: 'ConditionnalState.if',
                 val: 'if',
                 condition: 'Secteur protégé' },
               Node { type: 'ConditionnalState.then', val: 'then' },
               Node { type: 'Space', val: ' ' },
               Node { type: 'ConditionnalState.condition.open', val: '(' },
               Node { type: 'All', val: 'OUI)' },
               Node { type: 'NewLine', val: '\n' },
               Node { type: 'StaticState', val: 'CERFA = DP' },
               Node { type: 'Space', val: '    ' },
               Node { type: 'Activity.stop', val: 'stop' },
               Node { type: 'NewLine', val: '\n' },
               Node { type: 'Space', val: '  ' },
               Node { type: 'ConditionnalState.else', val: 'else' },
               Node { type: 'Space', val: ' ' },
               Node { type: 'ConditionnalState.condition.open', val: '(' },
               Node { type: 'All', val: 'NON)' },
               Node { type: 'NewLine', val: '\n' },
               Node { type: 'StaticState', val: 'Aucune autorisation nécessaire' },
               Node { type: 'StaticState', val: 'Retour au début de l\'assistant' },
               Node { type: 'Space', val: '    ' },
               Node { type: 'DetachPuml', val: 'detach' },
               Node { type: 'NewLine', val: '\n' },
               Node { type: 'All', val: 'endif' },
               Node { type: 'NewLine', val: '\n' } ],
            val: '\\undefined' },
          Node { type: 'All', val: '' } ] },
     Node { type: 'eos', val: '' } ] }

But I don't know why/when node should/must have a parent property.

Every node can or should have a parent. For example:

const ast = new Node({
  type: 'root',
  nodes: []
});

const block = new Node({
  type: 'block',
  nodes: []
});

// instead of doing ast.nodes.push(block), we want to use `ast.push()`
// so that ast is properly set as the parent of the block node
ast.push(block); 

const block = new Node({
  type: 'block',
  nodes: []
});

const otherNode = new Node({
  type: 'other',
  value: 'foo'
});

// same here
block.push(otherNode); 

console.log(otherNode.parent.type) //=> 'block'
console.log(otherNode.parent.parent.type) //=> 'root'

In your example, you're doing prev.nodes.push(conditionnalStateNode). Instead, try doing prev.push(conditionnalStateNode).

That said, I don't think it is (or should be) required. If it's throwing an error b/c node.parent is not defined, that might be a bug, or something is else is happening.

If you want I can help you get this figured out.

Hi Jon thank you for your time.

I naturally tried prev.push() instead of prev.nodes.push() but Node.prototype.push doesnt exist.

exemple:

/home/remi/app/src/lib/parsers/uml/EndUmlParser.js:39
    parent.push(close);
           ^

TypeError: parent.push is not a function
    at Parser.parse (/home/remi/app/src/lib/parsers/uml/EndUmlParser.js:27:12)
    at Parser.getNext (/home/remi/app/node_modules/snapdragon/lib/parser.js:471:36)
    at Parser.advance (/home/remi/app/node_modules/snapdragon/lib/parser.js:494:24)
    at Parser.parse (/home/remi/app/node_modules/snapdragon/lib/parser.js:529:29)
    at Object.<anonymous> (/home/remi/app/src/lib/index.js:15:20)
    at Module._compile (module.js:652:30)
    at loader (/home/remi/app/node_modules/babel-register/lib/node.js:144:5)
    at Object.require.extensions.(anonymous function) [as .js] (/home/remi/app/node_modules/babel-register/lib/node.js:154:7)
    at Module.load (module.js:565:32)
    at tryModuleLoad (module.js:505:12)

But I saw there is snapdragon-util .pushNode() to do the same.
I'm investigating this way

but Node.prototype.push doesnt exist.

If .push() doesn't exist, that means .prev is not an instance of Node.

you can also do:

parent.nodes.push(node);
node.parent = parent;

edit: .parent is useful in an AST when you need to access that object with information about nodes contained in a block.

Is having *.open and *.close nodes at first and last position nested in a parent node reaaly important ? required ?

Is having *.open and *.close nodes at first and last position nested in a parent node reaaly important ? reauired ?

No, not required or important. It's just really useful IMHO. It takes much less code to do certain things than other conventions I see.

also, open and close nodes usually represent a character or character sequence. like:

// given the string "${name}"

const block = new Node({
  type: 'block'
  nodes: []
});

const open = new Node({
  type: 'block.open',
  value: '${'
});

const close = new Node({
  type: 'block.close',
  value: '}'
});

block.push(open);
block.push(new Node({ type: 'variable', value: 'name' }));
block.push(close);

The, let's say there is something special about name. You can then easily set a value on the parent node ("block" in the example), to let other child nodes access that information when compiling.

In fact the latest snapdragon release (v0.12.0) depends on snapdragon-node v1.0.6 and that version doesnt implement a #push() method but a #pushNode() method.

"snapdragon-node": "^1.0.6",

https://github.com/here-be/snapdragon-node/blob/f53bb87e762536695c2440de55aa64f51cef7fb0/index.js#L70

Thank you Jon, using Node#push() or Node#pushNode(), in fact, defining properly parent when nesting nodes, solved my issue.

My parser looks like that now

function parser () {
  if (!this.isInside('uml')) {
    return
  }

  const pos = this.position()
  const match = this.match(IfConditionnalStateParser.pattern)
  const prev = this.prev()

  if (!match) return

  const conditionnalStateNode = pos(this.node({
    type: 'conditionnalState'
  }))

  const ifNode = pos(this.node({
    type: 'conditionnalState.if',
    val: match[1],
    condition: match[2]
  }))

  this.push('conditionnalState', conditionnalStateNode)
  conditionnalStateNode.pushNode(ifNode)
  prev.pushNode(conditionnalStateNode)
}

And my ast look like that (it is a work in progress)

'Root AST'
Node {
  type: 'root',
  errors: [],
  nodes:
   [ Node { type: 'bos', val: '' },
     Node {
       type: 'uml',
       title: 'Activité Eolienne',
       nodes:
        [ Node { type: 'uml.open', val: '@startuml' },
          Node {
            type: 'activity',
            nodes:
             [ Node { type: 'activity.start' },
               Node { type: 'StaticState', val: 'Je veux installer une éolienne' },
               Node {
                 type: 'ConditionnalState',
                 nodes:
                  [ Node {
                      type: 'conditionnalState.if',
                      val: '\\if',
                      condition: 'Hauteur >= 12m (mât + nacelle)' },
                    Node { type: 'ConditionnalState.then', val: 'then' },
                    Node { type: 'Space', val: ' ' },
                    Node { type: 'ConditionnalState.condition.open', val: '(' },
                    Node { type: 'All', val: 'OUI)' },
                    Node { type: 'NewLine', val: '\n' },
                    Node { type: 'StaticState', val: 'CERFA = PC' },
                    Node { type: 'Space', val: '  ' },
                    Node { type: 'Activity.stop', val: 'stop' },
                    Node { type: 'NewLine', val: '\n' },
                    Node { type: 'ConditionnalState.else', val: 'else' },
                    Node { type: 'Space', val: ' ' },
                    Node { type: 'ConditionnalState.condition.open', val: '(' },
                    Node { type: 'All', val: 'NON)' },
                    Node { type: 'NewLine', val: '\n' },
                    Node {
                      type: 'ConditionnalState',
                      nodes:
                       [ Node {
                           type: 'conditionnalState.if',
                           val: 'if',
                           condition: 'Secteur protégé' },
                         Node { type: 'ConditionnalState.then', val: 'then'},
                         Node { type: 'Space', val: ' ' },
                         Node { type: 'ConditionnalState.condition.open', val: '(' },
                         Node { type: 'All', val: 'OUI)' },
                         Node { type: 'NewLine', val: '\n' },
                         Node { type: 'StaticState', val: 'CERFA = DP' },
                         Node { type: 'Space', val: '    ' },
                         Node { type: 'Activity.stop', val: 'stop' },
                         Node { type: 'NewLine', val: '\n' },
                         Node { type: 'Space', val: '  ' },
                         Node { type: 'ConditionnalState.else', val: 'else'},
                         Node { type: 'Space', val: ' ' },
                         Node { type: 'ConditionnalState.condition.open', val: '(' },
                         Node { type: 'All', val: 'NON)' },
                         Node { type: 'NewLine', val: '\n' },
                         Node { type: 'StaticState', val: 'Aucune autorisation nécessaire' },
                         Node { type: 'StaticState', val: 'Retour au début de l\'assistant' },
                         Node { type: 'Space', val: '    ' },
                         Node { type: 'DetachPuml', val: 'detach' },
                         Node { type: 'NewLine', val: '\n' },
                         Node { type: 'All', val: 'endif' },
                         Node { type: 'NewLine', val: '\n' } ] },
                    Node { type: 'All', val: '' } ] } ] },
          Node { type: 'uml.close', val: '@enduml' } ] } ] }