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+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_use/sflhni%247rp%241%40ciao.gmane.io.

Reply via email to