Skip to content

Instantly share code, notes, and snippets.

@marihachi
Last active January 6, 2023 15:24
Show Gist options
  • Save marihachi/d827ff1448e799efab805e86d1397f36 to your computer and use it in GitHub Desktop.
Save marihachi/d827ff1448e799efab805e86d1397f36 to your computer and use it in GitHub Desktop.
Terrario for AiScript

Terrario for AiScript

v1.1.1
for AiScript v0.12.x (>= v0.12.1)

original implementation:
https://github.com/marihachi/terrario

Basic APIs

str(value)

Generates a parser that consumes the specified string.

let parser = str("abc")

seq(parsers)

Generates a parser that applies parsers in sequence.

let parser = seq([
  str("abc"),
  str("123"),
])

alt(parsers)

Generates a parser that tries to match one of the parsers. The parsers are used in order of precedence.

let parser = alt([
  str("abc"),
  str("123"),
])

matched(parser)

Generates a parser that performs the Positive lookahead.

let parser = seq([
  matched(str("a")),
  char.many(1),
])

notMatched(parser)

Generates a parser that performs the Negative lookahead.

let parser = seq([
  notMatched(str("a")),
  char.many(1),
])

char

A parser that consumes any single character.

let parser = char.many(1)

createLanguage(rules)

We can define some syntax rules to build a language.

let lang = createLanguage({
  root: @(r) {
    alt([
      r.nest,
      r.word,
    ]).many(0)
  }
  nest: @(r) {
    seq([
      str("("),
      r.root,
      str(")"),
    ])
  }
  word: @(r) {
    str("abc")
  }
})
lang.root.parse("abc(abc)")

parser.parse(input)

Parses with the parser.

let parser = str("abc")
let result = parser.parse("abc")
<: Json:stringify(result)
// => {"success":true,"value":"abc","index":3}

parser.map(func)

Maps the parsed results using the specified function.

let parser = str("123").map(@(x) { x.to_num() })
let parser = seq([
  str("#"),
  str("123"),
]).map(@(x) { x[1] })

parser.text()

The parser maps the consumed portion as a string.

let parser = seq([
  str("abc"),
  str("123"),
]).text()

parser.many(min)

Repeatedly applies the parser. The argument min specifies the minimum number of times it will be applied.

let parser = str("123").many(1)

parser.option()

Make the parser consumption optional.

let parser = seq([
  str("-").option(),
  str("1"),
])

Other APIs

success(index, value)

failure(index)

succeeded(value)

newParser(handler)

lazy(func)

eof

parser.handler(input, index)

// Terrario for AiScript
// v1.1.1
// MIT License
// (c) 2023 Marihachi
@success(index, value) {
{ success: true, value: value, index: index }
}
@failure(index) {
{ success: false, index: index }
}
@newParser(handler) {
let self = {
handler: handler,
parse: @(input) {
let parser = seq([self, eof]).map(@(x) { x[0] })
parser.handler(input, 0)
}
map: @(func) {
newParser(@(input, index) {
let result = self.handler(input, index)
if result.success {
success(result.index, func(result.value))
} else {
result
}
})
}
text: @() {
newParser(@(input, index) {
let result = self.handler(input, index)
if result.success {
success(result.index, input.slice(index, result.index))
} else {
result
}
})
}
many: @(min) {
newParser(@(input, index) {
var result = null
var latestIndex = index
let accum = []
loop {
if latestIndex >= input.len {
break
}
result = self.handler(input, latestIndex)
if result.success == false {
break
}
latestIndex = result.index
accum.push(result.value)
}
if accum.len >= min {
success(latestIndex, accum)
} else {
failure(latestIndex)
}
})
}
option: @() {
alt([
self,
succeeded(null),
])
}
}
self
}
@str(value) {
newParser(@(input, index) {
if (input.len - index) < value.len {
failure(index)
} elif input.slice(index, index + value.len) != value {
failure(index)
} else {
success(index + value.len, value)
}
})
}
@seq(parsers) {
newParser(@(input, index) {
var latestIndex = index
let accum = []
for (let i, parsers.len) {
let result = parsers[i].handler(input, latestIndex)
if result.success == false {
return result
}
latestIndex = result.index
accum.push(result.value)
}
success(latestIndex, accum)
})
}
@alt(parsers) {
newParser(@(input, index) {
for (let i, parsers.len) {
let result = parsers[i].handler(input, index)
if result.success {
return result
}
}
failure(index)
})
}
@lazy(func) {
let parser = newParser(@(input, index) {
parser.handler = func().handler
parser.handler(input, index)
})
parser
}
@succeeded(value) {
newParser(@(input, index) { success(index, value) })
}
@matched(parser) {
newParser(@(input, index) {
let result = parser.handler(input, index)
if result.success {
success(index, result.value)
} else {
failure(index)
}
})
}
@notMatched(parser) {
newParser(@(input, index) {
let result = parser.handler(input, index)
if result.success {
failure(index)
} else {
success(index, null)
}
})
}
let eof = newParser(@(input, index) {
if index >= input.len {
success(index, null)
} else {
failure(index)
}
})
let char = newParser(@(input, index) {
if (input.len - index) >= 1 {
success(index + 1, input.pick(index))
} else {
failure(index)
}
})
@createLanguage(syntaxes) {
let rules = {}
each (let key, Obj:keys(syntaxes)) {
let lazyParser = lazy(@() {
let parser = Obj:get(syntaxes, key)(rules)
if parser == null {
<: `createLanguage error: rule '{key}' returns null.`
}
parser
})
Obj:set(rules, key, lazyParser)
}
rules
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment