11 个版本
0.6.4 | 2023 年 10 月 9 日 |
---|---|
0.6.2 | 2023 年 9 月 29 日 |
0.6.0 | 2023 年 5 月 25 日 |
0.5.1 | 2021 年 10 月 20 日 |
0.1.0 | 2020 年 3 月 3 日 |
#28 in 文本编辑器
用于 sbnfc
300KB
7.5K SLoC
SBNF
用于编写 sublime-syntax 文件的 BNF 风格语言。
现在就在 实时游乐场 尝试一下!
SBNF 目前用于 SWI-Prolog。
动机与目标
编写语法定义容易出错,且结果难以维护。虽然 branch_point
是一个很棒的功能,但在使用时它会大大增加复杂性和重复性。
SBNF 试图做以下事情
- 提供一种可维护的、声明性的语言来编写 sublime 语法定义
- 快速编译,以便快速迭代
- 编译为高效的语法,与手工制作的语法相当
安装
安装 rust 后,您可以使用以下命令下载、构建和安装 SBNF 的最新发布版本
$ cargo install sbnfc
或者如果您想使用最新功能,克隆此仓库,然后使用以下命令构建和安装
$ cargo install --path cli
请注意,为了使用生成的语法,您至少需要 Sublime Text build 4077,并支持 Sublime Syntax 的第 2 版。
Sublime 语法
SBNF 的语法定义位于 sbnf/sbnf.sbnf
。要编译它,只需运行 sbnf sbnf/sbnf.sbnf
,然后可以将 sbnf/
目录链接或复制到您的用户包。
示例
以下是一个简化版C语言的SBNF语法。它只允许全局/局部变量声明、函数定义和简单的函数调用。即使是这个简化版,使用所需的meta.function
和meta.function-call
作用域进行正确解析也非常困难,因为函数定义和函数调用都需要分支点。
NAME = `simplec`
prototype : ( ~comment )* ;
comment : '(//+).*\n?'{comment.line, 1: punctuation.definition.comment} ;
main : ( variable-declaration | function-definition )* ;
IDENTIFIER = '\b[A-Za-z_]+\b'
function-definition{meta.function}
: type
IDENTIFIER{entity.name.function}
`(`
`)`
block
;
block{meta.block} : '{' statement* '}' ;
statement : variable-declaration
| value ';'
| block
;
variable-declaration : type IDENTIFIER{variable} ( '=' value )? ';' ;
type : IDENTIFIER{storage.type} ;
value : '[0-9]+'{constant.numeric}
| function-call
;
# Function calls don't have arguments :)
function-call{meta.function-call}
: IDENTIFIER{variable.function meta.path} `(` `)` ;
上述语法编译成以下
%YAML 1.2
---
# https://text.sublime.net.cn/docs/syntax.html
version: 2
name: simplec
scope: source.simplec
contexts:
# Rule: block
block|0:
- meta_content_scope: meta.block.simplec
- match: '{'
scope: meta.block.simplec
set: block|1
- match: '\S'
scope: invalid.illegal.simplec
pop: true
# Rule: block
block|1:
- meta_content_scope: meta.block.simplec
- include: include!block@1
- match: '[0-9]+'
scope: meta.block.simplec constant.numeric.simplec
push: [block|meta, statement|0]
- match: '{'
scope: meta.block.simplec meta.block.simplec
push: [block|meta, block|1]
- match: '}'
scope: meta.block.simplec
pop: true
- match: '\S'
scope: invalid.illegal.simplec
pop: true
# Rule: block
# For branch point 'block@1'
block|2|block@1:
- match: '\b[A-Za-z_]+\b'
scope: meta.block.simplec variable.simplec
set: [block|meta, variable-declaration|2]
- match: '\S'
fail: block@1
# Rule: block
# For branch point 'block@1'
block|3|block@1:
- match: '\('
scope: meta.block.simplec meta.function-call.simplec
set: [block|meta, statement|0, function-call|1]
- match: '\S'
scope: invalid.illegal.simplec
pop: true
# Meta scope context for block
block|meta:
- meta_content_scope: meta.block.simplec
- match: ''
pop: true
# Rule: function-call
function-call|0:
- meta_content_scope: meta.function-call.simplec
- match: '\('
scope: meta.function-call.simplec
set: function-call|1
- match: '\S'
scope: invalid.illegal.simplec
pop: true
# Rule: function-call
function-call|1:
- meta_content_scope: meta.function-call.simplec
- match: '\)'
scope: meta.function-call.simplec
pop: true
- match: '\S'
scope: invalid.illegal.simplec
pop: true
function-call|2|block@1:
- meta_include_prototype: false
- match: '\b[A-Za-z_]+\b'
scope: meta.function-call.simplec variable.function.simplec meta.path.simplec
push: block|3|block@1
pop: true
# Rule: function-definition
function-definition|0:
- meta_content_scope: meta.function.simplec
- match: '\)'
scope: meta.function.simplec
set: [function-definition|meta, block|0]
- match: '\S'
scope: invalid.illegal.simplec
pop: true
# Meta scope context for function-definition
function-definition|meta:
- meta_content_scope: meta.function.simplec
- match: ''
pop: true
# Include context for branch point block@1
include!block@1:
- match: '(?=\b[A-Za-z_]+\b)'
branch_point: block@1
branch:
- type|2|block@1
- function-call|2|block@1
# Include context for branch point main@1
include!main@1:
- match: '(?=\b[A-Za-z_]+\b)'
branch_point: main@1
branch:
- type|0|main@1
- type|1|main@1
# Rule: main
main:
- include: include!main@1
- match: '\S'
scope: invalid.illegal.simplec
# Rule: main
# For branch point 'main@1'
main|0|main@1:
- match: '\b[A-Za-z_]+\b'
scope: variable.simplec
push: main|2|main@1
pop: true
- match: '\S'
fail: main@1
# Rule: main
# For branch point 'main@1'
main|1|main@1:
- match: '\b[A-Za-z_]+\b'
scope: meta.function.simplec entity.name.function.simplec
push: main|3|main@1
pop: true
- match: '\S'
scope: invalid.illegal.simplec
pop: true
# Rule: main
# For branch point 'main@1'
main|2|main@1:
- match: '='
set: variable-declaration|0
- match: ';'
pop: true
- match: '\S'
fail: main@1
# Rule: main
# For branch point 'main@1'
main|3|main@1:
- match: '\('
scope: meta.function.simplec
set: function-definition|0
- match: '\S'
scope: invalid.illegal.simplec
pop: true
# Rule: prototype
prototype:
- match: '(//+).*\n?'
scope: comment.line.simplec
captures:
1: punctuation.definition.comment.simplec
# Rule: statement
statement|0:
- match: ';'
pop: true
- match: '\S'
scope: invalid.illegal.simplec
pop: true
type|0|main@1:
- meta_include_prototype: false
- match: '\b[A-Za-z_]+\b'
scope: storage.type.simplec
push: main|0|main@1
pop: true
type|1|main@1:
- meta_include_prototype: false
- match: '\b[A-Za-z_]+\b'
scope: meta.function.simplec storage.type.simplec
push: main|1|main@1
pop: true
type|2|block@1:
- meta_include_prototype: false
- match: '\b[A-Za-z_]+\b'
scope: storage.type.simplec
push: block|2|block@1
pop: true
# Rule: variable-declaration
variable-declaration|0:
- match: '[0-9]+'
scope: constant.numeric.simplec
set: variable-declaration|1
- match: '\b[A-Za-z_]+\b'
scope: meta.function-call.simplec variable.function.simplec meta.path.simplec
set: [variable-declaration|1, function-call|0]
- match: '\S'
scope: invalid.illegal.simplec
pop: true
# Rule: variable-declaration
variable-declaration|1:
- match: ';'
pop: true
- match: '\S'
scope: invalid.illegal.simplec
pop: true
# Rule: variable-declaration
variable-declaration|2:
- match: '='
set: variable-declaration|0
- match: ';'
pop: true
- match: '\S'
scope: invalid.illegal.simplec
pop: true
用法
SBNF文件包含两种类型的元素:子句和规则。子句提供语法元数据,如文件扩展名,以及一些元编程。规则是定义语法解析和作用域的BNF风格的规则。
SBNF中的注释从#
开始,并在下一行结束。
有关完整语法示例,请参阅sbnf.sbnf
。
子句
子句的形式为<name> <parameters> = <value>
。名称必须遵循大写蛇形命名法。以下名称为元数据保留:
NAME
:语法的名称。默认为SBNF文件的基名。EXTENSIONS
:文件扩展名的空格分隔列表。与sublime-syntax中的file_extensions
等效。FIRST_LINE
:匹配文件第一行的正则表达式。与sublime-syntax中的first_line_match
等效。SCOPE
:语法的默认作用域。默认为source.
后跟语法的名称的小写形式。SCOPE_POSTFIX
:附加到语法中所有作用域的后缀(不包括SCOPE
子句)。默认为名称的小写形式。可以留空以省略后缀。HIDDEN
:语法是否将在Sublime Text的菜单中显示。
示例
NAME = `SBNF`
EXTENSIONS = `sbnf`
# Don't need this, as this is already the default
# SCOPE = `source.sbnf`
规则
规则的形式为<name> <parameters> <options> : <expression> ;
。名称必须遵循短横线命名法。
与sublime-syntax文件一样,SBNF语法有两个入口点:main
和prototype
。它们的行为与sublime-syntax文件中的相同。只有从入口点直接或间接使用的规则会被编译。
规则可以可选地具有参数和选项。参数用于元编程,选项用于Sublime Text特定的选项。
示例
a : 'a' ;
b{source.b} : 'b' ;
c[S] : 'c'{#[S]} ;
d[S]{text.d} : a b c[S] ;
表达式
表达式可以采取以下任何形式
`<literal>` <options>
:匹配文本字面量的终止符。'<regex>' <options>
:根据正则表达式匹配文本的终止符。<identifier> <arguments>
:与非终结符号匹配的规则。<expr| <expr>
:表达式的选择。语法匹配左表达式或右表达式。可以用作列表,例如:'a' | 'b' | 'c'
。<expr> <expr>
:表达式的连接。语法匹配左表达式后跟右表达式。可以用作列表,例如:'a' 'b' 'c'
。(<expr>)
:分组。<expr>?
:可选表达式。语法匹配无内容或表达式。<expr>*
:重复表达式。语法匹配表达式任意次数,包括0次。~<expr>
:被动表达式。语法匹配直到表达式匹配的任意文本。
选项
选项的形式如下:{<param>, <key>: <value>}
。代码,
,:
或}
除外,<param>
,<key>
或<value>
可以包含任意文本。可以提供任意数量的选项,具体取决于选项的内容。如果没有选项,则大括号{}
是可选的。
规则允许以下选项
<meta-scope>
:规则的元范围。相当于sublime-syntax中的meta_scope
或meta_content_scope
。
文本和正则表达式终结符允许以下参数
<scope>
:终结符的范围。<capture>: <scope>
:正则表达式捕获组的范围。<capture>
必须是一个整数。
参数
规则和子句的参数形式为:[<value>, <value>]
。 <value>
可以是正则表达式终结符、字面终结符或标识符。相同的名称可用于具有不同参数集的规则/子句。
当使用时,带有参数的规则被实例化。匹配基于每个参数的类型和值。终结符参数基于正则表达式等价性进行匹配,而规则参数基于名称进行匹配。
不引用规则的标识符是规则作用域中唯一的自由变量。它可以匹配任何参数,并且可以被传递或插入。
可以使用以下语法插入变量:#[]
。这可以在任何终结符内部或选项内部完成。
示例
main
: a['a'] # instantiates rule 1
| a[a] # instantiates rule 2
| a['b'] # instantiates rule 3
| b['b'] # error: Ambiguous instantiation
;
# Rule 1.
a['a'] : 'a' ;
# Rule 2.
a[a] : 'a' ;
# Rule 3.
a[A] : 'a' ;
b[A] : 'a' ;
b[B] : 'b' ;
还存在一组全局参数,这些参数是从命令行传递的。这些参数的形式与其他参数相同,应放在文件顶部。它们可能仅由变量组成,并且在全局范围内可用,包括子句。
示例
# Declares a single global parameter
[TYPE]
# Can be used in clauses
NAME = 'd-#[TYPE]'
# As well as rules
main : '#[TYPE]' ;
# 'dmd' is passed to TYPE when compiled
$ sbnf syntax.sbnf dmd
包含/嵌入
SBNF还支持包含/嵌入其他Sublime语法。这只能在带有后缀的文本书面或正则表达式终结符表达式中完成:%include[<with_prototype>]{<syntax>}
用于包含语法或%embed[<regex>]{<syntax>}
用于嵌入。
请注意,这些直接转换为Sublime语法包含/嵌入功能,因此具有相同的限制。
示例
# This is a basic implementation of the html script tag embedding the javascript
# syntax.
script
: '<script>'{tag.begin.script}
%embed['</script>']{scope:source.js, embedded.js, 0: tag.end.script}
;
# The above translates to the following context
script:
- match: '<script>'
scope: tag.begin.script.example
embed: scope:source.js
embed_scope: embedded.js.example
escape: '</script>'
escape_captures:
0: tag.end.script.example
pop: true
- match: '\S'
scope: invalid.illegal.example
# This is a basic implementation of a regex string. It has a prototype rule that
# extends the regex syntax with an escape sequence for the string.
regex-prototype{include-prototype: false}
: ( ~`\'`{constant.character.escape} )*
# A lookahead is required here, as otherwise we would only pop one context
# The same is required in a sublime-syntax file
~'(?=\')'
;
regex-string{string.quoted}
: `'`{punctuation.definition.string.begin}
%include[regex-prototype]{scope:source.regexp}
`'`{punctuation.definition.string.end}
;
# The above translates to the following contexts
regex-string:
- meta_content_scope: string.quoted.example
- match: ''''
scope: string.quoted.example punctuation.definition.string.begin.example
set: [regex-string|0, regex-string|1]
- match: '\S'
scope: invalid.illegal.example
regex-string|0:
- meta_content_scope: string.quoted.example
- match: ''''
scope: string.quoted.example punctuation.definition.string.end.example
pop: true
- match: '\S'
scope: invalid.illegal.example
pop: true
regex-string|1:
- meta_include_prototype: false
- match: ''
set: scope:source.regexp
with_prototype:
- include: regex-prototype|0
regex-prototype|0:
- meta_include_prototype: false
- match: '\\'''
scope: constant.character.escape.example
- match: '(?='')'
pop: true
命令行
$ sbnf --help
SBNF compiler 0.4.0
USAGE:
sbnf [FLAGS] [OPTIONS] <INPUT> [ARGS]...
FLAGS:
-g Compile with debug scopes
-h, --help Prints help information
-q Do not display warnings
-V, --version Prints version information
OPTIONS:
-o <output> The file to write the compiled sublime-syntax to. Defaults to $INPUT.sublime-syntax if left out. Use a single dash `-` to write to stdout instead.
ARGS:
<INPUT> The SBNF file to compile
<ARGS>... Arguments to pass to the main and prototype rules
限制
正则表达式等价性
在确定是否在sublime-syntax中创建分支点时,SBNF必须考虑正则表达式是否重叠。以下是一个示例
main : 'aa?'{scope1} 'b'
| 'a'{scope2} 'c'
;
正则表达式'aa?'
和'a'
都匹配a
,这意味着需要分支点来正确解析此语法。SBNF 不会在这里创建分支点。由于正则表达式的复杂性,分支点仅在等效正则表达式上创建。将示例重写为与SBNF一起使用,得到以下内容
main : 'aa'{scope1} 'b'
| 'a'{scope1} 'b'
| 'a'{scope2} 'c'
;
将来不太可能改变,因为SBNF不试图理解任何正则表达式。
待办事项
- 修复编译器中的已知边缘情况。在几个地方,我们使用panic!()而不是提供实现。
- 当在非弹出循环中使用分支时添加警告。
- 修复规则引用自身时的无限循环/递归
依赖关系
~2.5MB
~38K SLoC