Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support semantic context of isNewStatement #361

Open
wants to merge 2 commits into
base: next
Choose a base branch
from

Conversation

JackWang032
Copy link
Collaborator

@JackWang032 JackWang032 commented Nov 4, 2024

Features

新增语义上下文( semanticContext ) - isNewStatment 用于判断当前输入位置是否处于一条新语句开头(基于语法文件)。

改动点

  1. 每种语言添加SemanticContextCollector实现类用于收集语义上下文
  2. basicSQL中新增getSemanticContextAtCaretPosition方法用于获取语义上下文信息

使用

const { isNewStatement } = hiveSql.getSemanticContextAtCaretPosition(sql, {
    lineNumber: 1,
    column: 5,
});

@JackWang032
Copy link
Collaborator Author

如何做 isNewStatement 的判断?
在 antlr4 Listener 遍历模式中,通过 visitTerminal 访问叶子节点,通过 visitErrorNode 访问存在错误的叶子节点。
理想情况下分析可能有以下几种情况

# 1. SELECT 关键字未输完,会处理成ErrorNode
SELECT id FROM table1 WHERE id = ``;
SELE|

# 2. SELECT 关键字输完了,处理成Terminal节点
SELECT id FROM table1 WHERE id = ``;
SELECT|

# 3. 关键字输入完了,处于一个 statement 规则中,但光标并不在 statmement 开头
SELECT id FROM table1 WHERE id = ``;
SELECT aaa FROM|

1.第一种情况:

语法树分析
image

SELE 对应的 ErrorNode 为 根节点 program 的直接子节点, 那么意味着它未匹配任何 statement 规则, 可以认定为正处于 statement 开头的上下文中。不过除此之外,还需要排除前面语句存在报错的情况,这会导致语法解析树不够可靠,如下图所示语句则不处于 newStatement 中。

image

2.第二种情况:

image

通过 visitTerminal 或者 visitErrorNode 定位到该节点后, 从该节点开始一直访问父节点直到 statement 规则节点,如果该节点是 statement 下子树的第一个叶子节点,那么可以判断为处于 newStatement 中, 同样的也需要对前置语句是否存在报错进行判断。

3.第三种情况:

当输入一个未完成的语句时,根据语句不同,它可能有三种解析状态:

  1. 语法树上已经是个完整的 statement 了,即不存在任何报错,但实际我们期望还需要输入更多
image

这种情况下,我们键入空格后,会认定为处于 newStatement 中, 但这其实是与 c3 的行为一致的, c3 的补全会在此处提供所有 关键字。

  1. 语法树解析错误,错误都被收敛至一个 statement
image

这时可以直接通过第二种情况的解决方法进行处理

  1. 语法解析错误,拆分成多个 statement
image

部分语言如MySQL与PG, 由于 grammar 语法文件书写原因,导致 antlr4 的错误恢复机制不能很好的推测出需要恢复至哪个规则,如图 create 被单独处理进了 createTable 规则中, 而 table if not 被处理进了 setOperations 子规则中。要解决这个问题要么从语法文件入手,重新编排顺序优先级,或者对前置所有 statement 进行报错判断,此处采用后者方法。

4. 特殊情况:光标前是空格

image

如果光标处的 token 类型为 whiteSpace,由于 whiteSpace 在语法文件中都被塞进了 hidden 管道中 WS: [ \r\n\t]+ -> channel(HIDDEN), 这就没法在遍历器中访问到该节点, 那我们需要利用 tokenStream 中找到该 WS 节点前的第一个非 WS 节点进行辅助判断,如果该非 WS 节点为 statement 的最后一个叶子节点, 即结束符,那么可以认定为处于 newStatement 中。

5. 特殊情况:前一个 token 为语句分隔符 ;

如果找到当前输入位置的前一个 token 为分号,那么强制认定为处于 newStatement 中, 不做任何额外的 Exception 判断

@JackWang032 JackWang032 self-assigned this Nov 4, 2024
@JackWang032 JackWang032 requested a review from mumiao November 4, 2024 07:24
@Cythia828
Copy link
Collaborator

image 这里说直接用第二种情况的解决方法,第二种情况是直接检索到dang qian当前输完的这个节点是当前statement的第一个节点,则认定是在一个新statement开头,那截图里说的这种情况都被收敛在同一个statement,但是并不能保证在同一个是第几个节点,按照你的意思,这种情况是认定为新statement呢还是根据是否只完整输入了第一个节点去判定是否为一个新statement开头呢?

@JackWang032
Copy link
Collaborator Author

image 这里说直接用第二种情况的解决方法,第二种情况是直接检索到dang qian当前输完的这个节点是当前statement的第一个节点,则认定是在一个新statement开头,那截图里说的这种情况都被收敛在同一个statement,但是并不能保证在同一个是第几个节点,按照你的意思,这种情况是认定为新statement呢还是根据是否只完整输入了第一个节点去判定是否为一个新statement开头呢?

判断规则是 statement 的第一个叶子节点,而不是父规则的第一个叶子节点,所以不是处于 newStatement 上下文

@Cythia828
Copy link
Collaborator

image 这里说直接用第二种情况的解决方法,第二种情况是直接检索到dang qian当前输完的这个节点是当前statement的第一个节点,则认定是在一个新statement开头,那截图里说的这种情况都被收敛在同一个statement,但是并不能保证在同一个是第几个节点,按照你的意思,这种情况是认定为新statement呢还是根据是否只完整输入了第一个节点去判定是否为一个新statement开头呢?

判断规则是 statement 的第一个叶子节点,而不是父规则的第一个叶子节点,所以不是处于 newStatement 上下文

所以我刚刚截图那里CREATE TABLE IF,光标出现在IF后面,按照这个逻辑应该判定为不是一个新statement开头了对吧

@JackWang032
Copy link
Collaborator Author

image 这里说直接用第二种情况的解决方法,第二种情况是直接检索到dang qian当前输完的这个节点是当前statement的第一个节点,则认定是在一个新statement开头,那截图里说的这种情况都被收敛在同一个statement,但是并不能保证在同一个是第几个节点,按照你的意思,这种情况是认定为新statement呢还是根据是否只完整输入了第一个节点去判定是否为一个新statement开头呢?

判断规则是 statement 的第一个叶子节点,而不是父规则的第一个叶子节点,所以不是处于 newStatement 上下文

所以我刚刚截图那里CREATE TABLE IF,光标出现在IF后面,按照这个逻辑应该判定为不是一个新statement开头了对吧

是的

@LuckyFBB
Copy link
Collaborator

LuckyFBB commented Nov 6, 2024

image

这个要不直接提个 issue,到时候大家在对应修改吧

@liuxy0551
Copy link
Collaborator

image
第三种情况的第3小点,建议从语法文件彻底修复该问题

test/helper.ts Show resolved Hide resolved
src/parser/common/semanticContextCollector.ts Outdated Show resolved Hide resolved
src/parser/common/semanticContextCollector.ts Outdated Show resolved Hide resolved
@mumiao
Copy link
Collaborator

mumiao commented Nov 14, 2024

建议git rebase一下,抵消一些
image
变更

@JackWang032
Copy link
Collaborator Author

新增支持配置切分策略sqlSplitStrategy, 可选项为严格策略SqlSplitStrategy.STRICT 与宽松策略SqlSplitStrategy.LOOSE,默认为STRICT

当策略为SqlSplitStrategy.STRICT时,仅在前一个有效token为分号时, isNewStatementtrue
例如,一段SQL为

SELECT id FROM t1
CREATE

SELECT语句后没有显式以分号结尾,那么CREATE并不处于isNewStatement上下文中
当策略为SqlSplitStrategy.LOOSE时,会基于语法解析树去拆分,那么这段SQL会被拆分为两段独立语句,而CREATE将处于isNewStatement上下文中

配置方式:

const { isNewStatement } = flinkSql.getSemanticContextAtCaretPosition(
    sql,
    {
        lineNumber: 2,
        column: 7,
    },
    {
        sqlSplitStrategy: SqlSplitStrategy.STRICT,
    }
);

@JackWang032
Copy link
Collaborator Author

isNewStatement欠缺语义性,重命名为isStatementBeginning

@JackWang032
Copy link
Collaborator Author

Docs done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants