///|
struct Runtime {
mut previous : @bytere.Pattern?
mut missing_newline : Bool
}
///|
fn PatternRef::resolve(
self : PatternRef,
runtime : Runtime,
) -> @bytere.Pattern raise {
match self {
Explicit(pattern) => {
runtime.previous = Some(pattern)
pattern
}
Previous =>
match runtime.previous {
Some(pattern) => pattern
None => fail("no previous regular expression")
}
}
}
///|
fn Address::matches(
self : Address,
line : Int,
last : Bool,
text : String,
runtime : Runtime,
) -> Bool raise {
match self {
Number(n) => n == line
Last => last
Search(re) => re.resolve(runtime).find(text) is Some(_)
}
}
///|
fn Command::selected(
self : Command,
line : Int,
last : Bool,
text : String,
runtime : Runtime,
) -> Bool raise {
let mut selected = false
if self.last is Some(end) {
let was_inside = self.inside
if !self.inside && self.first.unwrap().matches(line, last, text, runtime) {
self.inside = true
}
selected = self.inside
if self.inside {
let ended = match end {
Number(n) => line >= n
Last => last
Search(_) => was_inside && end.matches(line, last, text, runtime)
}
if ended {
self.inside = false
}
}
} else {
selected = self.first is None ||
self.first.unwrap().matches(line, last, text, runtime)
}
if self.invert {
!selected
} else {
selected
}
}
///|
fn expand(replacement : String, hit : @bytere.Hit) -> String raise {
let result = StringBuilder()
let mut i = 0
while i < replacement.length() {
let c = replacement[i]
if c == '&' {
result.write_string(hit.groups[0].unwrap_or(""))
} else if c == '\\' && i + 1 < replacement.length() {
i += 1
let e = replacement[i]
if e >= '1' && e <= '9' {
let index = e.to_int() - 48
guard index < hit.groups.length() else {
fail("invalid replacement backreference")
}
result.write_string(hit.groups[index].unwrap_or(""))
} else {
result.write_char(
if e == 'n' {
'\n'
} else if e == 't' {
'\t'
} else {
e.to_int().unsafe_to_char()
},
)
}
} else {
result.write_char(c.to_int().unsafe_to_char())
}
i += 1
}
result.to_string()
}
///|
fn substitute(
re : @bytere.Pattern,
text : String,
replacement : String,
global : Bool,
occurrence : Int,
) -> (String, Bool) raise {
let result = StringBuilder()
let mut search = 0
let mut copied = 0
let mut count = 0
let mut changed = false
let mut nonempty_end = -1
while search <= text.length() {
guard re.find(text, start=search) is Some(hit) else { break }
if hit.start == hit.end && hit.start == nonempty_end {
search = hit.end + 1
continue
}
count += 1
let replace = if occurrence == 0 {
global || count == 1
} else {
count == occurrence || (global && count > occurrence)
}
if replace {
result.write_string(text[copied:hit.start].to_owned())
result.write_string(expand(replacement, hit))
copied = hit.end
changed = true
if !global {
break
}
}
nonempty_end = if hit.end > hit.start { hit.end } else { -1 }
search = if hit.end == hit.start { hit.end + 1 } else { hit.end }
}
result.write_string(text[copied:].to_owned())
(result.to_string(), changed)
}
///|
async fn Runtime::emit(
self : Runtime,
text : String,
terminated : Bool,
) -> Unit {
if self.missing_newline {
@stdio.stdout.write(b"\n")
}
@stdio.stdout.write(@bytere.bytes(text))
if terminated {
@stdio.stdout.write(b"\n")
}
self.missing_newline = !terminated && text != "" && !text.has_suffix("\n")
}
///|
async fn execute_line(
commands : Array[Command],
line : @stream.Line,
number : Int,
last : Bool,
quiet : Bool,
runtime : Runtime,
) -> Bool {
let mut text = @bytere.text(line.data)
for command in commands {
if !command.selected(number, last, text, runtime) {
continue
}
match command.action {
Print => runtime.emit(text, line.terminated)
Number => runtime.emit(number.to_string(), true)
Delete => return false
Quit => {
if !quiet {
runtime.emit(text, line.terminated)
}
return true
}
Substitute(reference, replacement, global, occurrence, print) => {
let re = reference.resolve(runtime)
let mut r = 0
while r < replacement.length() {
if replacement[r] == '\\' && r + 1 < replacement.length() {
r += 1
if replacement[r] >= '1' &&
replacement[r] <= '9' &&
replacement[r].to_int() - 48 > re.group_count() {
fail("invalid replacement backreference")
}
}
r += 1
}
let (value, changed) = substitute(
re, text, replacement, global, occurrence,
)
text = value
if print && changed {
runtime.emit(text, line.terminated)
}
}
}
}
if !quiet {
runtime.emit(text, line.terminated)
}
false
}