SBNF
用于编写 sublime-syntax 文件的一种 BNF 语法的语言
详细资料
安装次数
- 总数 36
- Win 15
- Mac 11
- Linux 10
8 月 6 日 | 8 月 5 日 | 8 月 4 日 | 8 月 3 日 | 8 月 2 日 | 8 月 1 日 | 7 月 31 日 | 7 月 30 日 | 7 月 29 日 | 7 月 28 日 | 7 月 27 日 | 7 月 26 日 | 7 月 25 日 | 7 月 24 日 | 7 月 23 日 | 7 月 22 日 | 7 月 21 日 | 7 月 20 日 | 7 月 19 日 | 7 月 18 日 | 7 月 17 日 | 7 月 16 日 | 7 月 15 日 | 7 月 14 日 | 7 月 13 日 | 7 月 12 日 | 7 月 11 日 | 7 月 10 日 | 7 月 9 日 | 7 月 8 日 | 7 月 7 日 | 7 月 6 日 | 7 月 5 日 | 7 月 4 日 | 7 月 3 日 | 7 月 2 日 | 7 月 1 日 | 6 月 30 日 | 6 月 29 日 | 6 月 28 日 | 6 月 27 日 | 6 月 26 日 | 6 月 25 日 | 6 月 24 日 | 6 月 23 日 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Windows | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
Mac | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Linux | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
README
SBNF
用于编写 sublime-syntax 文件的一种 BNF 语法的语言。
现在在 实时游乐场 尝试它!
SBNF 目前用于 SWI-Prolog。
动机和目标
编写语法定义容易出错,且结果难以维护。虽然添加了 branch_point
作为一个很好的特征,但在使用时显著增加了复杂性和重复。
SBNF 尝试做到以下几点:* 提供一种可维护的、声明性的语言来编写 sublime 语法定义* 快速编译以实现快速迭代* 编译到效率高的语法,可媲美手动制作的语法
安装
请注意,为了使用生成的语法,您至少需要 Sublime Text 版本 4077 及其对 Sublime Syntax 版本 2 的支持。
Sublime 包
导航到 发布页面,然后下载 SBNF.sublime-package
文件。这是一个 zip 文件。您需要将 zip 文件的内容提取到 Sublime Text 数据目录 中的 Packages/SBNF
文件夹。
Cargo
在 rust 安装后,您可以使用以下命令下载、构建和安装 SBNF 的最新发布版本:
$ cargo install sbnfc
或者,如果您想使用最新功能,克隆此存储库,然后使用以下命令构建和安装:
$ cargo install --path cli
Sublime 语法
SBNF 的语法定义在 sbnf/sbnf.sbnf
中。要编译它,只需运行 sbnf sbnf/sbnf.sbnf
,然后您可以将 sbnf/
目录作为符号链接或复制到您的用户包。
示例
以下是一个用于简化版 C 的 sbnf 语法。它仅允许全局/局部变量声明、函数定义和简单函数调用。即使这个简化版也极具挑战性,难以正确解析所需 meta.function
和 meta.function-call
scopes,因为函数定义和函数调用都需要分支点。
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>
。名称必须遵循SCREAMING_SNAKE_CASE。以下名称保留用于元数据
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> ;
。名称必须遵循kebab-case。
与sublime-syntax文件一样,SBNF语法有两个入口点:main
和prototype
。它们的行为与sublime-syntax文件中的行为相同。只有直接或间接从入口点使用的规则会被编译。
规则可以可选地包含参数和选项。参数用于元编程,选项用于sublime-syntax特定的选项。
示例
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!() 而不是提供实现。
- 当在非弹出循环中使用分支时添加警告。
- 修复规则引用自身时的无限循环/递归。