///|
#cfg(target="native")
#external
type FileHandle
///|
#cfg(target="native")
#borrow(path, mode)
extern "C" fn file_open(path : Bytes, mode : Bytes) -> FileHandle = "moonbit_fopen_ffi"
///|
#cfg(target="native")
extern "C" fn is_null_file(file : FileHandle) -> Bool = "moonbit_is_null"
///|
#cfg(target="native")
extern "C" fn file_seek(file : FileHandle, offset : Int, whence : Int) -> Int = "moonbit_fseek_ffi"
///|
#cfg(target="native")
extern "C" fn file_tell(file : FileHandle) -> Int = "moonbit_ftell_ffi"
///|
#cfg(target="native")
#borrow(buffer, file)
extern "C" fn file_read(
buffer : Bytes,
size : Int,
nitems : Int,
file : FileHandle,
) -> Int = "moonbit_fread_ffi"
///|
#cfg(target="native")
#borrow(buffer, file)
extern "C" fn file_write(
buffer : Bytes,
size : Int,
nitems : Int,
file : FileHandle,
) -> Int = "moonbit_fwrite_ffi"
///|
#cfg(target="native")
extern "C" fn file_close(file : FileHandle) -> Int = "moonbit_fclose_ffi"
///|
#cfg(target="native")
fn c_string(text : String) -> Bytes {
let bytes = @utf8.encode(text).to_array()
bytes.push(b'\x00')
Bytes::from_array(bytes)
}
///|
#cfg(target="native")
fn read_text_file(path : String) -> String? {
let file = file_open(c_string(path), c_string("rb"))
if is_null_file(file) {
return None
}
if file_seek(file, 0, 2) != 0 {
ignore(file_close(file))
return None
}
let size = file_tell(file)
if size < 0 || file_seek(file, 0, 0) != 0 {
ignore(file_close(file))
return None
}
let buffer = Bytes::new(size)
let read = file_read(buffer, 1, size, file)
ignore(file_close(file))
if read == size {
Some(@utf8.decode_lossy(buffer, ignore_bom=true))
} else {
None
}
}
///|
#cfg(target="native")
fn write_text_file(path : String, text : String) -> Bool {
let file = file_open(c_string(path), c_string("wb"))
if is_null_file(file) {
return false
}
let buffer = @utf8.encode(text)
let written = file_write(buffer, 1, buffer.length(), file)
ignore(file_close(file))
written == buffer.length()
}
///|
#cfg(not(target="native"))
fn read_text_file(path : String) -> String? {
let _ = path
None
}
///|
#cfg(not(target="native"))
fn write_text_file(path : String, text : String) -> Bool {
let _ = path
let _ = text
false
}
///|
fn status_name(result : @core.PathResult) -> String {
if result.status is @core.Found {
"found"
} else if result.status is @core.NoPath {
"no_path"
} else {
"invalid_input"
}
}
///|
fn movement_name(movement : @grid.MovementMode) -> String {
match movement {
@grid.FourWay => "four_way"
@grid.EightWay => "eight_way"
}
}
///|
fn heuristic_for(movement : @grid.MovementMode) -> @heuristics.HeuristicKind {
match movement {
@grid.FourWay => @heuristics.Manhattan
@grid.EightWay => @heuristics.Octile
}
}
///|
fn print_result(
source : String,
movement : @grid.MovementMode,
result : @core.PathResult,
) -> Unit {
println("Moon PathPlanning A* demo")
println("source: \{source}")
println("movement: \{movement_name(movement)}")
println("status: \{status_name(result)}")
println("path nodes: \{result.path.length()}")
println("total cost: \{result.total_cost}")
println("visited nodes: \{result.visited_nodes}")
println("expanded nodes: \{result.expanded_nodes}")
}
///|
fn export_html(
source : String,
map : @grid.GridMap,
result : @core.PathResult,
html_path : String?,
) -> Bool {
match html_path {
None => true
Some(path) => {
let html = @svg.grid_to_html(
"Moon PathPlanning: \{source}",
map,
result.path,
24,
)
if write_text_file(path, html) {
println("html output: \{path}")
true
} else {
println("Could not write HTML file: \{path}")
false
}
}
}
}
///|
fn demo_map() -> @grid.GridMap {
@grid.new_grid(6, 5, @core.point(0, 0), @core.point(5, 4)).with_obstacles([
@core.point(1, 1),
@core.point(1, 2),
@core.point(2, 2),
@core.point(4, 3),
])
}
///|
fn run_demo(html_path : String?) -> Bool {
let movement = @grid.FourWay
let map = demo_map()
let options = @planner.options(movement, heuristic_for(movement))
let result = @planner.plan(map, @planner.AStar, options)
print_result("builtin-demo", movement, result)
export_html("builtin-demo", map, result, html_path)
}
///|
fn run_json_text(source : String, text : String, html_path : String?) -> Bool {
match @json_map.grid_from_json(text) {
Some(data) => {
let options = @planner.options(
data.movement,
heuristic_for(data.movement),
)
let result = @planner.plan(data.map, @planner.AStar, options)
print_result(source, data.movement, result)
export_html(source, data.map, result, html_path)
}
None => {
println("Invalid grid JSON: \{source}")
false
}
}
}
///|
fn run_json_file(path : String, html_path : String?) -> Bool {
match read_text_file(path) {
Some(text) => run_json_text(path, text, html_path)
None => {
println("Could not read JSON file: \{path}")
false
}
}
}
///|
fn run_example(name : String, html_path : String?) -> Bool {
match @json_map.example_json(name) {
Some(text) => run_json_text("example:\{name}", text, html_path)
None => {
println("Unknown example map: \{name}")
false
}
}
}
///|
fn usage() -> Unit {
println("Usage:")
println(" moon run cli")
println(" moon run cli -- --json ")
println(" moon run cli -- --example ")
println(" moon run cli --target native -- ")
println(" moon run cli --target native -- --map ")
println(
" moon run cli --target native -- --example --html ",
)
}
///|
fn json_arg(args : Array[String]) -> String? {
let mut index = 1
while index < args.length() {
match args[index] {
"--help" | "-h" => return Some("")
"--json" | "-j" =>
if index + 1 < args.length() {
return Some(args[index + 1])
} else {
return Some("")
}
_ => ()
}
index = index + 1
}
None
}
///|
fn example_arg(args : Array[String]) -> String? {
let mut index = 1
while index < args.length() {
match args[index] {
"--help" | "-h" => return Some("")
"--example" | "-e" =>
if index + 1 < args.length() {
return Some(args[index + 1])
} else {
return Some("")
}
_ => ()
}
index = index + 1
}
None
}
///|
fn html_arg(args : Array[String]) -> String? {
let mut index = 1
while index < args.length() {
match args[index] {
"--help" | "-h" => return Some("")
"--html" | "-o" =>
if index + 1 < args.length() {
return Some(args[index + 1])
} else {
return Some("")
}
_ => ()
}
index = index + 1
}
None
}
///|
fn map_arg(args : Array[String]) -> String? {
let mut index = 1
let mut path : String? = None
while index < args.length() {
match args[index] {
"--help" | "-h" => return Some("")
"--map" | "-m" =>
if index + 1 < args.length() {
path = Some(args[index + 1])
index = index + 1
} else {
return Some("")
}
"--json" | "-j" | "--example" | "-e" | "--html" | "-o" =>
if index + 1 < args.length() {
index = index + 1
} else {
return Some("")
}
value => if path is None { path = Some(value) }
}
index = index + 1
}
path
}
///|
fn main {
let args = @env.args()
let html_path = html_arg(args)
match json_arg(args) {
Some("") => usage()
Some(text) => if !run_json_text("inline-json", text, html_path) { usage() }
None =>
match example_arg(args) {
Some("") => usage()
Some(name) => if !run_example(name, html_path) { usage() }
None =>
match map_arg(args) {
Some("") => usage()
Some(path) => if !run_json_file(path, html_path) { usage() }
None => if !run_demo(html_path) { usage() }
}
}
}
}