Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Yarn 2.3+, other enhancements #7

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ Cargo.lock

test_files/*.csv
test_files/*.yarnc
ysc.exe
11 changes: 8 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@ repository = "https://github.com/mystal/yharnam"
csv = "1"
intl_pluralrules = "7"
log = "0.4"
prost = "0.7"
prost = "0.12"
rand = { optional = true, version = "0.8" }
serde = { version = "1", features = ["derive"] }
unic-langid = "0.9"

[build-dependencies]
prost-build = "0.7"
prost-build = "0.12"

[dev-dependencies]
pretty_env_logger = "0.4"
pretty_env_logger = "0.5.0"

[features]
default = ["random"]
random = ["rand"]
23 changes: 21 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
# Yharnam

This is a fork of the original `yharnam` repository which updates the supported
Yarn Spinner version to 2.3, and adds in additional methods and functions.

A Rust implementation of the [Yarn Spinner] runtime. Currently it is only
capable of running pre-compiled Yarn Spinner files. A parser for `.yarn` files
is in progress.

Currently targetting (and based on) Yarn Spinner
[1.2.0](https://github.com/YarnSpinnerTool/YarnSpinner/releases/tag/v1.2.0).
> **NOTE** to run tests you will need to download a copy of
> [`ysc`](https://github.com/YarnSpinnerTool/YarnSpinner-Console), and use it to
> compile each of the test `.yarn` files.

Currently tested on Yarn Spinner
[2.3.0](https://github.com/YarnSpinnerTool/YarnSpinner/releases/tag/v2.3.0).

[Yarn Spinner]: https://yarnspinner.dev/

## Features

### random

Use the `random` feature to enable random operations such as `dice`, `random`
and `random_range`. An additional non-standard function `random_test` is
provided. This takes a threshold between 0-1 and randomly generates a boolean
based on whether a random number is above or below the threshold (see `gen_bool`
from the `rand` crate).

In addition the random generator can be supplied a seed by calling
`vm.seed_random_generator(u64)`. See the random tests for an example.
11 changes: 6 additions & 5 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
fn main() {
prost_build::compile_protos(
&["src/yarn_spinner.proto"],
&["src/"],
).unwrap();
use std::io::Result;

fn main() -> Result<()> {
prost_build::compile_protos(&["src/yarn_spinner.proto"], &["src/"])?;

Ok(())
}
60 changes: 46 additions & 14 deletions src/bin/yarn-run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,49 +15,77 @@ fn main() -> Result<(), Box<dyn Error>> {

// Read first argument as a path to a yarnc file.
args.next();
let proto_path = args.next()
.unwrap();
let proto_path = args.next().unwrap();
let proto_path = PathBuf::from(proto_path);
println!("Opening file at {:?}", proto_path);

let start_node = args.next()
.unwrap_or(DEFAULT_START_NODE_NAME.to_string());
let start_node = args.next().unwrap_or(DEFAULT_START_NODE_NAME.to_string());

// Read the file's bytes and load a Program.
let proto_data = fs::read(&proto_path)?;
let program = Program::decode(&*proto_data)?;
// println!("{:#?}", &program);

// Load LineInfos from a csv file.
let mut csv_path = proto_path;
csv_path.set_extension("csv");
let mut csv_reader = csv::Reader::from_path(csv_path)?;
let string_table: Vec<LineInfo> = csv_reader.deserialize()
let mut lines_csv_path = proto_path.clone();
lines_csv_path.set_file_name(format!(
"{}-Lines.csv",
lines_csv_path.file_stem().unwrap().to_str().unwrap()
));

let string_table: Vec<LineInfo> = csv::Reader::from_path(lines_csv_path)?
.deserialize()
.map(|result| result.unwrap())
.collect();

// Load tags from a csv file.
let mut tags_csv_path = proto_path;
tags_csv_path.set_file_name(format!(
"{}-Metadata.csv",
tags_csv_path.file_stem().unwrap().to_str().unwrap()
));

let tags_table: Vec<MetadataInfo> = csv::ReaderBuilder::new()
.flexible(true)
.from_path(tags_csv_path)?
.deserialize()
.map(|result| result.unwrap())
.collect();

// Run the virtual machine!
let mut vm = VirtualMachine::new(program);
if vm.program.nodes.contains_key(&start_node) {
// Set the start node.
vm.set_node(&start_node);
vm.set_node(&start_node)?;

// Start executing.
loop {
match vm.continue_dialogue() {
match vm.continue_dialogue()? {
SuspendReason::Nop => {}
SuspendReason::Line(line) => {
let text = string_table.iter()
let text = string_table
.iter()
.find(|line_info| line_info.id == line.id)
.map(|line_info| &line_info.text);

let tags = tags_table
.iter()
.find(|metadata_info| metadata_info.id == line.id)
.map(|metadata_info| &metadata_info.tags)
.cloned()
.unwrap_or_default();

if let Some(text) = text {
println!("{}", text);
println!("{text}, tagged {tags:?}");
} else {
// TODO: Could not find line, handle error.
}
}
SuspendReason::Options(options) => {
println!("== Choose option ==");
for (i, opt) in options.iter().enumerate() {
let text = string_table.iter()
let text = string_table
.iter()
.find(|line_info| line_info.id == opt.line.id)
.map(|line_info| &line_info.text);
if let Some(text) = text {
Expand All @@ -71,7 +99,7 @@ fn main() -> Result<(), Box<dyn Error>> {
let mut selection = String::new();
io::stdin().read_line(&mut selection)?;
let selection: u32 = selection.trim().parse()?;
vm.set_selected_option(selection);
vm.set_selected_option(selection)?;
}
SuspendReason::Command(command_text) => {
println!("== Command: {} ==", command_text);
Expand All @@ -85,6 +113,10 @@ fn main() -> Result<(), Box<dyn Error>> {
println!("== Dialogue complete ==");
break;
}
SuspendReason::InvalidOption(option_name) => {
println!("INVALID OPTION: {option_name} is not an option. Please try again");
break;
}
}
}
} else {
Expand Down
31 changes: 31 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use std::error::Error;

#[derive(Debug)]
pub enum VmError {
/// A general logic or configuration error
General(String),
/// A library function doesn't exist in the configured library
MissingLibraryFunction(String),
/// No operation occurred, this may or may not be expected
NoOperation,
}

impl std::fmt::Display for VmError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "A Virtual Machine Error was encountered: {:?}", self)
}
}

impl Error for VmError {}

impl From<String> for VmError {
fn from(message: String) -> Self {
Self::General(message)
}
}

impl From<&str> for VmError {
fn from(message: &str) -> Self {
Self::General(message.to_owned())
}
}
Loading