///|
priv struct Parser {
toks : Array[Tok]
mut i : Int
file : String
}
///|
fn parser_eof(p : Parser) -> Bool {
p.i >= p.toks.length()
}
///|
fn parser_peek(p : Parser) -> Tok? {
if parser_eof(p) {
None
} else {
Some(p.toks[p.i])
}
}
///|
fn parser_bump(p : Parser) -> Unit {
if !parser_eof(p) {
p.i += 1
}
}
///|
pub fn parse_conf(text : String) -> Result[Config, NgxError] {
parse_conf_named("nginx.conf", text)
}
///|
pub fn parse_conf_named(
file : String,
text : String,
) -> Result[Config, NgxError] {
match lex_conf(text) {
Err(e) => Err(e)
Ok(toks) => {
let p : Parser = { toks, i: 0, file, }
match parse_stmts(p, false) {
Err(e) => Err(e)
Ok(parsed) =>
if !parser_eof(p) {
match parser_peek(p) {
Some(t) =>
match t.kind {
RBrace => Err(Unexpected(line_msg(t.line, "unexpected }")))
_ =>
Err(
Syntax(
line_msg(t.line, p.file + " trailing token " + t.value),
),
)
}
None => Ok(config_ok(file, parsed))
}
} else {
Ok(config_ok(file, parsed))
}
}
}
}
}
///|
fn parse_stmts(
p : Parser,
in_block : Bool,
) -> Result[Array[Directive], NgxError] {
let out : Array[Directive] = []
while !parser_eof(p) {
match parser_peek(p) {
None => break
Some(t) =>
match t.kind {
RBrace =>
if in_block {
parser_bump(p)
return Ok(out)
} else {
return Err(Unexpected(line_msg(t.line, "unexpected }")))
}
Comment => {
out.push(dir_comment(t.value, t.line))
parser_bump(p)
}
LBrace => return Err(Unexpected(line_msg(t.line, "unexpected {")))
Semi => return Err(Unexpected(line_msg(t.line, "unexpected ;")))
Quoted =>
return Err(
Syntax(line_msg(t.line, "quoted string cannot start a directive")),
)
Word =>
match parse_stmt(p) {
Ok(d) => out.push(d)
Err(e) => return Err(e)
}
}
}
}
if in_block {
Err(Unterminated(p.file + ": unexpected end of file, expecting }"))
} else {
Ok(out)
}
}
///|
fn parse_stmt(p : Parser) -> Result[Directive, NgxError] {
match parser_peek(p) {
None => Err(Syntax("premature end of file"))
Some(head) => {
let name = head.value
let line = head.line
parser_bump(p)
let args : Array[String] = []
let quoted : Array[Bool] = []
while !parser_eof(p) {
match parser_peek(p) {
None => break
Some(t) =>
if tok_is_arg(t) {
args.push(t.value)
quoted.push(
match t.kind {
Quoted => true
_ => false
},
)
parser_bump(p)
} else {
break
}
}
}
if parser_eof(p) {
return Err(Unterminated(line_msg(line, "premature end of file")))
}
match parser_peek(p) {
None => Err(Unterminated(line_msg(line, "premature end of file")))
Some(term) =>
match term.kind {
Semi => {
parser_bump(p)
let d = finish_stmt(name, line, args, quoted, [], false)
Ok(d)
}
LBrace => {
parser_bump(p)
match parse_stmts(p, true) {
Err(e) => Err(e)
Ok(kids) =>
Ok(finish_stmt(name, line, args, quoted, kids, true))
}
}
RBrace =>
Err(
Syntax(
line_msg(
line,
"directive " + name + " is not terminated by ;",
),
),
)
_ => Err(Unexpected(line_msg(term.line, "token " + term.value)))
}
}
}
}
}
///|
fn finish_stmt(
name : String,
line : Int,
args : Array[String],
quoted : Array[Bool],
children : Array[Directive],
has_block : Bool,
) -> Directive {
let (a2, q2) = if name == "if" {
strip_if_parens(args, quoted)
} else {
(args, quoted)
}
{ name, line, args: a2, quoted: q2, children, has_block, comment: None, }
}
///|
fn strip_if_parens(
args : Array[String],
quoted : Array[Bool],
) -> (Array[String], Array[Bool]) {
if args.length() == 0 {
return (args, quoted)
}
let first = args[0]
let last = args[args.length() - 1]
if !(first.has_prefix("(") && last.has_suffix(")")) {
return (args, quoted)
}
let out_a : Array[String] = []
let out_q : Array[Bool] = []
let mut i = 0
while i < args.length() {
let mut s = args[i]
if i == 0 {
s = slice_text(s, 1, s.length())
}
if i == args.length() - 1 {
if s.length() > 0 {
s = slice_text(s, 0, s.length() - 1)
}
}
if s.length() > 0 {
out_a.push(s)
out_q.push(quoted[i])
}
i = i + 1
}
(out_a, out_q)
}