#![cfg_attr(not(test), no_std)]
#![deny(missing_docs)]
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub enum Error {
FileNotFound,
BadFileHandle,
IOError,
NotSupported,
Unknown = 0xFFFF,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Handle(pub u16);
#[repr(C)]
#[derive(Debug, Clone, Eq)]
pub struct BorrowedString {
pub ptr: *const u8,
pub length: usize,
}
impl BorrowedString {
pub fn new(value: &'static str) -> BorrowedString {
BorrowedString {
ptr: value.as_ptr(),
length: value.len(),
}
}
}
impl core::cmp::PartialEq for BorrowedString {
fn eq(&self, rhs: &BorrowedString) -> bool {
if self.length == rhs.length {
let left = unsafe { core::slice::from_raw_parts(self.ptr, self.length) };
let right = unsafe { core::slice::from_raw_parts(rhs.ptr, rhs.length) };
left == right
} else {
false
}
}
}
#[repr(C)]
#[derive(Debug)]
pub enum HandleResult {
Ok(Handle),
Error(Error),
}
#[repr(C)]
#[derive(Debug)]
pub enum EmptyResult {
Ok,
Error(Error),
}
#[repr(C)]
#[derive(Debug)]
pub enum SizeResult {
Ok(usize),
Error(Error),
}
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum FileType {
File,
Directory,
BlockDevice,
CharDevice,
}
#[repr(C)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Timestamp {
pub year_from_1970: u8,
pub month: u8,
pub days: u8,
pub hours: u8,
pub minutes: u8,
pub seconds: u8,
}
#[repr(C)]
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum DayOfWeek {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday,
}
impl DayOfWeek {
pub fn day_str(&self) -> &'static str {
match self {
DayOfWeek::Monday => "Monday",
DayOfWeek::Tuesday => "Tuesday",
DayOfWeek::Wednesday => "Wednesday",
DayOfWeek::Thursday => "Thursday",
DayOfWeek::Friday => "Friday",
DayOfWeek::Saturday => "Saturday",
DayOfWeek::Sunday => "Sunday",
}
}
}
impl Timestamp {
pub fn day_of_week(&self) -> DayOfWeek {
let zellers_month = ((i32::from(self.month) + 9) % 12) + 1;
let k = i32::from(self.days);
let year = if zellers_month >= 11 {
i32::from(self.year_from_1970) + 1969
} else {
i32::from(self.year_from_1970) + 1970
};
let d = year % 100;
let c = year / 100;
let f = k + (((13 * zellers_month) - 1) / 5) + d + (d / 4) + (c / 4) - (2 * c);
let day_of_week = f % 7;
match day_of_week {
0 => DayOfWeek::Sunday,
1 => DayOfWeek::Monday,
2 => DayOfWeek::Tuesday,
3 => DayOfWeek::Wednesday,
4 => DayOfWeek::Thursday,
5 => DayOfWeek::Friday,
_ => DayOfWeek::Saturday,
}
}
pub fn increment(&mut self, days: u32, seconds: u32) {
let new_seconds = seconds + u32::from(self.seconds);
self.seconds = (new_seconds % 60) as u8;
let new_minutes = (new_seconds / 60) + u32::from(self.minutes);
self.minutes = (new_minutes % 60) as u8;
let new_hours = (new_minutes / 60) + u32::from(self.hours);
self.hours = (new_hours % 24) as u8;
let mut new_days = (new_hours / 24) + u32::from(self.days) + days;
while new_days > u32::from(self.days_in_month()) {
new_days -= u32::from(self.days_in_month());
self.month += 1;
if self.month > 12 {
self.month = 1;
self.year_from_1970 += 1;
}
}
self.days = new_days as u8;
}
pub fn is_leap_year(&self) -> bool {
let year = u32::from(self.year_from_1970) + 1970;
(year == 2000) || (((year % 4) == 0) && ((year % 100) != 0))
}
pub fn days_in_month(&self) -> u8 {
match self.month {
1 => 31,
2 if self.is_leap_year() => 29,
2 => 28,
3 => 31,
4 => 30,
5 => 31,
6 => 30,
7 => 31,
8 => 31,
9 => 30,
10 => 31,
11 => 30,
12 => 31,
_ => panic!("Bad timestamp {:?}", self),
}
}
pub fn month_str(&self) -> &'static str {
match self.month {
1 => "January",
2 => "February",
3 => "March",
4 => "April",
5 => "May",
6 => "June",
7 => "July",
8 => "August",
9 => "September",
10 => "October",
11 => "November",
12 => "December",
_ => "Unknown",
}
}
}
impl core::fmt::Display for Timestamp {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(
f,
"{year:04}-{month:02}-{days:02}T{hours:02}:{minutes:02}:{seconds:02}",
year = u16::from(self.year_from_1970) + 1970u16,
month = self.month,
days = self.days,
hours = self.hours,
minutes = self.minutes,
seconds = self.seconds,
)
}
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct DirEntry {
pub file_type: FileType,
pub name: [u8; 11],
pub size: u32,
pub mtime: Timestamp,
pub ctime: Timestamp,
pub mode: FileMode,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct FileMode(u8);
#[no_mangle]
pub extern "C" fn monotron_filemode_is_readonly(flags: FileMode) -> bool {
(flags.0 & FileMode::READ_ONLY) != 0
}
#[no_mangle]
pub extern "C" fn monotron_filemode_is_volume(flags: FileMode) -> bool {
(flags.0 & FileMode::VOLUME) != 0
}
#[no_mangle]
pub extern "C" fn monotron_filemode_is_system(flags: FileMode) -> bool {
(flags.0 & FileMode::SYSTEM) != 0
}
#[no_mangle]
pub extern "C" fn monotron_filemode_is_archive(flags: FileMode) -> bool {
(flags.0 & FileMode::ARCHIVE) != 0
}
impl FileMode {
const READ_ONLY: u8 = 1;
const VOLUME: u8 = 2;
const SYSTEM: u8 = 4;
const ARCHIVE: u8 = 8;
}
#[repr(C)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Offset {
FromStart(u32),
FromCurrent(i32),
FromEnd(u32),
}
#[repr(C)]
pub enum OpenMode {
ReadOnly {
non_blocking: bool,
},
WriteOnly {
append: bool,
create: bool,
exclusive: bool,
truncate: bool,
non_blocking: bool,
},
ReadWrite {
append: bool,
create: bool,
exclusive: bool,
truncate: bool,
non_blocking: bool,
},
}
#[no_mangle]
pub extern "C" fn monotron_openmode_readonly(non_blocking: bool) -> OpenMode {
OpenMode::ReadOnly { non_blocking }
}
#[no_mangle]
pub extern "C" fn monotron_openmode_writeonly(
append: bool,
create: bool,
exclusive: bool,
truncate: bool,
non_blocking: bool,
) -> OpenMode {
OpenMode::WriteOnly {
append,
create,
exclusive,
truncate,
non_blocking,
}
}
#[no_mangle]
pub extern "C" fn monotron_openmode_readwrite(
append: bool,
create: bool,
exclusive: bool,
truncate: bool,
non_blocking: bool,
) -> OpenMode {
OpenMode::ReadWrite {
append,
create,
exclusive,
truncate,
non_blocking,
}
}
pub static STDOUT: Handle = Handle(0);
pub static STDERR: Handle = Handle(1);
pub static STDIN: Handle = Handle(2);
#[repr(C)]
pub struct Api {
pub putchar: extern "C" fn(ch: u8) -> i32,
pub puts: extern "C" fn(string: *const u8) -> i32,
pub readc: extern "C" fn() -> i32,
pub kbhit: extern "C" fn() -> i32,
pub move_cursor: extern "C" fn(row: u8, col: u8),
pub play: extern "C" fn(frequency: u32, channel: u8, volume: u8, waveform: u8) -> i32,
pub change_font: extern "C" fn(font_id: u32, font_data: *const u8),
pub get_joystick: extern "C" fn() -> u8,
pub set_cursor_visible: extern "C" fn(enabled: u8),
pub read_char_at: extern "C" fn(row: u8, col: u8) -> u16,
pub wfvbi: extern "C" fn(),
pub open: extern "C" fn(filename: BorrowedString, mode: OpenMode) -> HandleResult,
pub close: extern "C" fn(handle: Handle) -> EmptyResult,
pub read: extern "C" fn(handle: Handle, buffer: *mut u8, buffer_len: usize) -> SizeResult,
pub write: extern "C" fn(handle: Handle, buffer: *const u8, buffer_len: usize) -> SizeResult,
pub write_then_read: extern "C" fn(
handle: Handle,
out_buffer: *const u8,
out_buffer_len: usize,
in_buffer: *mut u8,
in_buffer_len: usize,
) -> SizeResult,
pub seek: extern "C" fn(handle: Handle, offset: Offset) -> EmptyResult,
pub opendir: extern "C" fn(filename: BorrowedString) -> HandleResult,
pub readdir: extern "C" fn(handle: Handle, dir_entry: &mut DirEntry) -> EmptyResult,
pub stat: extern "C" fn(filename: BorrowedString, stat_entry: &mut DirEntry) -> EmptyResult,
pub gettime: extern "C" fn() -> Timestamp,
pub puts_utf8: extern "C" fn(string: *const u8, length: usize),
pub map_line: extern "C" fn(actual_scanline: u16, drawn_scanline: u16),
pub get_cursor: extern "C" fn(row: *mut u8, col: *mut u8),
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn day_of_week() {
let samples = [
(
Timestamp {
year_from_1970: 49,
month: 7,
day: 16,
hours: 0,
minutes: 0,
seconds: 0,
},
DayOfWeek::Tuesday,
),
(
Timestamp {
year_from_1970: 49,
month: 7,
day: 17,
hours: 0,
minutes: 0,
seconds: 0,
},
DayOfWeek::Wednesday,
),
(
Timestamp {
year_from_1970: 49,
month: 7,
day: 18,
hours: 0,
minutes: 0,
seconds: 0,
},
DayOfWeek::Thursday,
),
];
for (timestamp, day) in samples.iter() {
assert_eq!(timestamp.day_of_week(), *day);
}
}
}