/* * md ast - pluggable markdown parser */ const syntax = { bold: { paired: true, recursive: true, startRegexp: /\*\*\S.*/, endRegexp: /\*\*\W/, content: { start: { point: 'start', offset: 2 }, end: { point: 'start', offset: 0 } }, begin: 0, forward: { point: 'endEnd', //start, startEnd, end, endEnd offset: -1 } }, bold2: { paired: true, recursive: true, startRegexp: /\s__\S.*/, endRegexp: /__\W/, content: { start: { point: 'start', offset: 3 }, end: { point: 'start', offset: 0 } }, begin: 1, forward: { point: 'endEnd', //start, startEnd, end, endEnd offset: -1 } }, italic: { paired: true, recursive: true, startRegexp: /\*\S.*/, endRegexp: /\S\*[^*]/, content: { start: { point: 'start', offset: 1 }, end: { point: 'start', offset: 1 } }, begin: 0, forward: { point: 'endEnd', //start, startEnd, end, endEnd offset: -1 } }, italic2: { paired: true, recursive: true, startRegexp: /\s_\S.*/, endRegexp: /\S_\W/, content: { start: { point: 'start', offset: 2 }, end: { point: 'start', offset: 1 } }, begin: 1, forward: { point: 'endEnd', //start, startEnd, end, endEnd offset: -1 } }, root: { paired: true, recursive: true, startRegexp: /^/, endRegexp: /$/, content: { start: { point: 'start', offset: 1 }, end: { point: 'end', offset: 0 } }, begin: 0, forward: { point: 'endEnd', //start, startEnd, end, endEnd offset: -1 } }, heading1: { paired: true, recursive: true, startRegexp: /\n#[ \t]*(.*)\n/, endRegexp: /\n#/, content: { start: { point: 'end', offset: 0 }, end: { point: 'start', offset: 0 } }, begin: 1, forward: { point: 'end', //start, startEnd, end, endEnd offset: 0 }, title: { //index: 1, recursive: true, }, onbuild(md, mdTags, buildAST){ //this = {tag: } } }, code: { paired: true, recursive: false, startRegexp: /`/, endRegexp: /`/, content: { start: { point: 'start', offset: 1 }, end: { point: 'start', offset: 0 } }, begin: 0, forward: { point: 'end', //start, startEnd, end, endEnd offset: 1 } }, codeMultiLine: { paired: true, recursive: false, startRegexp: /\n```\s*\n/, endRegexp: /\n```\s*\n/, content:{ start:{ point: 'end', offset: 0 }, end:{ point: 'start', offset: 0 } }, begin: 1, forward: { point: 'endEnd', offset: 0 }, }, codeLanguage: { paired: true, recursive: false, startRegexp: /\n```(\w+)\s*\n/, endRegexp: /\n```\s*\n/, title: { recursive: false }, content:{ start:{ point: 'end', offset: 0 }, end:{ point: 'start', offset: 0 } }, begin: 1, forward: { point: 'endEnd', offset: 0 }, }, unOrderedList: { indent: true, childName: 'unOrderedListItem', //paired: true, recursive: true, regexp: /\n(\s*)-\s*/, content:{ start:{ point: 'end', offset: 0 }, end:{ point: 'start', offset: 0 } }, begin: 1, forward: { point: 'end', offset: -1 } } } function findNearest(md, mdTags, offset=0){ let nearest, nearestMatch = {index: Infinity}; for (let [mdTag, {paired, startRegexp, regexp}] of Object.entries(mdTags)) { if (mdTag === 'root') continue; let match = md.offsetMatch(offset, startRegexp || regexp) if (match && match.index < nearestMatch.index){ nearestMatch = match nearest = mdTag } } return [nearest, nearestMatch] } //node: //{ // tag: 'keyFromSyntax', // children: [String, Node] // parent: node //} // String.prototype.offsetMatch = function(offset, ...params){ return this.slice(offset).match(...params) } Array.prototype.last = function(){ return this[this.length -1] } function buildAST(md, mdTags=syntax, offset=0, tree={tag: 'root'}, stack=[]){ const currentNode = stack.last() || tree if (currentNode.tag === 'root') md = '\n' + md + '\n' currentNode.children = currentNode.children || [] const { children } = currentNode const {indent, title, recursive, endRegexp, content: {end: {offset: offsetEnd, point} }, forward } = mdTags[currentNode.tag] if (indent){ debugger; } if (title){ const {index=1, recursive} = title const {[index]: titleContent } = currentNode.startMatch if (titleContent && recursive){ currentNode.title = buildAST(titleContent, mdTags).children currentNode.title.forEach(item => item.parent = currentNode) } else { currentNode.title = [titleContent] } } while(offset < md.length){ const [nearest, nearestMatch] = findNearest(md, mdTags, offset) let endMatch = md.offsetMatch(offset, endRegexp) if (!recursive || endMatch) { if (!recursive || !nearest || endMatch.index <= nearestMatch.index ){ endMatch = endMatch || {index: md.length - offset, 0: "zzz"} currentNode.endContent = offset + endMatch.index + offsetEnd + (point === 'end' ? endMatch[0].length : 0) children.push(md.slice(offset, currentNode.endContent)) offset += endMatch.index + forward.offset + (forward.point === 'endEnd' ? endMatch[0].length : 0) console.log(currentNode.tag, forward.point === 'endEnd' ? endMatch[0].length : 0) currentNode.endOffset = offset currentNode.endMatch = endMatch return currentNode } } if (nearest){ const {begin,content: {start}} = mdTags[nearest] if (nearestMatch.index){ children.push(md.slice(offset, offset + nearestMatch.index + begin)) offset += nearestMatch.index } else { const newNode = {tag: nearest, startOffset: offset, parent: currentNode, startMatch: nearestMatch} children.push(newNode) buildAST(md, mdTags, offset + start.offset + (start.point === 'end' ? nearestMatch[0].length : 0), tree, [...stack, newNode]) offset = newNode.endOffset } } else { children.push(md.slice(offset)) offset = md.length } } return currentNode } //const md = //` //# heading1 //какой-то _текст_ //# heading2 //а тут **шо** цикавого?))) //`; //console.log( buildAST(md).children)