Home > Software design >  Struggling to iterate through an AST
Struggling to iterate through an AST

Time:11-26

I'm currently working with this package for Markdown in my application (https://github.com/Khan/perseus/tree/main/packages/simple-markdown), and in the README.md they describe what their AST looks like for parsing markdown.

I'm currently trying to recurse/iterate through this tree, and find all of the nodes that are of type "em", to replace the content with "this is italics" (basically just to try to make sure I have the right AST).

Problem is, this isn't just like a simple array where I can go one by one - instead, it seems like some contents have more nodes inside of them, and so on?

Could I get some advice how to do this, thanks!

I currently have something like this, but replaces nothing

/*
    1. Deep search for all node.type === 'em'
    2. Within each spoiler node, find all nodes with type === 'text'
    3. Replace the text node content
    4. Return the modified AST
  */
  const traverse = (node) => {
    if (node.type === 'em') {
      const children = node.content;
      const newChildren = children.map(child => {
        if (child.type === 'text') {
          return {
            ...child,
            content: 'this is italics',
          };
        }
        return traverse(child);
      });
      return {
        ...node,
        content: newChildren,
      };
    }
    return node;
  };
  return myAST.map(traverse);

Example (Nesting can be deeper/varied/split across more nodes)

    [
      {
        "content": [
          {
            "content": [
              {
                "content": [
                  {
                    "content": "Italic Bold",
                    "type": "text"
                  }
                ],
                "type": "strong"
              }
            ],
            "type": "italics"
          }
        ],
        "type": "paragraph"
      }
    ]

    becomes

    [
      {
        "content": [
          {
            "content": [
              {
                "content": [
                  {
                    "content": "this is italics",
                    "type": "text"
                  }
                ],
                "type": "strong"
              }
            ],
            "type": "italics"
          }
        ],
        "type": "paragraph"
      }
    ]

CodePudding user response:

  if (node.content && Array.isArray(node.content)) {
    node.content = node.content.map(traverse)
  }

You need to recurse through all child nodes. This addition to the top of your function works on my test file.

CodePudding user response:

You are only recursing into em nodes, but not into other nodes.

It seems like what you want to do instead is

function replaceContent(type, content, inEm) {
  if (type === 'em' && Array.isArray(content)) return traverse(content, true);
  if (Array.isArray(content)) return traverse(content, inEm);
  if (type === 'text' && inEm) return 'this is text in italics';
  if (type === 'text') return content;
  throw new Error(`unexpected content in ${type}: ${JSON.stringify(content)}`);
  // or alternatively just always `return content` unchanged if not known
}

function traverse(content, inEm) {
  return content.map(node => {
    return {
      ...node,
      content: replaceContent(node.type, node.content, inEm),
    };
  });
}

return traverse(myAST, false);
  • Related