diff options
| author | Adam Stankiewicz <sheerun@sher.pl> | 2021-06-09 09:50:19 +0200 | 
|---|---|---|
| committer | Adam Stankiewicz <sheerun@sher.pl> | 2021-06-09 09:50:19 +0200 | 
| commit | ff8c1d76741f148d5f6efb9a57119dcf11afaec6 (patch) | |
| tree | 2717f611f45d2a82d83ae1e320af4f3aa33b4264 | |
| parent | 27756b129b7a1f9763a0062aafa8fd35b0181b19 (diff) | |
| download | vim-polyglot-ff8c1d76741f148d5f6efb9a57119dcf11afaec6.tar.gz vim-polyglot-ff8c1d76741f148d5f6efb9a57119dcf11afaec6.zip | |
Update
Diffstat (limited to '')
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | autoload/smt2.vim | 34 | ||||
| -rw-r--r-- | autoload/smt2/formatter.vim | 142 | ||||
| -rw-r--r-- | autoload/smt2/parser.vim | 227 | ||||
| -rw-r--r-- | autoload/smt2/scanner.vim | 381 | ||||
| -rw-r--r-- | autoload/smt2/solver.vim | 56 | ||||
| -rw-r--r-- | extras/filetype.vim | 2 | ||||
| -rw-r--r-- | ftplugin/dhall.vim | 2 | ||||
| -rw-r--r-- | ftplugin/julia.vim | 8 | ||||
| -rw-r--r-- | ftplugin/smt2.vim | 30 | 
10 files changed, 821 insertions, 63 deletions
| @@ -83,7 +83,7 @@ On top of all language packs from [vim repository](https://github.com/vim/vim/tr  - [fsharp](https://github.com/ionide/Ionide-vim) (F# syntax highlighting for fs, fsi and fsx files)  - [gdscript](https://github.com/calviken/vim-gdscript3) (GDScript syntax highlighting for gd files)  - [git](https://github.com/tpope/vim-git) (Git Config syntax highlighting for gitconfig files) -- [gitignore](https://github.com/fszymanski/fzf-gitignore) +- [gitignore](https://github.com/SirJson/fzf-gitignore)  - [gleam](https://github.com/gleam-lang/gleam.vim) (Syntax highlighting for gleam files)  - [glsl](https://github.com/tikhomirov/vim-glsl) (GLSL syntax highlighting for glsl, fp, frag, frg, fs and 16 more files)  - [gmpl](https://github.com/maelvalais/gmpl.vim) (Syntax highlighting for mod files) diff --git a/autoload/smt2.vim b/autoload/smt2.vim deleted file mode 100644 index 1aee2635..00000000 --- a/autoload/smt2.vim +++ /dev/null @@ -1,34 +0,0 @@ -if polyglot#init#is_disabled(expand('<sfile>:p'), 'smt2', 'autoload/smt2.vim') -  finish -endif - -" Invokes the solver on current file -function! smt2#RunSolver() -    silent !clear -    execute "!" . g:smt2_solver_command . " " . bufname("%") -endfunction - -" Puts the solver's output in new split (replaces old split) -function! smt2#RunSolverAndShowResult() -    let output = system(g:smt2_solver_command . " " . bufname("%") . " 2>&1") - -    " Create split (or reuse existent) -    if exists("s:outputbufnr") && bufwinnr(s:outputbufnr) > 0 -        execute bufwinnr(s:outputbufnr) . 'wincmd w' -    else -        silent! vnew -        let s:outputbufnr=bufnr('%') -    endif - -    " Clear & (re-)fill contents -    silent! normal! ggdG -    setlocal filetype=smt2 buftype=nofile nobuflisted noswapfile -    call append(0, split(output, '\v\n')) -    normal! gg -endfunction - -" Requests the solver's version -function! smt2#PrintSolverVersion() -    silent !clear -    execute "!" . g:smt2_solver_command . " " . g:smt2_solver_version_switch -endfunction diff --git a/autoload/smt2/formatter.vim b/autoload/smt2/formatter.vim new file mode 100644 index 00000000..6a461b4c --- /dev/null +++ b/autoload/smt2/formatter.vim @@ -0,0 +1,142 @@ +if polyglot#init#is_disabled(expand('<sfile>:p'), 'smt2', 'autoload/smt2/formatter.vim') +  finish +endif + +" Formatting requires a rather recent Vim version +if !((v:version > 802) || (v:version == 802 && has("patch2725"))) +    const s:errmsg_oldvim = "Vim >= 8.2.2725 required for auto-formatting" + +    "Dummies +    function! smt2#formatter#FormatCurrentParagraph() +        echoerr s:errmsg_oldvim +    endfunction +    function! smt2#formatter#FormatAllParagraphs() +        echoerr s:errmsg_oldvim +    endfunction + +    finish +endif +vim9script + +# ------------------------------------------------------------------------------ +# Config +# ------------------------------------------------------------------------------ +# Length of "short" S-expressions +if !exists("g:smt2_formatter_short_length") +    g:smt2_formatter_short_length = 80 +endif + +# String to use for indentation +if !exists("g:smt2_formatter_indent_str") +    g:smt2_formatter_indent_str = '  ' +endif + +# ------------------------------------------------------------------------------ +# Formatter +# ------------------------------------------------------------------------------ +def FitsOneLine(ast: dict<any>): bool +    # A paragraph with several entries should not be formatted in one line +    if ast.kind ==# 'Paragraph' && len(ast.value) != 1 +        return false +    endif +    return ast.pos_to - ast.pos_from < g:smt2_formatter_short_length && !ast.contains_comment +enddef + +def FormatOneLine(ast: dict<any>): string +    if ast.kind ==# 'Atom' +        return ast.value.lexeme +    elseif ast.kind ==# 'SExpr' +        var formatted = [] +        for expr in ast.value +            call formatted->add(expr->FormatOneLine()) +        endfor +        return '(' .. formatted->join(' ') .. ')' +    elseif ast.kind ==# 'Paragraph' +        return ast.value[0]->FormatOneLine() +    endif +    throw 'Cannot format AST node: ' .. string(ast) +    return '' # Unreachable +enddef + +def Format(ast: dict<any>, indent = 0): string +    const indent_str = repeat(g:smt2_formatter_indent_str, indent) + +    if ast.kind ==# 'Atom' +        return indent_str .. ast.value.lexeme +    elseif ast.kind ==# 'SExpr' +        # Short expression -- avoid line breaks +        if ast->FitsOneLine() +            return indent_str .. ast->FormatOneLine() +        endif + +        # Long expression -- break lines and indent subexpressions. +        # Don't break before first subexpression if it's an atom +        # Note: ast.value->empty() == false; otherwise it would fit in one line +        var formatted = [] +        if (ast.value[0].kind ==# 'Atom') +            call formatted->add(ast.value[0]->Format(0)) +        else +            call formatted->add("\n" .. ast.value[0]->Format(indent + 1)) +        endif +        for child in ast.value[1 :] +            call formatted->add(child->Format(indent + 1)) +        endfor +        return indent_str .. "(" .. formatted->join("\n") .. ")" +    elseif ast.kind ==# 'Paragraph' +        var formatted = [] +        for child in ast.value +            call formatted->add(child->Format()) +        endfor +        return formatted->join("\n") +    endif +    throw 'Cannot format AST node: ' .. string(ast) +    return '' # Unreachable +enddef + +# ------------------------------------------------------------------------------ +# Public functions +# ------------------------------------------------------------------------------ +def smt2#formatter#FormatCurrentParagraph() +    const cursor = getpos('.') +    const ast = smt2#parser#ParseCurrentParagraph() + +    # Identify on which end of the buffer we are (to fix newlines later) +    silent! normal! { +    const is_first_paragraph = line('.') == 1 +    silent! normal! } +    const is_last_paragraph = line('.') == line('$') + +    # Replace paragraph by formatted lines +    const lines = split(Format(ast), '\n') +    silent! normal! {d} +    if is_last_paragraph && !is_first_paragraph +        call append('.', [''] + lines) +    else +        call append('.', lines + ['']) +    endif + +    # Remove potentially introduced first empty line +    if is_first_paragraph | silent! :1delete | endif + +    # Restore cursor position +    call setpos('.', cursor) +enddef + +def smt2#formatter#FormatAllParagraphs() +    const cursor = getpos('.') +    const asts = smt2#parser#ParseAllParagraphs() + +    # Clear buffer & insert formatted paragraphs +    silent! :1,$delete +    for ast in asts +        const lines = split(Format(ast), '\n') + [''] +        call append('$', lines) +    endfor + +    # Remove first & trailing empty lines +    silent! :1delete +    silent! :$delete + +    # Restore cursor position +    call setpos('.', cursor) +enddef diff --git a/autoload/smt2/parser.vim b/autoload/smt2/parser.vim new file mode 100644 index 00000000..ef4068e5 --- /dev/null +++ b/autoload/smt2/parser.vim @@ -0,0 +1,227 @@ +if polyglot#init#is_disabled(expand('<sfile>:p'), 'smt2', 'autoload/smt2/parser.vim') +  finish +endif + +vim9script +const debug = false +set maxfuncdepth=100000000 # SMT files tend to be highly nested + +# TODO: Retry iterative parsing now that we have a scanner and simpler grammar +# TODO: Refer to token kind by name, e.g. token_comment instead of 8 +# TODO: Change Ast.kind type from string to enum/number? + +# ------------------------------------------------------------------------------ +# AST nodes -- essentially named token wrappers +# +# Note: pos_from, pos_to and contains_comment were only introduced to allow for +#       a fast FitsOneLine(ast) function in the formatter. +#       Here, pos_from and pos_to refer to indices of characters -- not tokens +# ------------------------------------------------------------------------------ +def Ast(kind: string, value: any, pos_from: number, pos_to: number, contains_comment: bool): dict<any> +    return {kind: kind, value: value, pos_from: pos_from, pos_to: pos_to, contains_comment: contains_comment} +enddef + +def ParagraphAst(exprs: list<dict<any>>, pos_from: number, pos_to: number): dict<any> +    var contains_comment = false +    for expr in exprs +        if expr.contains_comment +            contains_comment = true +            break +        endif +    endfor +    return Ast('Paragraph', exprs, pos_from, pos_to, contains_comment) +enddef + +def SExprAst(exprs: list<dict<any>>, pos_from: number, pos_to: number): dict<any> +    var contains_comment = false +    for expr in exprs +        if expr.contains_comment +            contains_comment = true +            break +        endif +    endfor +    return Ast('SExpr', exprs, pos_from, pos_to, contains_comment) +enddef + +def AtomAst(token: dict<any>): dict<any> +    return Ast('Atom', token, token.pos, token.pos + len(token.lexeme), token.kind == 8) +enddef + +def PrintAst(ast: dict<any>, indent = 0) +    echo repeat(' ', indent * 2) .. '[' .. ast.kind .. '] ' + +    if ast.kind ==# 'Atom' +        echon ast.value.lexeme +    elseif ast.kind ==# 'SExpr' +        for v in ast.value +            call PrintAst(v, indent + 1) +        endfor +    elseif ast.kind ==# 'Paragraph' +        for v in ast.value +            call PrintAst(v, indent + 1) +        endfor +    endif +enddef + +# ------------------------------------------------------------------------------ +# Grammar +# ------------------------------------------------------------------------------ +# Paragraph ::= Expr+ +# Expr      ::= SExpr | Atom +# SExpr     ::= '(' Expr* ')' + +# ------------------------------------------------------------------------------ +# LParen +# ------------------------------------------------------------------------------ +def AtStartOfLParen(scanner: dict<any>): bool +    return scanner.cur_token.kind == 0 # token_lparen +enddef + +def ParseLParen(scanner: dict<any>) # consumes token; no return +    if debug +        scanner->smt2#scanner#Enforce(scanner->AtStartOfLParen(), +            "ParseLParen called but not at start of LParen", +            scanner.cur_token.pos) +    endif + +    scanner->smt2#scanner#NextToken() +enddef + +# ------------------------------------------------------------------------------ +# RParen +# ------------------------------------------------------------------------------ +def AtStartOfRParen(scanner: dict<any>): bool +    return scanner.cur_token.kind == 1 # token_rparen +enddef + +def ParseRParen(scanner: dict<any>) # consumes token; no return +    if debug +        scanner->smt2#scanner#Enforce(scanner->AtStartOfRParen(), +            "ParseRParen called but not at start of RParen", +            scanner.cur_token.pos) +    endif + +    scanner->smt2#scanner#NextToken() +enddef + +# ------------------------------------------------------------------------------ +# Atom +# ------------------------------------------------------------------------------ +def AtStartOfAtom(scanner: dict<any>): bool +    return 2 <= scanner.cur_token.kind && scanner.cur_token.kind <= 8 +enddef + +def ParseAtom(scanner: dict<any>): dict<any> +    if debug +        scanner->smt2#scanner#Enforce(scanner->AtStartOfAtom(), +            "ParseAtom called but not at start of Atom", +            scanner.cur_token.pos) +    endif + +    const ast = AtomAst(scanner.cur_token) +    scanner->smt2#scanner#NextToken() +    return ast +enddef + +# ------------------------------------------------------------------------------ +# Expr +# ------------------------------------------------------------------------------ +def AtStartOfExpr(scanner: dict<any>): bool +    return scanner->AtStartOfSExpr() || scanner->AtStartOfAtom() +enddef +def ParseExpr(scanner: dict<any>): dict<any> +    if debug +        scanner->smt2#scanner#Enforce(scanner->AtStartOfExpr(), +            "ParseExpr called but not at start of Expr", +            scanner.cur_token.pos) +    endif + +    if scanner->AtStartOfSExpr() +        return scanner->ParseSExpr() +    endif +    return scanner->ParseAtom() +enddef + +# ------------------------------------------------------------------------------ +# SExpr +# ------------------------------------------------------------------------------ +const AtStartOfSExpr = funcref(AtStartOfLParen) +def ParseSExpr(scanner: dict<any>): dict<any> +    const pos_from = scanner.cur_token.pos + +    if debug +        scanner->smt2#scanner#Enforce(scanner->AtStartOfSExpr(), +            "ParseSExpr called but not at start of SExpr", +            pos_from) +    endif +    scanner->ParseLParen() + +    # Expr* +    var exprs: list<any> +    while scanner->AtStartOfExpr() +        exprs->add(scanner->ParseExpr()) +    endwhile + +    scanner->smt2#scanner#Enforce(scanner->AtStartOfRParen(), +        printf("Expected RParen but got %s", scanner.cur_token.kind->smt2#scanner#TokenKind2Str()), +        scanner.cur_token.pos) +    scanner->ParseRParen() + +    const pos_to = scanner.cur_token.pos +    return SExprAst(exprs, pos_from, pos_to) +enddef + +# ------------------------------------------------------------------------------ +# Paragraph +# ------------------------------------------------------------------------------ +def ParseParagraph(scanner: dict<any>): dict<any> +    const pos_from = scanner.cur_token.pos + +    # Expr+ +    scanner->smt2#scanner#Enforce(scanner->AtStartOfExpr(), +        printf("Expected Expr but got %s", scanner.cur_token.kind->smt2#scanner#TokenKind2Str()), +        pos_from) + +    var exprs = [scanner->ParseExpr()] +    while scanner->AtStartOfExpr() && !scanner.at_new_paragraph +        exprs->add(scanner->ParseExpr()) +    endwhile + +    const pos_to = scanner.cur_token.pos +    return ParagraphAst(exprs, pos_from, pos_to) +enddef + +# ------------------------------------------------------------------------------ +# Public functions +# ------------------------------------------------------------------------------ +def smt2#parser#ParseCurrentParagraph(): dict<any> +    # source = [start of current paragraph, EOF] +    # Note: This is needed since `silent! normal! {y}` may not yank full paragraphs +    #       in the context of multiline expressions +    const cursor = getpos('.') +    silent! normal! { +    const line_offset = line('.') +    const source = join(getline('.', '$'), "\n") +    call setpos('.', cursor) + +    var scanner = smt2#scanner#Scanner(source, line_offset) +    const ast = scanner->ParseParagraph() + +    if debug | ast->PrintAst() | endif +    return ast +enddef + +def smt2#parser#ParseAllParagraphs(): list<dict<any>> +    # source = current buffer +    const source = join(getline(1, '$'), "\n") + +    var scanner = smt2#scanner#Scanner(source) +    var asts = [] +    while scanner.cur_token.kind != 9 # token_eof +        const ast = scanner->ParseParagraph() +        asts->add(ast) + +        if debug | ast->PrintAst() | endif +    endwhile +    return asts +enddef diff --git a/autoload/smt2/scanner.vim b/autoload/smt2/scanner.vim new file mode 100644 index 00000000..464dbe24 --- /dev/null +++ b/autoload/smt2/scanner.vim @@ -0,0 +1,381 @@ +if polyglot#init#is_disabled(expand('<sfile>:p'), 'smt2', 'autoload/smt2/scanner.vim') +  finish +endif + +vim9script +const debug = false + +# ------------------------------------------------------------------------------ +# Ref: http://smtlib.cs.uiowa.edu/papers/smt-lib-reference-v2.6-r2021-05-12.pdf +# ------------------------------------------------------------------------------ + +# ------------------------------------------------------------------------------ +# Token +# ------------------------------------------------------------------------------ +const token_lparen = 0 +const token_rparen = 1 +const token_numeral = 2 +const token_decimal = 3 +const token_bv = 4 +const token_string = 5 +const token_symbol = 6 +const token_keyword = 7 +const token_comment = 8 +const token_eof = 9 + +def Token(kind: number, pos: number, lexeme: string): dict<any> +    return {kind: kind, pos: pos, lexeme: lexeme} +enddef + +def smt2#scanner#TokenKind2Str(kind: number): string +    if kind == token_lparen +        return "LParen" +    elseif kind == token_rparen +        return "RParen" +    elseif kind == token_numeral +        return "Numeral" +    elseif kind == token_decimal +        return "Decimal" +    elseif kind == token_bv +        return "Bv" +    elseif kind == token_string +        return "String" +    elseif kind == token_symbol +        return "Symbol" +    elseif kind == token_keyword +        return "Keyword" +    elseif kind == token_comment +        return "Comment" +    elseif kind == token_eof +        return "EOF" +    else +        echoerr "Unexpected token kind: " .. kind +        return '' +    endif +enddef + +def PrettyPrint(scanner: dict<any>, token: dict<any>) +    const coord = scanner->Pos2Coord(token.pos) +    echo printf("%4d:%-3d (%5d) %8s %s", coord.line, coord.col, token.pos, token.kind->smt2#scanner#TokenKind2Str(), token.lexeme) +enddef + +# ------------------------------------------------------------------------------ +# Scanner +# +# Note: The public interface is limited to the +#       - field cur_token +#       - method NextToken +#       - field at_new_paragraph (needed to distinguish paragraphs in parser) +# +#       The other fields should only be used internally / in this file +# ------------------------------------------------------------------------------ +# TODO: Enforce restriction to ASCII? We should if we use the lookup table below +# TODO: Do not take a string but a character stream (or just buffer and pos)? + +def smt2#scanner#Scanner(source: string, line_offset = 1): dict<any> +    var scanner = { +        chars: source->trim(" \t\n\r", 2)->split('\zs'), +        line_offset: line_offset, # start line of source string in buffer +        pos: 0,                   # pos in source string -- not column in line +        at_new_paragraph: false} + +    if scanner.chars->empty() +        scanner.at_eof = true +        scanner.cur_char = '' +    else +        scanner.at_eof = false +        scanner.cur_char = scanner.chars[0] +    endif +    scanner.cur_char_nr = scanner.cur_char->char2nr() +    scanner.chars_len = len(scanner.chars) +    scanner.cur_token = {} +    scanner->smt2#scanner#NextToken() +    return scanner +enddef + +def smt2#scanner#NextToken(scanner: dict<any>) +    if scanner.at_eof +        scanner.cur_token = Token(token_eof, scanner.pos, '') +    else +        scanner->SkipWhitespace() # Cannot end up at eof since end is trimmed + +        const nr = scanner.cur_char_nr +        if nr == 40 # '(' +            scanner.cur_token = Token(token_lparen, scanner.pos, '(') +            scanner->NextPos() +        elseif nr == 41 # ')' +            scanner.cur_token = Token(token_rparen, scanner.pos, ')') +            scanner->NextPos() +        elseif nr->IsStartOfSimpleSymbol() +            scanner.cur_token = scanner->ReadSimpleSymbol() +        elseif nr == 124 # '|' +            scanner.cur_token = scanner->ReadQuotedSymbol() +        elseif nr == 58 # ':' +            scanner.cur_token = scanner->ReadKeyword() +        elseif nr->IsDigit() +            scanner.cur_token = scanner->ReadNumber() +        elseif nr == 35 # '#' +            scanner.cur_token = scanner->ReadBv() +        elseif nr == 34 # '"' +            scanner.cur_token = scanner->ReadString() +        elseif nr == 59 # ';' +            scanner.cur_token = scanner->ReadComment() +        else +            scanner->smt2#scanner#Enforce(false, printf("Unexpected character '%s'", scanner.cur_char), scanner.pos) +        endif +    endif + +    if debug +        if scanner.at_new_paragraph | echo "\n" | endif +        scanner->PrettyPrint(scanner.cur_token) +    endif +enddef + +def NextPos(scanner: dict<any>) +    if debug | scanner->smt2#scanner#Enforce(!scanner.at_eof, "Already at EOF", scanner.pos) | endif + +    scanner.pos += 1 +    scanner.at_eof = scanner.pos == scanner.chars_len +    scanner.cur_char = scanner.at_eof ? '' : scanner.chars[scanner.pos] +    scanner.cur_char_nr = scanner.cur_char->char2nr() +enddef + +def smt2#scanner#Enforce(scanner: dict<any>, expr: bool, msg: string, pos: number) +    if !expr +        const coord = scanner->Pos2Coord(pos) +        throw printf("Syntax error (at %d:%d): %s ", coord.line, coord.col, msg) +    endif +enddef + +# This is slow and intended for use in error messages & debugging only +def Pos2Coord(scanner: dict<any>, pos: number): dict<number> +    const line = scanner.chars[: pos]->count("\n") + scanner.line_offset + +    var cur_pos = pos - 1 +    while cur_pos >= 0 && scanner.chars[cur_pos] != "\n" +        cur_pos -= 1 +    endwhile + +    return {line: line, col: pos - cur_pos} +enddef + +# ------------------------------------------------------------------------------ +# <white_space_char> ::= 9 (tab), 10 (lf), 13 (cr), 32 (space) +# +# Note: The source string has all lines joined by "\n" so "\r" can be ignored +# ------------------------------------------------------------------------------ +def SkipWhitespace(scanner: dict<any>) +    var newlines = 0 +    while !scanner.at_eof +        const nr = scanner.cur_char_nr +        if nr == 32 || nr == 9 +            scanner->NextPos() +        elseif nr == 10 +            newlines += 1 +            scanner->NextPos() +        else +            break +        endif +    endwhile +    scanner.at_new_paragraph = newlines > 1 +enddef + +# ------------------------------------------------------------------------------ +# A comment is any character sequence not contained within a string literal or a +# quoted symbol that begins with ; and ends with the first subsequent +# line-breaking character, i.e. 10 (lf) or 13 (cr) +# +# Note: The source string has all lines joined by "\n" so "\r" can be ignored +# ------------------------------------------------------------------------------ +def ReadComment(scanner: dict<any>): dict<any> +    if debug | scanner->smt2#scanner#Enforce(scanner.cur_char == ';', "Not the start of a comment", scanner.pos) | endif + +    const start_pos = scanner.pos +    scanner->NextPos() +    while !scanner.at_eof && scanner.cur_char_nr != 10 +        scanner->NextPos() +    endwhile +    return Token(token_comment, start_pos, scanner.chars[start_pos : scanner.pos - 1]->join('')) +enddef + +# ------------------------------------------------------------------------------ +# <numeral> ::= 0 +#             | a non-empty sequence of digits not starting with 0 +# +# <decimal> ::= <numeral>.0*<numeral> +# ------------------------------------------------------------------------------ +def IsDigit(char_nr: number): bool +    # '0'->char2nr() == 48 && '9'->char2nr() == 57 +    return 48 <= char_nr && char_nr <= 57 +enddef + +def ReadNumber(scanner: dict<any>): dict<any> +    if debug | scanner->smt2#scanner#Enforce(scanner.cur_char_nr->IsDigit(), "Not the start of a number", scanner.pos) | endif + +    const starts_with_zero = scanner.cur_char == '0' +    const start_pos = scanner.pos +    scanner->NextPos() +    # Note: We aren't strict about numbers not starting with 0 when not debugging +    if debug | scanner->smt2#scanner#Enforce(!starts_with_zero || scanner.cur_char != '0', "Numeral may not start with 0", scanner.pos) | endif + +    var is_decimal = false +    while !scanner.at_eof +        const nr = scanner.cur_char_nr +        if 48 <= nr && nr <= 57 # inlined IsDigit +            scanner->NextPos() +        elseif scanner.cur_char == '.' +            if is_decimal +                break +            else +                is_decimal = true +                scanner->NextPos() +            endif +        else +            break +        endif +    endwhile +    const kind = is_decimal ? token_decimal : token_numeral +    return Token(kind, start_pos, scanner.chars[start_pos : scanner.pos - 1]->join('')) +enddef + +# ------------------------------------------------------------------------------ +# <hexadecimal> ::= #x followed by a non-empty sequence of digits and letters +#                   from A to F, capitalized or not +# +# <binary> ::= #b followed by a non-empty sequence of 0 and 1 characters +# ------------------------------------------------------------------------------ + +# Build lookup table for char->match('\m\C^[0-9a-fA-F]') +def InitIsAlphaNumericCharNr(): list<bool> +    var lookup_table = [] +    var char_nr = 0 +    while char_nr < 255 +        lookup_table->add(char_nr->nr2char()->match('\m\C^[0-9a-fA-F]') != -1) +        char_nr += 1 +    endwhile +    return lookup_table +enddef +const is_alphanumeric_char_nr = InitIsAlphaNumericCharNr() + +def ReadBv(scanner: dict<any>): dict<any> +    if debug | scanner->smt2#scanner#Enforce(scanner.cur_char == '#', "Not the start of a bit vector literal", scanner.pos) | endif + +    const start_pos = scanner.pos +    scanner->NextPos() +    if scanner.cur_char == 'x' +        scanner->NextPos() +        scanner->smt2#scanner#Enforce(!scanner.at_eof && is_alphanumeric_char_nr[scanner.cur_char_nr], +            "hexadecimal literal may not be empty", +            scanner.pos) +        while !scanner.at_eof && is_alphanumeric_char_nr[scanner.cur_char_nr] +            scanner->NextPos() +        endwhile +    elseif scanner.cur_char == 'b' +        scanner->NextPos() +        # '0'->char2nr() == 48 && '1'->char2nr() == 49 +        scanner->smt2#scanner#Enforce(!scanner.at_eof && scanner.cur_char_num == 48 || scanner.cur_char_num == 49, +            "binary literal may not be empty", +            scanner.pos) +        while !scanner.at_eof && scanner.cur_char_num == 48 || scanner.cur_char_num == 49 +            scanner->NextPos() +        endwhile +    else +        scanner->smt2#scanner#Enforce(false, "invalid bit vector literal -- expected 'x' or 'b'", scanner.pos) +    endif +    return Token(token_bv, start_pos, scanner.chars[start_pos : scanner.pos - 1]->join('')) +enddef + +# ------------------------------------------------------------------------------ +# <string> ::= sequence of whitespace and printable characters in double +#              quotes with escape sequence "" +# ------------------------------------------------------------------------------ +# TODO: Allow only printable characters, i.e. ranges [32, 126], [128-255]? +def ReadString(scanner: dict<any>): dict<any> +    if debug | scanner->smt2#scanner#Enforce(scanner.cur_char == '"', "Not the start of a string", scanner.pos) | endif + +    const start_pos = scanner.pos +    scanner->NextPos() +    while true +        scanner->smt2#scanner#Enforce(!scanner.at_eof, "unexpected end of string", scanner.pos) + +        if scanner.cur_char == '"' +            scanner->NextPos() +            if scanner.cur_char != '"' +                break +            endif +        endif +        scanner->NextPos() +    endwhile +    return Token(token_string, start_pos, scanner.chars[start_pos : scanner.pos - 1]->join('')) +enddef + +# ------------------------------------------------------------------------------ +# <simple symbol> ::= a non-empty sequence of letters, digits and the characters +#                     + - / * = % ? ! . $ _ ~ & ^ < > @ that does not start with +#                     a digit +# ------------------------------------------------------------------------------ + +# Build lookup table for char->match('\m\C^[a-zA-Z0-9+-/*=%?!.$_~&^<>@]') +def InitIsSimpleSymbolCharNr(): list<bool> +    var lookup_table = [] +    var char_nr = 0 +    while char_nr < 255 +        lookup_table->add(char_nr->nr2char()->match('\m\C^[a-zA-Z0-9+-/*=%?!.$_~&^<>@]') != -1) +        char_nr += 1 +    endwhile +    return lookup_table +enddef +const is_simple_symbol_char_nr = InitIsSimpleSymbolCharNr() + +def IsStartOfSimpleSymbol(char_nr: number): bool +    # '0'->char2nr() == 48 && '9'->char2nr() == 57 +    return is_simple_symbol_char_nr[char_nr] && !(48 <= char_nr && char_nr <= 57) +enddef + +def ReadSimpleSymbol(scanner: dict<any>): dict<any> +    if debug | scanner->smt2#scanner#Enforce(scanner.cur_char_nr->IsStartOfSimpleSymbol(), "Not the start of a simple symbol", scanner.pos) | endif + +    const start_pos = scanner.pos +    scanner->NextPos() +    while !scanner.at_eof && is_simple_symbol_char_nr[scanner.cur_char_nr] +        scanner->NextPos() +    endwhile +    return Token(token_symbol, start_pos, scanner.chars[start_pos : scanner.pos - 1]->join('')) +enddef + +# ------------------------------------------------------------------------------ +# <symbol> ::= <simple symbol> +#            | a sequence of whitespace and printable characters that starts +#              and ends with '|' and does not otherwise include '|' or '\' +# ------------------------------------------------------------------------------ +# TODO: Allow only printable characters, i.e. ranges [32, 126], [128-255]? +def ReadQuotedSymbol(scanner: dict<any>): dict<any> +    if debug | scanner->smt2#scanner#Enforce(scanner.cur_char == '|', "Not the start of a quoted symbol", scanner.pos) | endif + +    const start_pos = scanner.pos +    scanner->NextPos() +    while true +        scanner->smt2#scanner#Enforce(!scanner.at_eof, "unexpected end of quoted symbol", scanner.pos) +        scanner->smt2#scanner#Enforce(scanner.cur_char != '\\', "quoted symbol may not contain '\'", scanner.pos) +        if scanner.cur_char == '|' +            break +        endif +        scanner->NextPos() +    endwhile +    scanner->NextPos() +    return Token(token_symbol, start_pos, scanner.chars[start_pos : scanner.pos - 1]->join('')) +enddef + +# ------------------------------------------------------------------------------ +# <keyword> ::= :<simple symbol> +# ------------------------------------------------------------------------------ +def ReadKeyword(scanner: dict<any>): dict<any> +    if debug | scanner->smt2#scanner#Enforce(scanner.cur_char == ':', "Not the start of a keyword", scanner.pos) | endif + +    const start_pos = scanner.pos +    scanner->NextPos() +    while !scanner.at_eof && is_simple_symbol_char_nr[scanner.cur_char_nr] +        scanner->NextPos() +    endwhile +    return Token(token_keyword, start_pos, scanner.chars[start_pos : scanner.pos - 1]->join('')) +enddef diff --git a/autoload/smt2/solver.vim b/autoload/smt2/solver.vim new file mode 100644 index 00000000..39e7d477 --- /dev/null +++ b/autoload/smt2/solver.vim @@ -0,0 +1,56 @@ +if polyglot#init#is_disabled(expand('<sfile>:p'), 'smt2', 'autoload/smt2/solver.vim') +  finish +endif + +" ------------------------------------------------------------------------------ +" Config +" ------------------------------------------------------------------------------ +" If no command for invoking a solver is specified in ~/.vimrc, test if either +" 'z3' or 'boolector' is accessible through $PATH (in that order) +if !exists("g:smt2_solver_command") +    if executable("z3") +        let g:smt2_solver_command = "z3" +    elseif executable("boolector") +        let g:smt2_solver_command = "boolector" +    endif +endif + +" If no command line switch for printing the solver's version is specified in +" ~/.vimrc, use '--version' +if !exists("g:smt2_solver_version_switch") +    let g:smt2_solver_version_switch = "--version" +endif + +" ------------------------------------------------------------------------------ +" Public functions +" ------------------------------------------------------------------------------ +" Invokes the solver on current file +function! smt2#solver#Run() +    silent !clear +    execute "!" . g:smt2_solver_command . " " . bufname("%") +endfunction + +" Puts the solver's output in new split (replaces old split) +function! smt2#solver#RunAndShowResult() +    let output = system(g:smt2_solver_command . " " . bufname("%") . " 2>&1") + +    " Create split (or reuse existent) +    if exists("s:outputbufnr") && bufwinnr(s:outputbufnr) > 0 +        execute bufwinnr(s:outputbufnr) . 'wincmd w' +    else +        silent! vnew +        let s:outputbufnr=bufnr('%') +    endif + +    " Clear & (re-)fill contents +    silent! normal! ggdG +    setlocal filetype=smt2 buftype=nofile nobuflisted noswapfile +    call append(0, split(output, '\v\n')) +    normal! gg +endfunction + +" Requests the solver's version +function! smt2#solver#PrintVersion() +    silent !clear +    execute "!" . g:smt2_solver_command . " " . g:smt2_solver_version_switch +endfunction diff --git a/extras/filetype.vim b/extras/filetype.vim index 26c15db3..e6ab472f 100644 --- a/extras/filetype.vim +++ b/extras/filetype.vim @@ -1496,7 +1496,7 @@ au BufNewFile,BufRead *.sass			setf sass  au BufNewFile,BufRead *.sa			setf sather  " Scala -au BufNewFile,BufRead *.scala			setf scala +au BufNewFile,BufRead *.scala,*.sc		setf scala  " SBT - Scala Build Tool  au BufNewFile,BufRead *.sbt			setf sbt diff --git a/ftplugin/dhall.vim b/ftplugin/dhall.vim index a484585b..9a662090 100644 --- a/ftplugin/dhall.vim +++ b/ftplugin/dhall.vim @@ -33,7 +33,7 @@ endif  function! DhallFormat()      let cursor = getpos('.')      exec 'normal! gg' -    exec 'silent !dhall format --inplace ' . expand('%') +    exec 'silent !dhall format ' . expand('%')      exec 'e'      call setpos('.', cursor)  endfunction diff --git a/ftplugin/julia.vim b/ftplugin/julia.vim index 785ffa2a..39126598 100644 --- a/ftplugin/julia.vim +++ b/ftplugin/julia.vim @@ -39,7 +39,7 @@ if exists("loaded_matchit")    " for nested-structures-skipping to work properly    " note: 'mutable struct' and 'struct' are defined separately because    " using \? puts the cursor on 'struct' instead of 'mutable' for some reason -  let b:julia_begin_keywords = '\%(\%(\.\s*\)\@<!\|\%(@\s*.\s*\)\@<=\)\<\%(function\|macro\|begin\|mutable\s\+struct\|\%(mutable\s\+\)\@<!struct\|\%(abstract\|primitive\)\s\+type\|let\|do\|\%(bare\)\?module\|quote\|if\|for\|while\|try\)\>' +  let b:julia_begin_keywords = '\%(\.\s*\|@\)\@<!\<\%(function\|macro\|begin\|mutable\s\+struct\|\%(mutable\s\+\)\@<!struct\|\%(abstract\|primitive\)\s\+type\|let\|do\|\%(bare\)\?module\|quote\|if\|for\|while\|try\)\>'    " note: the following regex not only recognizes macros, but also local/global keywords.    " the purpose is recognizing things like `@inline myfunction()`    " or `global myfunction(...)` etc, for matchit and block movement functionality @@ -67,7 +67,7 @@ if exists("loaded_matchit")      call cursor(l, c)      if attr == 'juliaConditional'        return b:julia_begin_keywordsm . ':\<\%(elseif\|else\)\>:' . b:julia_end_keywords -    elseif attr =~ '\<\%(juliaRepeat\|juliaRepKeyword\)\>' +    elseif attr =~# '\<\%(juliaRepeat\|juliaRepKeyword\)\>'        return b:julia_begin_keywordsm . ':\<\%(break\|continue\)\>:' . b:julia_end_keywords      elseif attr == 'juliaBlKeyword'        return b:julia_begin_keywordsm . ':' . b:julia_end_keywords @@ -82,8 +82,8 @@ if exists("loaded_matchit")    " we need to skip everything within comments, strings and    " the 'begin' and 'end' keywords when they are used as a range rather than as    " the delimiter of a block -  let b:match_skip = 'synIDattr(synID(line("."),col("."),1),"name") =~ ' -        \ . '"\\<julia\\%(Comprehension\\%(For\\|If\\)\\|RangeKeyword\\|Comment\\%([LM]\\|Delim\\)\\|\\%([bs]\\|Shell\\|Printf\\|Doc\\)\\?String\\|StringPrefixed\\|DocStringM\\(Raw\\)\\?\\|RegEx\\|SymbolS\\?\\|Macro\\|Dotted\\)\\>"' +  let b:match_skip = 'synIDattr(synID(line("."),col("."),0),"name") =~# ' +        \ . '"\\<julia\\%(Comprehension\\%(For\\|If\\)\\|RangeKeyword\\|Comment\\%([LM]\\|Delim\\)\\|\\%([bs]\\|Shell\\|Printf\\|Doc\\)\\?String\\|StringPrefixed\\|DocStringM\\(Raw\\)\\?\\|RegEx\\|SymbolS\\?\\|Dotted\\)\\>"'    let b:undo_ftplugin = b:undo_ftplugin          \ . " | unlet! b:match_words b:match_skip b:match_ignorecase" diff --git a/ftplugin/smt2.vim b/ftplugin/smt2.vim index d6cfa5ec..b8e8c46c 100644 --- a/ftplugin/smt2.vim +++ b/ftplugin/smt2.vim @@ -3,27 +3,13 @@ if polyglot#init#is_disabled(expand('<sfile>:p'), 'smt2', 'ftplugin/smt2.vim')  endif  setlocal iskeyword+=-,:,#,',$ +setlocal commentstring=;%s -" If no command for invoking a solver is specified in ~/.vimrc, test if either -" 'z3' or 'boolector' is accessible through $PATH (in that order) -if !exists("g:smt2_solver_command") -    if executable("z3") -        let g:smt2_solver_command = "z3" -    elseif executable("boolector") -        let g:smt2_solver_command = "boolector" -    endif -endif - -" If no command line switch for printing the solver's version is specified in -" ~/.vimrc, use '--version' -if !exists("g:smt2_solver_version_switch") -    let g:smt2_solver_version_switch = "--version" -endif - -" Mappings -nnoremap <silent> <buffer> <localleader>r :call smt2#RunSolver()<cr> -nnoremap <silent> <buffer> <localleader>R :call smt2#RunSolverAndShowResult()<cr> -nnoremap <silent> <buffer> <localleader>v :call smt2#PrintSolverVersion()<cr> +" Mappings for solver functionality +nnoremap <silent> <buffer> <localleader>r :call smt2#solver#Run()<cr> +nnoremap <silent> <buffer> <localleader>R :call smt2#solver#RunAndShowResult()<cr> +nnoremap <silent> <buffer> <localleader>v :call smt2#solver#PrintVersion()<cr> -" Comment String -setlocal commentstring=;%s +" Mappings for formatting functionality +nnoremap <silent> <buffer> <localleader>f :call smt2#formatter#FormatCurrentParagraph()<cr> +nnoremap <silent> <buffer> <localleader>F :call smt2#formatter#FormatAllParagraphs()<cr> | 
