前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >complier-core 编译器内核

complier-core 编译器内核

作者头像
copy_left
发布2020-12-31 14:33:14
3970
发布2020-12-31 14:33:14
举报
文章被收录于专栏:方球方球

parse.ts 转换器

将字符模板转换为节点树

数据定义

代码语言:javascript
复制
// 主要方法以及配置


// 属性对象
type AttributeValue =
  | {
      content: string
      isQuoted: boolean
      loc: SourceLocation
    }
  | undefined


// 需要处理为字符的转义符
const decodeRE = /&(gt|lt|amp|apos|quot);/g
// 转义符映射
const decodeMap: Record<string, string> = {
  gt: '>',
  lt: '<',
  amp: '&',
  apos: "'",
  quot: '"'
}
// 默认上下文属性
export const defaultParserOptions: MergedParserOptions = {
  delimiters: [`{{`, `}}`], // 动态数据分界符
  getNamespace: () => Namespaces.HTML, // 
  getTextMode: () => TextModes.DATA, // 
  isVoidTag: NO, // 是否空标签
  isPreTag: NO, // 是否预处理标签
  isCustomElement: NO, // 是否自定义元素
  decodeEntities: (rawText: string): string =>
    rawText.replace(decodeRE, (_, p1) => decodeMap[p1]), // 转译
  onError: defaultOnError, // 抛出错误 默认:throw(error)
  comments: false // 是否为注释,(这里命名有点不同的?)
}
// 文本类型
export const enum TextModes {
  //          | Elements | Entities | End sign              | Inside of
  DATA, //    | ✔        | ✔        | End tags of ancestors |
  RCDATA, //  | ✘        | ✔        | End tag of the parent | <textarea>
  RAWTEXT, // | ✘        | ✘        | End tag of the parent | <style>,<script>
  CDATA,
  ATTRIBUTE_VALUE
}
// 编译上下文对象
export interface ParserContext {
  options: MergedParserOptions
  readonly originalSource: string // 只读模板
  source: string // 源模板
  offset: number // 偏移量
  line: number // 行号
  column: number // 列号
  inPre: boolean // HTML <pre> tag, preserve whitespaces
  inVPre: boolean // v-pre, do not process directives and interpolations
}
// 标签类型
const enum TagType {
  Start, // 起始标签
  End // 结束标签
}
// 指令映射查询, 判断字符是否为指令字段
const isSpecialTemplateDirective = /*#__PURE__*/ makeMap(
  `if,else,else-if,for,slot`
)
// => key => !!({ if:true, else:true, else-if:true, for:true, slot:true }[key]

方法

代码语言:javascript
复制
// 编译入口
baseParse(){}
// 创建编译上下文
createParserContext(){}
// 编译子标签
parseChildren(){}
// 加入节点队列,优化函数
pushNode(){}
// 生成CDATA节点: TextModes.CDATA
parseCDATA(){}
// 生成注释节点:NodeTypes.COMMENT
parseComment(){}
// 生成虚假注释节点:  NodeTypes.COMMENT
parseBogusComment(){} 
// 生成标签节点
parseTag(){}
// 生成属性列表
parseAttributes(){}
// 生成属性节点
parseAttribute(){}
// 提取属性值
parseAttributeValue(){}
// 生成动态数据节点 "{{ data }}"
parseInterpolation() {}
// 生成文本节点
parseText(){}
// 提取文本数据 "name}}" => { type: NodeType.TEXT, content: 'name', loc: SourceLocation }
parseTextData(){}
// 获取读头位置
getCursor(){}
// 生成选取对象
getSelection(){}
// 获取队列末尾项
last()[]
// 字符开头校验
startsWith(){}
// 读头前进
advanceBy(){}
// 跳过空行
advanceSpaces(){}
// 生成新位置信息对象
getNewPosition(){}
// 生成错误位置等信息,并抛出
emitError() {}
// 是否读取结束
isEnd(){}
// 是否以结束标签开头
startsWithEndTagOpen(){}

编译入口, 创建根节点

代码语言:javascript
复制
export function baseParse(
  content: string,
  options: ParserOptions = {}
): RootNode {
  // 创建上下文
  const context = createParserContext(content, options)
  // 获取标签位置信息
  const start = getCursor(context)
  // 创建根节点
  return createRoot(
    // 子节点编译
    parseChildren(context, TextModes.DATA, []),
    // 生成节点信息,{ start: 起始位置, end: 结束位置, source: 标签内容 }
    // 这个函数需要在 parseChildren 完成后调用,此时才能获取到 end 的位置
    getSelection(context, start)
  )
}


// 上下文对象
function createParserContext(
  content: string,
  rawOptions: ParserOptions
): ParserContext {
  const options = extend({}, defaultParserOptions)
  // 合并默认配置
  for (const key in rawOptions) {
    // @ts-ignore
    options[key] = rawOptions[key] || defaultParserOptions[key]
  }
  return {
    options,
    column: 1, // 列位置
    line: 1, // 行位置
    offset: 0, // 偏移量
    originalSource: content, 
    source: content,
    inPre: false, // 是否为html
    inVPre: false // 是否 v-pre
  }
} 


// 返回的节点结构
export function createRoot(
  children: TemplateChildNode[],
  loc = locStub
): RootNode {
  return {
    type: NodeTypes.ROOT, // 节点类型
    children, // 子节点队列
    helpers: [], // 
    components: [], // 组件队列
    directives: [], // 指令
    hoists: [], // 
    imports: [],  // 引入路径, 动态租金啊
    cached: 0, // 缓存
    temps: 0, // 模板
    codegenNode: undefined,
    loc // 字符原copy
  }

节点编译

代码语言:javascript
复制
function parseChildren(
  context: ParserContext,
  mode: TextModes,
  ancestors: ElementNode[]
): TemplateChildNode[] {
  
  // 父节点队列中最后一位,为当前节点父节点
  const parent = last(ancestors)
  // 节点类型
  const ns = parent ? parent.ns : Namespaces.HTML
  // 子节点收集器
  const nodes: TemplateChildNode[] = []


  // 是否遍历结束
  while (!isEnd(context, mode, ancestors)) {


    __TEST__ && assert(context.source.length > 0)
    const s = context.source
    let node: TemplateChildNode | TemplateChildNode[] | undefined = undefined


    //  TextModes.DATA / TextModes.RCDATA 模式
    if (mode === TextModes.DATA || mode === TextModes.RCDATA) {
      // 非 v-pre 节点, 以 '{{' 开头
      if (!context.inVPre && startsWith(s, context.options.delimiters[0])) {
        // '{{'
        node = parseInterpolation(context, mode)
        
        // 标签开头 '<'
      } else if (mode === TextModes.DATA && s[0] === '<') {
        // https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
        // 单 < 报错
        if (s.length === 1) {
          emitError(context, ErrorCodes.EOF_BEFORE_TAG_NAME, 1)
        } else if (s[1] === '!') {
          // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state
          if (startsWith(s, '<!--')) {
            // 生成注释节点
            node = parseComment(context)
          } else if (startsWith(s, '<!DOCTYPE')) {
            // 生成文档节点
            node = parseBogusComment(context)
          } else if (startsWith(s, '<![CDATA[')) {
            // xml 节点
            if (ns !== Namespaces.HTML) {
              node = parseCDATA(context, ancestors)
            } else {
              emitError(context, ErrorCodes.CDATA_IN_HTML_CONTENT)
              node = parseBogusComment(context)
            }
          } else {
            emitError(context, ErrorCodes.INCORRECTLY_OPENED_COMMENT)
            node = parseBogusComment(context)
          }
        } else if (s[1] === '/') {
          // https://html.spec.whatwg.org/multipage/parsing.html#end-tag-open-state
          if (s.length === 2) {
            // </ 错误标签
            emitError(context, ErrorCodes.EOF_BEFORE_TAG_NAME, 2)
          } else if (s[2] === '>') {
            // </> 丢失标签名
            emitError(context, ErrorCodes.MISSING_END_TAG_NAME, 2)
            advanceBy(context, 3)
            continue
          } else if (/[a-z]/i.test(s[2])) {
            // </tag> 错误的自闭合标签
            emitError(context, ErrorCodes.X_INVALID_END_TAG)
            parseTag(context, TagType.End, parent)
            continue
          } else {
            emitError(
              context,
              ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME,
              2
            )
            node = parseBogusComment(context)
          }
        } else if (/[a-z]/i.test(s[1])) {
          // 正确标签解析
          node = parseElement(context, ancestors)
        } else if (s[1] === '?') {
          // <? 问号标签
          emitError(
            context,
            ErrorCodes.UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME,
            1
          )
          node = parseBogusComment(context)
        } else {
          // 其他错误标签
          emitError(context, ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME, 1)
        }
      }
    }


    // 非节点类型,生成文本节点
    if (!node) {
      node = parseText(context, mode)
    }
      
    // 将子节点加入队列
    if (isArray(node)) {
      for (let i = 0; i < node.length; i++) {
        pushNode(nodes, node[i])
      }
    } else {
      pushNode(nodes, node)
    }
  }


  // Whitespace management for more efficient output
  // (same as v2 whitespace: 'condense')
  let removedWhitespace = false


  // 非行文字节点, 替换或去除空格节点
  if (mode !== TextModes.RAWTEXT) {
     ....
  }


  return removedWhitespace ? nodes.filter(Boolean) : n
代码语言:javascript
复制
// parseChildren 伪代码
while( !isEnd() // 是否遍历结束 ){
  // TextModes.DATA | TextModes.RCDATA
  // 如果为 DATA 或 RCDATA 类型,做标签解析
  if (mode === TextModes.DATA || mode === TextModes.RCDATA) {
    // 如果 非 v-pre '{{' 开头, 认为是动态插入值, 例如: {{ data }}
    if (!context.inVPre && startsWith(s, context.options.delimiters[0])) {
       node = parseInterpolation(context, mode)  
    }else if (mode === TextModes.DATA && s[0] === '<'){
      // DATA 类型 以 '<' 开头的标签
      // 以下为各种标签情况的判断
      // 1: '<' 报错: EOF_BEFORE_TAG_NAME
      // 2: '<!' 可能为文档标签类型, 向下查询判断
          // 2-1: '<!--'  注释节点
          // 2-2: '<!DOCTYPE'  html 文档
          // 2-3: '<![CDATA[' xml 文档
          // 2-4: 其他无法识别的节点,  将剔除问题内容,生成虚假的注释节点, parseBogusComment
      // 3: '</' 错误开始标签处理
          // 3-1: '</' 报错提示 EOF_BEFORE_TAG_NAME
          // 3-2: '</>' 提出问题标签, 报错提示: EOF_BEFORE_TAG_NAME
          // 3-3: '</tagName>'  报错提示: X_INVALID_END_TAG 尝试作为闭合标签解析 -> parseTag(context, TagType.End, parent)
      // 4: '<?' 报错提示: INVALID_FIRST_CHARACTER_OF_TAG_NAME    
    }
    // 如果以上未生成节点,将作为文本节点处理
    if (!node) {
      node = parseText(context, mode)
    }
    // 节点加入队列
    if (isArray(node)) {
      for (let i = 0; i < node.length; i++) {
        pushNode(nodes, node[i])
      }
    } else {
      // 这里对文本节点做了优化,如果上一节点也为文本节点,NodeTypes.TEXT,将合并两个节点
      pushNode(nodes, node)
    }
    // 空格节点的处理
    let removedWhitespace = false
    if (mode !== TextModes.RAWTEXT) { ... }
    // 返回节点树
    return removedWhitespace ? nodes.filter(Boolean) : nodes
  }
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • parse.ts 转换器
    • 数据定义
      • 方法
        • 编译入口, 创建根节点
          • 节点编译
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档