[vim9script] Why is a forward declaration needed?

34 views
Skip to first unread message

Lifepillar

unread,
Aug 19, 2021, 8:10:45 AM8/19/21
to vim...@googlegroups.com
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))")
##############################################################################



Bram Moolenaar

unread,
Aug 19, 2021, 3:09:17 PM8/19/21
to vim...@googlegroups.com, Lifepillar

> 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?

You are using "Call" in Expr(), and Expr is used in an expression
to set "TrailingArg". To be able to check the type there, Expr() is
compiled, which requires "Call" to exist.

NumberLiteral is defined just before setting "TrailingArg".

What is tricky here that in principle a variable needs to be defined
before used, and when used in a function it should be defined before
that function. But since compilation is done only when needed, it can
be defined after the function, so long as it's before compiling it.

--
What do you get when you cross a joke with a rehtorical question?

/// Bram Moolenaar -- Br...@Moolenaar.net -- http://www.Moolenaar.net \\\
/// \\\
\\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
\\\ help me help AIDS victims -- http://ICCF-Holland.org ///

Lifepillar

unread,
Aug 19, 2021, 4:03:06 PM8/19/21
to vim...@googlegroups.com
On 2021-08-19, Bram Moolenaar <Br...@moolenaar.net> wrote:
> You are using "Call" in Expr(), and Expr is used in an expression
> to set "TrailingArg". To be able to check the type there, Expr() is
> compiled, which requires "Call" to exist.

Got it.

> NumberLiteral is defined just before setting "TrailingArg".

Too silly of me not to notice that.

> What is tricky here that in principle a variable needs to be defined
> before used, and when used in a function it should be defined before
> that function. But since compilation is done only when needed, it can
> be defined after the function, so long as it's before compiling it.

Ok, that makes sense.

Thanks,
Life.

Reply all
Reply to author
Forward
0 new messages