///|
priv struct BvhParser {
tokens : Array[BvhToken]
mut index : Int
mut channel_cursor : Int
mut error : BvhError
}
///|
fn BvhParser::new(tokens : Array[BvhToken]) -> BvhParser {
{ tokens, index: 0, channel_cursor: 0, error: BvhError::none() }
}
///|
fn BvhParser::is_eof(self : BvhParser) -> Bool {
self.index >= self.tokens.length()
}
///|
fn BvhParser::current(self : BvhParser) -> BvhToken? {
self.tokens.get(self.index)
}
///|
fn BvhParser::peek_text(self : BvhParser) -> String {
match self.current() {
Some(token) => token.text
None => ""
}
}
///|
fn BvhParser::fail(
self : BvhParser,
kind : BvhErrorKind,
message : String,
token? : BvhToken = BvhToken::new(TokenWord, "", 0, 0),
) -> Unit {
if !self.error.is_error() {
self.error = BvhError::new(
kind,
message,
line=token.line,
column=token.column,
token=token.text,
)
}
}
///|
fn BvhParser::fail_eof(
self : BvhParser,
kind : BvhErrorKind,
message : String,
) -> Unit {
if !self.error.is_error() {
let last = if self.tokens.length() > 0 {
self.tokens[self.tokens.length() - 1]
} else {
BvhToken::new(TokenWord, "", 0, 0)
}
self.error = BvhError::new(
kind,
message,
line=last.line,
column=last.column,
token="",
)
}
}
///|
fn BvhParser::accept(self : BvhParser, text : String) -> Bool {
match self.current() {
Some(token) if token.text == text => {
self.index += 1
true
}
_ => false
}
}
///|
fn BvhParser::expect(
self : BvhParser,
text : String,
kind : BvhErrorKind,
) -> Bool {
match self.current() {
Some(token) if token.text == text => {
self.index += 1
true
}
Some(token) => {
self.fail(kind, "expected '\{text}', got '\{token.text}'", token~)
false
}
None => {
self.fail_eof(kind, "expected '\{text}', reached end of file")
false
}
}
}
///|
fn BvhParser::read_word(self : BvhParser, label : String) -> String {
match self.current() {
Some(token) if token.kind == TokenWord => {
self.index += 1
token.text
}
Some(token) => {
self.fail(
ErrorUnexpectedToken,
"expected \{label}, got '\{token.text}'",
token~,
)
""
}
None => {
self.fail_eof(
ErrorUnexpectedEof,
"expected \{label}, reached end of file",
)
""
}
}
}
///|
fn BvhParser::read_int(self : BvhParser, label : String) -> Int {
match self.current() {
Some(token) => {
let value = @string.parse_int(token.text) catch {
_ => {
self.fail(
ErrorInvalidNumber,
"expected integer \{label}, got '\{token.text}'",
token~,
)
return 0
}
}
self.index += 1
value
}
None => {
self.fail_eof(
ErrorUnexpectedEof,
"expected integer \{label}, reached end of file",
)
0
}
}
}
///|
fn BvhParser::read_double(self : BvhParser, label : String) -> Double {
match self.current() {
Some(token) => {
let value = @string.parse_double(token.text) catch {
_ => {
self.fail(
ErrorInvalidNumber,
"expected number \{label}, got '\{token.text}'",
token~,
)
return 0.0
}
}
self.index += 1
value
}
None => {
self.fail_eof(
ErrorUnexpectedEof,
"expected number \{label}, reached end of file",
)
0.0
}
}
}
///|
fn BvhParser::read_offset(self : BvhParser, path : String) -> BvhVec3 {
if !self.expect("OFFSET", ErrorInvalidHierarchy) {
return BvhVec3::zero()
}
BvhVec3::new(
self.read_double("\{path}.offset.x"),
self.read_double("\{path}.offset.y"),
self.read_double("\{path}.offset.z"),
)
}
///|
fn BvhParser::read_channels(
self : BvhParser,
path : String,
) -> (Array[BvhChannel], Int) {
if !self.expect("CHANNELS", ErrorInvalidHierarchy) {
return ([], self.channel_cursor)
}
let count = self.read_int("\{path}.channels.count")
let start = self.channel_cursor
let channels : Array[BvhChannel] = []
if count < 0 {
match self.current() {
Some(token) =>
self.fail(
ErrorInvalidHierarchy,
"channel count cannot be negative",
token~,
)
None =>
self.fail_eof(ErrorUnexpectedEof, "channel count cannot be negative")
}
return (channels, start)
}
for i in 0.. String {
if parent_path == "" {
name
} else {
parent_path + "/" + name
}
}
///|
fn BvhParser::parse_end_site(
self : BvhParser,
parent_path : String,
depth : Int,
) -> BvhEndSite {
if !self.expect("{", ErrorInvalidHierarchy) {
return BvhEndSite::empty()
}
let path = parent_path + "/EndSite"
let offset = self.read_offset(path)
ignore(self.expect("}", ErrorInvalidHierarchy))
BvhEndSite::new(path, offset, depth)
}
///|
fn BvhParser::parse_joint_node(
self : BvhParser,
kind : BvhJointKind,
parent_path : String,
depth : Int,
) -> BvhJoint {
let name = self.read_word("joint name")
let path = child_path(parent_path, name)
if !self.expect("{", ErrorInvalidHierarchy) {
return BvhJoint::empty()
}
let offset = self.read_offset(path)
let (channels, start) = self.read_channels(path)
let children : Array[BvhJoint] = []
let end_sites : Array[BvhEndSite] = []
while !self.is_eof() && self.peek_text() != "}" && !self.error.is_error() {
if self.accept("JOINT") {
children.push(self.parse_joint_node(JointRegular, path, depth + 1))
} else if self.accept("End") {
if self.expect("Site", ErrorInvalidHierarchy) {
end_sites.push(self.parse_end_site(path, depth + 1))
}
} else {
match self.current() {
Some(token) =>
self.fail(
ErrorUnexpectedToken,
"expected JOINT, End Site, or closing brace inside hierarchy",
token~,
)
None =>
self.fail_eof(
ErrorUnexpectedEof,
"expected hierarchy item or closing brace",
)
}
}
}
ignore(self.expect("}", ErrorInvalidHierarchy))
{
name,
path,
kind,
offset,
channels,
channel_start: start,
channel_count: channels.length(),
depth,
children,
end_sites,
}
}
///|
fn BvhParser::parse_motion(self : BvhParser) -> BvhMotion {
if !self.expect("MOTION", ErrorInvalidMotion) {
return BvhMotion::empty()
}
if !self.expect("Frames:", ErrorInvalidMotion) {
return BvhMotion::empty()
}
let frame_count = self.read_int("motion frame count")
if !self.expect("Frame", ErrorInvalidMotion) {
return BvhMotion::empty()
}
if !self.expect("Time:", ErrorInvalidMotion) {
return BvhMotion::empty()
}
let frame_time = self.read_double("motion frame time")
if frame_count < 0 {
self.fail_eof(ErrorInvalidMotion, "frame count cannot be negative")
return BvhMotion::empty()
}
let frames : Array[Array[Double]] = []
for frame_index in 0.. BvhDocument {
if !self.expect("HIERARCHY", ErrorInvalidHierarchy) {
return BvhDocument::empty()
}
if !self.expect("ROOT", ErrorInvalidHierarchy) {
return BvhDocument::empty()
}
let root = self.parse_joint_node(JointRoot, "", 0)
let motion = self.parse_motion()
{ root, motion, source_length, token_count: self.tokens.length() }
}
///|
/// Parse ASCII BVH source into hierarchy and motion data.
pub fn parse_bvh(input : String) -> BvhParseResult {
let tokenized = tokenize_bvh(input)
if !tokenized.ok {
BvhParseResult::failure(tokenized.error)
} else {
let parser = BvhParser::new(tokenized.tokens)
let document = parser.parse_document(input.length())
if parser.error.is_error() {
BvhParseResult::failure(parser.error)
} else if !parser.is_eof() {
match parser.current() {
Some(token) =>
BvhParseResult::failure(
BvhError::new(
ErrorUnexpectedToken,
"unexpected trailing token '\{token.text}'",
line=token.line,
column=token.column,
token=token.text,
),
)
None => BvhParseResult::success(document)
}
} else {
BvhParseResult::success(document)
}
}
}
///|
pub fn parse_bvh_or_empty(input : String) -> BvhDocument {
let parsed = parse_bvh(input)
if parsed.ok {
parsed.document
} else {
BvhDocument::empty()
}
}