🎨
Notes
  • 持续更新中...
  • articles
    • browser
      • 深入理解现代浏览器 - 导航
      • 深入理解现代浏览器 - 架构
      • 深入理解现代浏览器 - 交互
      • 深入理解现代浏览器 - 渲染器进程
    • dsa
      • DSA - 并查集
      • DSA - 哈希表
      • DSA - AVL 树
      • DSA - 二叉树
      • 快速选择
      • Big O 算法复杂度
      • DSA - 栈和队列
      • DSA - 前缀树 Trie
      • DSA - 图
      • DSA - 链表
      • DSA - 递归
    • typescript
      • TypeScript 学习笔记 - 任意属性 (Indexable Types)
      • 力扣的 TypeScript 面试题
      • TypeScript 学习笔记 - as const
      • TypeScript 学习笔记 - infer
    • network
      • Internet Protocol (IP)
      • 计算机网络基础
      • 如何分辨同源和同站
      • DNS 如何查询 IP 地址?
    • vue
      • Nuxt.js 入门
      • 从零实现一个 Mini Vue
      • 从零实现一个简单的 VDOM 引擎
      • 从零实现一个响应式状态管理
    • sorting
      • 排序 - 归并排序
      • 排序 - 冒泡排序
      • 排序 - 选择排序
      • 排序 - 计数排序
      • 排序 - 插入排序
    • compile
      • Compiler and Interpreter
      • Just-In-Time (JIT) Compilers
      • 编译流程
    • others
      • 什么是上下文无关语法
      • 如何在终端打印出有颜色的字
    • dev-ops
      • github-actions
        • GitHub Action 简介
        • GitHub Actions for CI
    • workflow
      • 用 Node 写一个 cli
      • 如何规范 git commit 信息
      • 如何监听 git hooks
      • 如何规范代码风格 - prettier
      • 如何发布一个 npm package
      • 如何规范代码质量 - eslint
    • design-pattern
      • 代理模式
      • 单例模式
      • 策略模式
    • security
      • 点击劫持
      • CSP 内容安全策略
    • javascript
      • 尾调用优化
      • 4种常见的内存泄漏及解决方法
    • unit-test
      • Test Vuejs Application - Chapter 2
      • Test Vuejs Application - Chapter 1
      • Vue Unit Test Intro
    • performance
      • HTTP 缓存
      • 如何优化图片资源
Powered by GitBook
On this page
  • CFG 的组成
  • 来一个英语栗子
  • 定义语法规则
  • 根据语法规则创造句子
  • JavaScript 栗子

Was this helpful?

  1. articles
  2. others

什么是上下文无关语法

从名字可以看出,既然上下文无关语法(Context Free Grammar, 以下简称 CFG)被称为“语法”,说明它就是一套规则,是用来规定语句是如何生成的。

就像英语语法一样,比如英语语法规定了一个英语句子的构成方式可以是“名词+动词+名词”这种形式,那么根据英语语法:

  • I drink milk 是一个符合语法的句子

  • Tom drinks cat 看起来也是一个符合语法的句子

但其实后者并不是正确的英语句子,为什么呢?因为英语语法并不是 CFG,英语是依赖上下文的,一个句子是否正确不仅要看它的语法构成是否正确,还要从上下文来看它是否能被正确理解,Tom drinks cat 很明显不是一个可以被正常理解的句子,所以,虽然它符合“动词+动词+名词”结构,但它并不是一个正确的英语句子。

而 CFG 不同于英语语法,它完全不考虑上下文,只要一个句子符合语法规则,那它就是一个有效的句子。假如在一个平行世界中,英语语法是一种 CFG 的话,那 Tom drinks cat 也是一个正确的句子了。

虽然 CFG 不能用来描述自然语言(因为自然语言一般都需要考虑上下文),但我们还是可以用 CFG 来描述自然语言的语法和句子结构的,或许,我们可以把英语语法想象成 CFG + 上下文,在不考虑上下文的情况下(也就是不管语句是否意思通顺,只要语句结构符合语法规定它就是正确的),我们完全可以使用 CFG 来描述英语语法。

CFG 的组成

首先来熟悉一下几个相关词汇:

  • Terminals(终结符): 是构成最终语句的成分,比如在英语中,句子由单词组成,所以终结符就是一个个单词。

  • Non Terminals(非终结符):也被称为变量,可以认为非终结符是终结符或其他非终结符的占位符。

  • Start Symbol(开始变量):也是一个非终结符,由于是语法规则的开始,所以也被称为开始变量。

  • Production Rules(规则/产生式):语法规则,用来描述一个句子是怎样产生的。

来一个英语栗子

定义语法规则

让我们试着用 CFG 来定义一个英语句子的语法:

Sentence --> NounPhrase VerbPhrase

上式表示:我们定义了一个规则来规定一个 Sentence 应该长什么样,根据这个规则,要生成 Sentence,我们需要把 NounPhrase 和 VerbPhrase 合在一起。

解析:Sentence --> NounPhrase VerbPhrase 是一个产生式,其中 Sentence, NounPhrase, VerbPhrase 是变量,它们都是 CFG 中的非终结符,其中 Sentence 是开始变量。

上面的产生式描述了一个句子的结构,但根据这个语法还不能生成句子,因为我们只知道 Sentence 由 NounPhrase 和 VerbPhrase 组成,那 NounPhrase 和 VerbPhrase 又是什么呢?我们需要继续定义它们。

NounPhrase --> Adj Noun
NounPhrase --> Adj NounPhrase

上面两个产生式定义了生成 NounPhrase 的两种方法,Adj + Noun,或者 Adj + NounPhrase,这两条规则还可以合写成一条:

NounPhrase --> Adj Noun | Adj NounPhrase。

而 VerbPhrase 的生成规则可以定义为:

VerbPhrase --> Verb NounPhrase

注意:到这一步我们得到的仍然都是非终结符,上述产生式中的 Adj, Noun, Verb 也都是变量,我们还需要继续定义它们。

Adj  --> pink | Pretty
Noun --> girls | skirts
Verb --> like

终于,出现了组成句子的真正成分:单词。因为单词可以用来组成句子,所以不需要继续定义,语法定义的工作到这里就结束了。在 CFG 中,这些不需要再展开定义的成分被称为终结符(Terminals),终结符就是构成最终句子的成分。

现在我们完整的语法规则是这样的:

Sentence --> NounPhrase VerbPhrase
NounPhrase --> Adj Noun | Adj NounPhrase
VerbPhrase --> Verb NounPhrase
Adj  --> pink | Pretty
Noun --> girls | skirts
Verb --> like

根据语法规则创造句子

现在让我们根据这些语法来生成一个句子:

  1. 要生成 Sentence,需要 NounPhrase + VerbPhrase

  2. 要生成 NounPhrase,需要 Adj + Noun 或者 Adj + NounPhrase,这里我们选择 Adj + Noun

  3. 要生成 Adj,我们可以使用 pink 或者 Pretty 这个单词,这里我们选择 Pretty

  4. 要生成 Noun,我们可以使用 girls 或者 skirt,这里我们选择 girls

  5. 要生成 VerbPhrase,需要 Verb + NounPhrase

  6. 要生成 Verb,我们可以使用 like 这个单词

  7. 要生成 NounPhrase,需要 Adj + Noun 或者 Adj + NounPhrase,这里我们选择 Adj + Noun

  8. 要生成 Adj,我们可以使用 pink 或者 Pretty 这个单词,这里我们选择 pink

  9. 要生成 Noun,我们可以使用 girls 或者 skirt,这里我们选择 skirts

P.S. 上述步骤中所有选择都是随机的。

这些步骤用句子描述太繁琐了,我们还是来看一张图吧,基本上,这就是一个递归操作,用树结构来表示,就清晰多了。

把树的所有叶子节点,也就是所有终结符连起来,我们就生成了一个句子 Pretty girls like pink skirts (BTW, it seems to rhyme!)。这个树一般叫做解析树(Parse Tree),我们还可以创建另一棵树来生成另一个句子,步骤是一样的,只是其中的选择不同,不同的选择会生成不同的结果,比如 Pretty skirts like pink girls。

JavaScript 栗子

英语语法并不是真正的 CFG,因为英语必须依赖上下文,CFG 一般用来描述编程语言,比如 JS,实际上,ECMAScript 规范就是使用 CFG 来定义 JavaScript 这门语言的。

我们来看一个简单的 JS 语法生成式,下面是 ECMAScript 规范对 NumericLiteral 的定义,如果我们要生成一个 NumericLiteral,我们就得遵循这个语法规则。

NumericLiteral::
DecimalLiteral
DecimalBigIntegerLiteral
NonDecimalIntegerLiteral
NonDecimalIntegerLiteral BigIntLiteralSuffix

转换成上文用到的语法的话,规则写成这样:

NumericLiteral --> DecimalLiteral | DecimalBigIntegerLiteral | NonDecimalIntegerLiteral | NonDecimalIntegerLiteral BigIntLiteralSuffix

ECMAScript 规范用到的语法稍有不同,不过本质上是一样的。

首先规范定义了 NumericLiteral 可以有 4 种生成方式。我们可以选择一种,这里就选第一种吧,然后我们发现了 DecimalLiteral 也是一个非终结符,我们得接着找对它的定义。

DecimalLiteral::
DecimalIntegerLiteral . DecimalDigits(opt) ExponentPart(opt)
. DecimalDigits ExponentPart(opt)
DecimalIntegerLiteral ExponentPart(opt)

规范中定义了 NumericLiteral 可以有 3 种生成方式,我们选择一种 DecimalIntegerLiteral . DecimalDigits(opt) ExponentPart(opt),因为后面的部分都是可选的(opt),我们就简单点,只考虑第一部分 DecimalIntegerLiteral 吧,接着去找 DecimalIntegerLiteral 的定义:

DecimalIntegerLiteral::
0
NonZeroDigit DecimalDigits(opt)

规范中定义了 DecimalIntegerLiteral 可以有 2 种生成方式,如果我们选第一种 0,因为 0 是一个终结符,我们的生成过程就到此结束了,我们生成了一个 NumericLiteral,就是 0,用树结构来表示生成过程的话就是下图。

PreviousothersNext如何在终端打印出有颜色的字

Last updated 4 years ago

Was this helpful?