Thursday, August 19, 2021

[vim9script] Why is a forward declaration needed?

Hi, I have tried to port this TypeScript script:

https://github.com/sigma-engineering/blog-combinators/blob/master/index.ts

to Vim9 script (please find the full code at the end of this message).
It appears to work, but to make it work I had to add a "forward
declaration" of the Call() function:

var Call: func(dict<any>): dict<any>

If I comment out the above line, I get a "E1001: Variable not found:
Call" error, pointing at this line:

const TrailingArg = Map(Sequence([Str(","), Expr]), (args) => args[1])

What baffles me is that Vim doesn't complain in the same way about
NumberLiteral. Does anyone have an explanation for this behaviour?

Using Vim 8.2.3350.

Thanks,
Life.

##############################################################################
vim9script

# See also: https://github.com/sigma-engineering/blog-combinators

# type TSuccess dict<any>
# type TFailure dict<any>
# type TResult TSuccess | TFailure
# type TContext dict<any>
# type TParser func(TContext): TResult

def Success(ctx: dict<any>, value: any): dict<any>
return { success: true, value: value, ctx: ctx }
enddef

def Failure(ctx: dict<any>, expected: string): dict<any>
return { success: false, expected: expected, ctx: ctx }
enddef

# Match a string exactly, or fail
def Str(match: string): func(dict<any>): dict<any>
return (ctx: dict<any>): dict<any> => {
const endIndex = ctx.index + len(match) - 1
if (ctx.text[(ctx.index) : (endIndex)] ==# match)
ctx.index = endIndex + 1
return Success(ctx, match)
else
return Failure(ctx, match)
endif
}
enddef

# Match a regexp or fail
def Regex(re: string, expected: string): func(dict<any>): dict<any>
return (ctx: dict<any>): dict<any> => {
const res = matchstrpos(ctx.text, re, ctx.index)
if res[1] == ctx.index
ctx.index = res[2]
return Success(ctx, res[0])
else
return Failure(ctx, expected)
endif
}
enddef

# Try each matcher in order, starting from the same point in the input. return
# the first one that succeeds. or return the failure that got furthest in the
# input string. Which failure to return is a matter of taste; we prefer the
# furthest failure because it tends be the most useful/complete error
# message.
def Either(Parsers: list<func(dict<any>): dict<any>>): func(dict<any>): dict<any>
return (ctx: dict<any>): dict<any> => {
var furthestRes: dict<any> = {ctx: {index: -1}}

for Parser in Parsers
const res = Parser(ctx)
if res.success
return res
endif
if furthestRes.ctx.index < res.ctx.index
furthestRes = res
endif
endfor
return furthestRes
}
enddef

def Null(ctx: dict<any>): dict<any>
return Success(ctx, null)
enddef

# Match a parser, or fail silently
def Optional(Parser: func(dict<any>): dict<any>): func(dict<any>): dict<any>
return Either([Parser, Null])
enddef

def Many(Parser: func(dict<any>): dict<any>): func(dict<any>): dict<any>
return (ctx): dict<any> => {
var values = []
var nextCtx = ctx
while (true)
const res = Parser(nextCtx)
if (!res.success)
break
endif
values->add(res.value)
nextCtx = res.ctx
endwhile
return Success(nextCtx, values)
}
enddef

# Look for an exact sequence of parsers, or fail
def Sequence(Parsers: list<func(dict<any>): dict<any>>): func(dict<any>): dict<any>
return (ctx): dict<any> => {
var values = []
var nextCtx = ctx
for Parser in Parsers
const res = Parser(nextCtx)
if (!res.success)
return res
endif
values->add(res.value)
nextCtx = res.ctx
endfor
return Success(nextCtx, values)
}
enddef

# A convenience method that will map a Success to callback, to let us do
# common things like build AST nodes from input strings.
def Map(Parser: func(dict<any>): dict<any>, Fn: func(any): any): func(dict<any>): dict<any>
return (ctx: dict<any>): dict<any> => {
const res = Parser(ctx)
return res.success ? Success(res.ctx, Fn(res.value)) : res
}
enddef

def Str2Nr(n: string): number
return str2nr(n)
enddef

# Grammar-specific

# Expr ::= Call | NumberLiteral
# Call ::= Ident '(' [ArgList] ')'
# ArgList ::= Expr (TrailingArg)*
# TrailingArg ::= ',' Expr
# Number ::= '[+-]\?[0-9]\+'
# Ident ::= '[a-zA-Z][a-zA-Z0-9]*'

#########################################################
# This seems necessary for Vim to digest what follows: #
#########################################################
var Call: func(dict<any>): dict<any>
#########################################################
# Why? #
#########################################################

def Expr(ctx: dict<any>): dict<any>
return Either([NumberLiteral, Call])(ctx)
enddef

const Ident = Regex('[a-zA-Z][a-zA-Z0-9]*', 'identifier')

const NumberLiteral = Map(Regex('[+-]\?[0-9]\+', 'number'), Str2Nr)

const TrailingArg = Map(Sequence([Str(","), Expr]), (args) => args[1])

const ArgList = Map(Sequence([Expr, Many(TrailingArg)]), (args) => flattennew(args))

Call = Sequence([Ident, Str('('), Optional(ArgList), Str(')')])

# Top level parsing function
def Parse(text: string): any
const res = Expr({ text: text, index: 0 })
if res.success
return res.value
endif
return printf("Parse error, expected %s", res.expected)
enddef

def Example(code: string): void
echo Parse(code)
enddef

Example("1")
Example("Foo()")
Example("Foo(Bar())")
Example("Foo(Bar(1,2,3))")
##############################################################################



--
--
You received this message from the "vim_use" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

---
You received this message because you are subscribed to the Google Groups "vim_use" group.
To unsubscribe from this group and stop receiving emails from it, send an email to vim_use+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/vim_use/sflhni%247rp%241%40ciao.gmane.io.

No comments: