stdio: get/put + most of **scanf()
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
use libyalloc::GlobalAllocator;
|
||||
|
||||
#[global_allocator]
|
||||
pub static GLOBAL_ALLOCATOR: GlobalAllocator = GlobalAllocator;
|
||||
@@ -1 +0,0 @@
|
||||
pub mod rust;
|
||||
@@ -1,4 +0,0 @@
|
||||
use libyalloc::GlobalAllocator;
|
||||
|
||||
#[global_allocator]
|
||||
static GLOBAL_ALLOCATOR: GlobalAllocator = GlobalAllocator;
|
||||
@@ -126,3 +126,9 @@ impl<T> OptionExt<T> for Option<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_errno(e: Errno) {
|
||||
unsafe {
|
||||
errno = e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ use core::{
|
||||
ptr::null_mut,
|
||||
};
|
||||
|
||||
use crate::io::{Read, Write};
|
||||
use crate::{
|
||||
header::stdlib::realloc,
|
||||
io::{Read, Write},
|
||||
};
|
||||
|
||||
use super::{stdin, stdout, EOF, FILE};
|
||||
|
||||
@@ -132,6 +135,32 @@ unsafe extern "C" fn ungetc(c: c_int, stream: *mut FILE) -> c_int {
|
||||
|
||||
// Line
|
||||
|
||||
struct MallocBufferWriter {
|
||||
buffer: *mut c_char,
|
||||
position: usize,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
impl MallocBufferWriter {
|
||||
fn putc(&mut self, ch: c_int) -> bool {
|
||||
if self.position == self.capacity {
|
||||
// Extend the buffer
|
||||
self.capacity = (self.capacity + 64) & !63;
|
||||
self.buffer = unsafe { realloc(self.buffer as _, self.capacity) as _ };
|
||||
if self.buffer.is_null() {
|
||||
// ENOMEM is set
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
*self.buffer.add(self.position) = ch as _;
|
||||
}
|
||||
self.position += 1;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn getdelim(
|
||||
lineptr: *mut *mut c_char,
|
||||
@@ -139,10 +168,51 @@ unsafe extern "C" fn getdelim(
|
||||
delim: c_int,
|
||||
stream: *mut FILE,
|
||||
) -> isize {
|
||||
todo!()
|
||||
let lineptr = lineptr.as_mut().unwrap();
|
||||
let n = n.as_mut().unwrap();
|
||||
let mut capacity = *n;
|
||||
|
||||
if lineptr.is_null() {
|
||||
capacity = 0;
|
||||
}
|
||||
|
||||
let mut writer = MallocBufferWriter {
|
||||
buffer: *lineptr,
|
||||
position: 0,
|
||||
capacity,
|
||||
};
|
||||
|
||||
loop {
|
||||
let ch = fgetc(stream);
|
||||
if ch == EOF {
|
||||
if writer.position == 0 {
|
||||
// EOF and no data read
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if !writer.putc(ch) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ch == delim {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !writer.putc(0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
*lineptr = writer.buffer;
|
||||
*n = writer.capacity;
|
||||
|
||||
// Minus the '\0'
|
||||
(writer.position - 1).try_into().unwrap()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn getline(lineptr: *mut *mut c_char, n: *mut usize, stream: *mut FILE) -> isize {
|
||||
todo!()
|
||||
getdelim(lineptr, n, b'\n' as _, stream)
|
||||
}
|
||||
|
||||
@@ -58,10 +58,6 @@ char *tempnam(const char *, const char *);
|
||||
FILE *tmpfile(void);
|
||||
char *tmpnam(char *);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
*/
|
||||
|
||||
macro_rules! locked_op {
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
int fscanf(FILE *restrict, const char *restrict, ...);
|
||||
int scanf(const char *restrict, ...);
|
||||
int sscanf(const char *restrict, const char *restrict, ...);
|
||||
int vfscanf(FILE *restrict, const char *restrict, va_list);
|
||||
int vscanf(const char *restrict, va_list);
|
||||
int vsscanf(const char *restrict, const char *restrict, va_list);
|
||||
*/
|
||||
|
||||
use core::ffi::{c_char, c_int, VaList};
|
||||
|
||||
use super::{stdin, FILE};
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn fscanf(stream: *mut FILE, format: *const c_char, mut args: ...) -> c_int {
|
||||
vfscanf(stream, format, args.as_va_list())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn scanf(format: *const c_char, mut args: ...) -> c_int {
|
||||
vfscanf(stdin, format, args.as_va_list())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn sscanf(str: *const c_char, format: *const c_char, mut args: ...) -> c_int {
|
||||
vsscanf(str, format, args.as_va_list())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn vfscanf(stream: *mut FILE, format: *const c_char, ap: VaList) -> c_int {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn vscanf(format: *const c_char, ap: VaList) -> c_int {
|
||||
vfscanf(stdin, format, ap)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn vsscanf(str: *const c_char, format: *const c_char, ap: VaList) -> c_int {
|
||||
todo!()
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
use core::ops::RangeInclusive;
|
||||
|
||||
use super::ScanError;
|
||||
|
||||
pub struct ScanCharSet {
|
||||
mask: [u64; 4],
|
||||
invert: bool,
|
||||
}
|
||||
|
||||
impl ScanCharSet {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
mask: [0; 4],
|
||||
invert: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_format_iter<'a, I: Iterator<Item = &'a u8>>(
|
||||
mut it: I,
|
||||
) -> Result<(I, Self), ScanError> {
|
||||
let mut maybe_close_bracket = true;
|
||||
let mut hyphen = false;
|
||||
let mut start: Option<u8> = None;
|
||||
let mut set = ScanCharSet::new();
|
||||
|
||||
while let Some(&ch) = it.next() {
|
||||
if ch == b'^' && maybe_close_bracket {
|
||||
// "[^...]"
|
||||
set.set_invert();
|
||||
continue;
|
||||
}
|
||||
|
||||
if ch == b']' {
|
||||
if !maybe_close_bracket {
|
||||
if hyphen {
|
||||
// "[...-]"
|
||||
set.insert(b'-');
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
debug_assert!(!hyphen);
|
||||
// "[]...]" or "[^]...]"
|
||||
maybe_close_bracket = false;
|
||||
set.insert(b']');
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
maybe_close_bracket = false;
|
||||
|
||||
if ch == b'-' {
|
||||
if hyphen {
|
||||
// "--"?
|
||||
return Err(ScanError::InvalidFormat);
|
||||
}
|
||||
|
||||
hyphen = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Any other character
|
||||
if hyphen {
|
||||
let Some(start) = start.take() else {
|
||||
// "-x" without start?
|
||||
return Err(ScanError::InvalidFormat);
|
||||
};
|
||||
|
||||
let same_category = (start.is_ascii_digit() && ch.is_ascii_digit())
|
||||
|| (start.is_ascii_uppercase() && ch.is_ascii_uppercase())
|
||||
|| (start.is_ascii_lowercase() && ch.is_ascii_lowercase());
|
||||
|
||||
if same_category && ch > start {
|
||||
hyphen = false;
|
||||
set.insert_range(start..=ch);
|
||||
} else {
|
||||
return Err(ScanError::InvalidFormat);
|
||||
}
|
||||
} else {
|
||||
if let Some(start) = start.replace(ch) {
|
||||
// "c" without range
|
||||
set.insert(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((it, set))
|
||||
}
|
||||
|
||||
pub fn set_invert(&mut self) {
|
||||
self.invert = true;
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, ch: u8) {
|
||||
let index = (ch / 64) as usize;
|
||||
let bit = 1 << (ch % 64);
|
||||
self.mask[index] |= bit;
|
||||
}
|
||||
|
||||
pub fn insert_range(&mut self, range: RangeInclusive<u8>) {
|
||||
for ch in range {
|
||||
self.insert(ch);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains(&self, ch: u8) -> bool {
|
||||
let index = (ch / 64) as usize;
|
||||
let bit = 1 << (ch % 64);
|
||||
self.mask[index] & bit == bit
|
||||
}
|
||||
|
||||
pub fn check(&self, ch: u8) -> bool {
|
||||
self.invert ^ self.contains(ch)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,434 @@
|
||||
use core::ffi::{
|
||||
c_char, c_int, c_long, c_longlong, c_short, c_uchar, c_uint, c_ulong, c_ulonglong, c_ushort,
|
||||
c_void, VaList,
|
||||
};
|
||||
|
||||
use super::{
|
||||
char_set::ScanCharSet,
|
||||
reader::{GetChar, ScanReader},
|
||||
ConversionError,
|
||||
};
|
||||
|
||||
pub enum ScanRadix {
|
||||
Decimal,
|
||||
Oct,
|
||||
Hex,
|
||||
Auto,
|
||||
}
|
||||
|
||||
pub enum ScanSpec {
|
||||
// %s, a sequence of non-whitespace characters
|
||||
Word,
|
||||
// %c, a sequence of characters whose length is specified by field width (default 1)
|
||||
Chars(ScanCharSet),
|
||||
// %[...], nonempty sequence of characters from the specified set
|
||||
AnyChar,
|
||||
// %d, %u, %x/%X, %o, %i, deprecated, optionally-signed/unsigned decimals
|
||||
Signed(ScanRadix),
|
||||
Unsigned(ScanRadix),
|
||||
// %n, reports consumed count
|
||||
ConsumedCount,
|
||||
// %p
|
||||
Pointer,
|
||||
// %%
|
||||
Percent,
|
||||
}
|
||||
|
||||
pub enum ScanSize {
|
||||
ShortShort,
|
||||
Short,
|
||||
Normal,
|
||||
Long,
|
||||
LongLong,
|
||||
Size,
|
||||
}
|
||||
|
||||
pub struct ScanOpt {
|
||||
pub width: Option<usize>,
|
||||
pub size: ScanSize,
|
||||
pub discard: bool,
|
||||
}
|
||||
|
||||
impl ScanSize {
|
||||
pub fn shorter(self) -> ScanSize {
|
||||
match self {
|
||||
Self::Normal => Self::Short,
|
||||
Self::Short => Self::ShortShort,
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn longer(self) -> ScanSize {
|
||||
match self {
|
||||
Self::Normal => Self::Long,
|
||||
Self::Long => Self::LongLong,
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn write_int(&self, value: i64, ap: &mut VaList) {
|
||||
let raw = ap.arg::<*mut c_void>();
|
||||
|
||||
if raw.is_null() {
|
||||
panic!();
|
||||
}
|
||||
|
||||
match self {
|
||||
Self::ShortShort => *(raw as *mut c_char) = value as _,
|
||||
Self::Short => *(raw as *mut c_short) = value as _,
|
||||
Self::Normal => *(raw as *mut c_int) = value as _,
|
||||
Self::Long => *(raw as *mut c_long) = value as _,
|
||||
Self::LongLong => *(raw as *mut c_longlong) = value as _,
|
||||
Self::Size => *(raw as *mut isize) = value as _,
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn write_uint(&self, value: u64, ap: &mut VaList) {
|
||||
let raw = ap.arg::<*mut c_void>();
|
||||
|
||||
if raw.is_null() {
|
||||
panic!();
|
||||
}
|
||||
|
||||
match self {
|
||||
Self::ShortShort => *(raw as *mut c_uchar) = value as _,
|
||||
Self::Short => *(raw as *mut c_ushort) = value as _,
|
||||
Self::Normal => *(raw as *mut c_uint) = value as _,
|
||||
Self::Long => *(raw as *mut c_ulong) = value as _,
|
||||
Self::LongLong => *(raw as *mut c_ulonglong) = value as _,
|
||||
Self::Size => *(raw as *mut usize) = value as _,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ScanOpt {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
width: None,
|
||||
size: ScanSize::Normal,
|
||||
discard: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ScanOpt {
|
||||
pub fn scan<G: GetChar>(
|
||||
self,
|
||||
stream: &mut ScanReader<G>,
|
||||
spec: ScanSpec,
|
||||
ap: &mut VaList,
|
||||
) -> Result<usize, ConversionError> {
|
||||
let n = if self.discard { 0 } else { 1 };
|
||||
|
||||
match (spec, self.size) {
|
||||
(ScanSpec::Percent, _) => todo!(),
|
||||
(ScanSpec::Word, ScanSize::Normal) => {
|
||||
let width = self.width.unwrap_or(usize::MAX);
|
||||
let dst = if self.discard {
|
||||
None
|
||||
} else {
|
||||
let dst = unsafe { ap.arg::<*mut c_char>() };
|
||||
if dst.is_null() {
|
||||
panic!();
|
||||
}
|
||||
Some(dst)
|
||||
};
|
||||
|
||||
scan_word(stream, dst, width)?;
|
||||
|
||||
Ok(n)
|
||||
}
|
||||
(ScanSpec::Chars(set), ScanSize::Normal) => {
|
||||
let width = self.width.unwrap_or(usize::MAX);
|
||||
let dst = if self.discard {
|
||||
None
|
||||
} else {
|
||||
let dst = unsafe { ap.arg::<*mut c_char>() };
|
||||
if dst.is_null() {
|
||||
panic!();
|
||||
}
|
||||
Some(dst)
|
||||
};
|
||||
|
||||
scan_char_set(stream, dst, set, width)?;
|
||||
|
||||
Ok(n)
|
||||
}
|
||||
(ScanSpec::AnyChar, ScanSize::Normal) => {
|
||||
let width = self.width.unwrap_or(1);
|
||||
let dst = if self.discard {
|
||||
None
|
||||
} else {
|
||||
let dst = unsafe { ap.arg::<*mut c_char>() };
|
||||
if dst.is_null() {
|
||||
panic!();
|
||||
}
|
||||
Some(dst)
|
||||
};
|
||||
|
||||
scan_chars(stream, dst, width)?;
|
||||
|
||||
Ok(n)
|
||||
}
|
||||
(ScanSpec::Word, _) => todo!("%ls"),
|
||||
(ScanSpec::Chars(_), _) => todo!("%l[...]"),
|
||||
(ScanSpec::AnyChar, _) => todo!("%lc"),
|
||||
(ScanSpec::Signed(radix), size) => {
|
||||
let value = scan_signed(stream, radix)?;
|
||||
|
||||
if !self.discard {
|
||||
unsafe {
|
||||
size.write_int(value, ap);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(n)
|
||||
}
|
||||
(ScanSpec::Unsigned(radix), size) => {
|
||||
let value = scan_unsigned(stream, radix)?;
|
||||
|
||||
if !self.discard {
|
||||
unsafe {
|
||||
size.write_uint(value, ap);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(n)
|
||||
}
|
||||
(ScanSpec::Pointer, _) => todo!(),
|
||||
(ScanSpec::ConsumedCount, size) => {
|
||||
if !self.discard {
|
||||
unsafe {
|
||||
size.write_int(stream.consumed_count().try_into().unwrap(), ap);
|
||||
}
|
||||
}
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn scan_word<G: GetChar>(
|
||||
stream: &mut ScanReader<G>,
|
||||
mut dst: Option<*mut c_char>,
|
||||
limit: usize,
|
||||
) -> Result<(), ConversionError> {
|
||||
// Read until limit reached or whitespace/EOF
|
||||
let mut matched = 0;
|
||||
while matched < limit {
|
||||
let Some(ch) = stream.peek()? else {
|
||||
break;
|
||||
};
|
||||
|
||||
if ch.is_ascii_whitespace() {
|
||||
break;
|
||||
}
|
||||
|
||||
if let Some(dst) = dst.as_mut() {
|
||||
unsafe {
|
||||
**dst = ch as _;
|
||||
*dst = dst.add(1);
|
||||
}
|
||||
}
|
||||
matched += 1;
|
||||
|
||||
stream.next()?;
|
||||
}
|
||||
|
||||
if matched == 0 {
|
||||
return Err(ConversionError::ConversionFailed);
|
||||
}
|
||||
|
||||
if let Some(dst) = dst.as_mut() {
|
||||
unsafe {
|
||||
**dst = 0;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn scan_chars<G: GetChar>(
|
||||
stream: &mut ScanReader<G>,
|
||||
mut dst: Option<*mut c_char>,
|
||||
width: usize,
|
||||
) -> Result<(), ConversionError> {
|
||||
let mut matched = 0;
|
||||
|
||||
while matched < width {
|
||||
let Some(ch) = stream.next()? else {
|
||||
break;
|
||||
};
|
||||
|
||||
if let Some(dst) = dst.as_mut() {
|
||||
unsafe {
|
||||
**dst = ch as _;
|
||||
*dst = dst.add(1);
|
||||
}
|
||||
}
|
||||
|
||||
matched += 1;
|
||||
stream.next()?;
|
||||
}
|
||||
|
||||
if matched == width {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ConversionError::ConversionFailed)
|
||||
}
|
||||
}
|
||||
|
||||
fn scan_char_set<G: GetChar>(
|
||||
stream: &mut ScanReader<G>,
|
||||
mut dst: Option<*mut c_char>,
|
||||
set: ScanCharSet,
|
||||
width: usize,
|
||||
) -> Result<(), ConversionError> {
|
||||
let mut matched = 0;
|
||||
|
||||
while matched < width {
|
||||
let Some(ch) = stream.peek()? else {
|
||||
break;
|
||||
};
|
||||
|
||||
if !set.check(ch) {
|
||||
break;
|
||||
}
|
||||
|
||||
if let Some(dst) = dst.as_mut() {
|
||||
unsafe {
|
||||
**dst = ch as _;
|
||||
*dst = dst.add(1);
|
||||
}
|
||||
}
|
||||
|
||||
stream.next()?;
|
||||
matched += 1;
|
||||
}
|
||||
|
||||
if matched == 0 {
|
||||
return Err(ConversionError::ConversionFailed);
|
||||
}
|
||||
|
||||
if let Some(dst) = dst.as_mut() {
|
||||
unsafe {
|
||||
**dst = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn scan_unsigned<G: GetChar>(
|
||||
stream: &mut ScanReader<G>,
|
||||
radix: ScanRadix,
|
||||
) -> Result<u64, ConversionError> {
|
||||
let mut value = 0;
|
||||
let mut matched = 0;
|
||||
|
||||
match radix {
|
||||
ScanRadix::Auto => {
|
||||
let Some(maybe_0) = stream.peek()? else {
|
||||
return Err(ConversionError::ConversionFailed);
|
||||
};
|
||||
|
||||
if maybe_0 == b'0' {
|
||||
stream.next()?;
|
||||
|
||||
// Maybe x/X or octal
|
||||
let Some(maybe_next) = stream.peek()? else {
|
||||
// At least matched a zero
|
||||
return Ok(0);
|
||||
};
|
||||
|
||||
return match maybe_next {
|
||||
b'x' | b'X' => {
|
||||
stream.next()?;
|
||||
scan_unsigned(stream, ScanRadix::Hex)
|
||||
}
|
||||
b'0'..=b'9' => scan_unsigned(stream, ScanRadix::Oct),
|
||||
_ => Err(ConversionError::ConversionFailed),
|
||||
};
|
||||
} else {
|
||||
// Anything else is a decimal
|
||||
return scan_unsigned(stream, ScanRadix::Decimal);
|
||||
}
|
||||
}
|
||||
ScanRadix::Oct => {
|
||||
while let Some(ch) = stream.peek()? {
|
||||
let digit = match ch {
|
||||
b'0'..=b'7' => (ch - b'0') as u64,
|
||||
_ => break,
|
||||
};
|
||||
|
||||
stream.next()?;
|
||||
|
||||
value <<= 3;
|
||||
value |= digit;
|
||||
|
||||
matched += 1;
|
||||
}
|
||||
}
|
||||
ScanRadix::Decimal => {
|
||||
while let Some(ch) = stream.peek()? {
|
||||
let digit = match ch {
|
||||
b'0'..=b'9' => (ch - b'0') as u64,
|
||||
_ => break,
|
||||
};
|
||||
|
||||
stream.next()?;
|
||||
|
||||
value *= 10;
|
||||
value += digit;
|
||||
|
||||
matched += 1;
|
||||
}
|
||||
}
|
||||
ScanRadix::Hex => {
|
||||
while let Some(ch) = stream.peek()? {
|
||||
let digit = match ch {
|
||||
b'0'..=b'9' => (ch - b'0') as u64,
|
||||
b'a'..=b'f' => (ch - b'a') as u64 + 10,
|
||||
b'A'..=b'F' => (ch - b'A') as u64 + 10,
|
||||
_ => break,
|
||||
};
|
||||
|
||||
stream.next()?;
|
||||
|
||||
value <<= 4;
|
||||
value |= digit;
|
||||
|
||||
matched += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if matched != 0 {
|
||||
Ok(value)
|
||||
} else {
|
||||
// No characters matched
|
||||
Err(ConversionError::ConversionFailed)
|
||||
}
|
||||
}
|
||||
|
||||
fn scan_signed<G: GetChar>(
|
||||
stream: &mut ScanReader<G>,
|
||||
radix: ScanRadix,
|
||||
) -> Result<i64, ConversionError> {
|
||||
let sign = if let Some(ch) = stream.peek()? {
|
||||
if ch == b'-' {
|
||||
stream.next()?;
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
// EOF
|
||||
return Err(ConversionError::ConversionFailed);
|
||||
};
|
||||
|
||||
let value: i64 = scan_unsigned(stream, radix)?
|
||||
.try_into()
|
||||
.map_err(|_| ConversionError::ConversionFailed)?;
|
||||
|
||||
Ok(if sign { value } else { -value })
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
use core::ffi::{c_char, c_int, c_void, CStr, VaList};
|
||||
|
||||
use crate::header::errno::Errno;
|
||||
|
||||
use self::{
|
||||
char_set::ScanCharSet,
|
||||
format::{ScanOpt, ScanRadix, ScanSize, ScanSpec},
|
||||
reader::{GetChar, ScanReader},
|
||||
};
|
||||
|
||||
use super::{stdin, FILE};
|
||||
|
||||
mod char_set;
|
||||
mod format;
|
||||
mod reader;
|
||||
|
||||
pub enum ScanError {
|
||||
ReadError(Errno),
|
||||
InvalidFormat,
|
||||
}
|
||||
|
||||
pub enum ConversionError {
|
||||
ReadError(Errno),
|
||||
ConversionFailed,
|
||||
}
|
||||
|
||||
impl From<Errno> for ScanError {
|
||||
fn from(value: Errno) -> Self {
|
||||
Self::ReadError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Errno> for ConversionError {
|
||||
fn from(value: Errno) -> Self {
|
||||
Self::ReadError(value)
|
||||
}
|
||||
}
|
||||
|
||||
fn scanf_inner<G: GetChar>(
|
||||
stream: &mut ScanReader<G>,
|
||||
format: &[u8],
|
||||
mut ap: VaList,
|
||||
) -> Result<usize, ScanError> {
|
||||
let mut it = format.into_iter();
|
||||
let mut matched = 0;
|
||||
let mut skip_space = false;
|
||||
|
||||
while let Some(&ch) = it.next() {
|
||||
if ch == b' ' {
|
||||
// Any amount of whitespace
|
||||
if skip_space {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip following whitespace in format
|
||||
skip_space = true;
|
||||
|
||||
// Skip whitespace in stream
|
||||
while let Some(ic) = stream.peek()? {
|
||||
if !ic.is_ascii_whitespace() {
|
||||
break;
|
||||
}
|
||||
|
||||
stream.next()?;
|
||||
}
|
||||
|
||||
continue;
|
||||
} else if ch != b'%' {
|
||||
// Regular character
|
||||
skip_space = false;
|
||||
|
||||
let ic = stream.peek()?;
|
||||
if !ic.map(|ic| ic == ch).unwrap_or(false) {
|
||||
break;
|
||||
}
|
||||
|
||||
stream.next()?;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
skip_space = false;
|
||||
|
||||
// %*123lld
|
||||
let mut opts = ScanOpt::default();
|
||||
let mut cur = it.next();
|
||||
|
||||
if let Some(&ch) = cur {
|
||||
if ch == b'*' {
|
||||
opts.discard = true;
|
||||
cur = it.next();
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(&ch) = cur {
|
||||
if ch.is_ascii_digit() {
|
||||
opts.width
|
||||
.replace(opts.width.unwrap_or(0) * 10 + (ch - b'0') as usize);
|
||||
cur = it.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(&ch) = cur {
|
||||
match ch {
|
||||
b'h' => opts.size = opts.size.shorter(),
|
||||
b'l' => opts.size = opts.size.longer(),
|
||||
b'z' => opts.size = ScanSize::Size,
|
||||
// TODO ptrdiff_t
|
||||
b't' => todo!(),
|
||||
// TODO intmax_t/uintmax_t
|
||||
b'j' => todo!(),
|
||||
// TODO floats
|
||||
b'L' | b'q' => todo!(),
|
||||
_ => break,
|
||||
}
|
||||
|
||||
cur = it.next();
|
||||
}
|
||||
|
||||
let spec = if let Some(&ch) = cur {
|
||||
match ch {
|
||||
b'i' => ScanSpec::Signed(ScanRadix::Auto),
|
||||
b'd' => ScanSpec::Signed(ScanRadix::Decimal),
|
||||
b'x' | b'X' => ScanSpec::Unsigned(ScanRadix::Hex),
|
||||
b'u' => ScanSpec::Unsigned(ScanRadix::Decimal),
|
||||
b'o' => ScanSpec::Unsigned(ScanRadix::Oct),
|
||||
b's' => ScanSpec::Word,
|
||||
b'[' => {
|
||||
let (new_it, set) = ScanCharSet::from_format_iter(it)?;
|
||||
it = new_it;
|
||||
ScanSpec::Chars(set)
|
||||
}
|
||||
b'c' => ScanSpec::AnyChar,
|
||||
b'n' => ScanSpec::ConsumedCount,
|
||||
b'p' => ScanSpec::Pointer,
|
||||
b'%' => ScanSpec::Percent,
|
||||
b'f' | b'e' | b'g' | b'E' | b'a' => todo!(),
|
||||
// Unrecognized specifier
|
||||
_ => return Err(ScanError::InvalidFormat),
|
||||
}
|
||||
} else {
|
||||
// No specifier after %
|
||||
return Err(ScanError::InvalidFormat);
|
||||
};
|
||||
|
||||
matched += match opts.scan(stream, spec, &mut ap) {
|
||||
Ok(matched) => matched,
|
||||
Err(ConversionError::ConversionFailed) => break,
|
||||
Err(ConversionError::ReadError(err)) => return Err(ScanError::ReadError(err)),
|
||||
};
|
||||
}
|
||||
|
||||
// TODO report end of stream before anything is matched
|
||||
|
||||
Ok(matched)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn fscanf(stream: *mut FILE, format: *const c_char, mut args: ...) -> c_int {
|
||||
vfscanf(stream, format, args.as_va_list())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn scanf(format: *const c_char, mut args: ...) -> c_int {
|
||||
vfscanf(stdin, format, args.as_va_list())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn sscanf(str: *const c_char, format: *const c_char, mut args: ...) -> c_int {
|
||||
vsscanf(str, format, args.as_va_list())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn vfscanf(stream: *mut FILE, format: *const c_char, ap: VaList) -> c_int {
|
||||
if format.is_null() {
|
||||
panic!();
|
||||
}
|
||||
|
||||
let stream = stream.as_mut().unwrap();
|
||||
let format = CStr::from_ptr(format);
|
||||
|
||||
let mut reader = ScanReader::new(stream);
|
||||
|
||||
match scanf_inner(&mut reader, format.to_bytes(), ap) {
|
||||
Ok(count) => count.try_into().unwrap(),
|
||||
Err(_) => -1,
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn vscanf(format: *const c_char, ap: VaList) -> c_int {
|
||||
vfscanf(stdin, format, ap)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn vsscanf(str: *const c_char, format: *const c_char, ap: VaList) -> c_int {
|
||||
if str.is_null() || format.is_null() {
|
||||
panic!();
|
||||
}
|
||||
|
||||
let str = CStr::from_ptr(str);
|
||||
let format = CStr::from_ptr(format);
|
||||
|
||||
let mut str_it = str.to_bytes().into_iter();
|
||||
let mut reader = ScanReader::new(&mut str_it);
|
||||
|
||||
match scanf_inner(&mut reader, format.to_bytes(), ap) {
|
||||
Ok(count) => count.try_into().unwrap(),
|
||||
Err(_) => -1,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
use crate::{
|
||||
header::{errno::Errno, stdio::FILE},
|
||||
io::Read,
|
||||
};
|
||||
|
||||
pub trait GetChar {
|
||||
fn getc(&mut self) -> Result<Option<u8>, Errno>;
|
||||
}
|
||||
|
||||
pub struct ScanReader<'a, G: GetChar> {
|
||||
getter: &'a mut G,
|
||||
buffer: Option<u8>,
|
||||
consumed_count: usize,
|
||||
}
|
||||
|
||||
impl GetChar for FILE {
|
||||
fn getc(&mut self) -> Result<Option<u8>, Errno> {
|
||||
let mut buf = [0];
|
||||
match self.read(&mut buf) {
|
||||
Ok(1) => Ok(Some(buf[0])),
|
||||
Ok(_) => Ok(None),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, I: Iterator<Item = &'a u8>> GetChar for I {
|
||||
fn getc(&mut self) -> Result<Option<u8>, Errno> {
|
||||
Ok(self.next().copied())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, G: GetChar> ScanReader<'a, G> {
|
||||
pub fn new(getter: &'a mut G) -> Self {
|
||||
Self {
|
||||
getter,
|
||||
buffer: None,
|
||||
consumed_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn getc(&mut self) -> Result<Option<u8>, Errno> {
|
||||
match self.getter.getc() {
|
||||
Ok(Some(n)) => {
|
||||
self.consumed_count += 1;
|
||||
Ok(Some(n))
|
||||
}
|
||||
e => e,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn consumed_count(&self) -> usize {
|
||||
self.consumed_count
|
||||
}
|
||||
|
||||
pub fn peek(&mut self) -> Result<Option<u8>, Errno> {
|
||||
if self.buffer.is_none() {
|
||||
self.buffer = self.getc()?;
|
||||
}
|
||||
Ok(self.buffer)
|
||||
}
|
||||
|
||||
pub fn next(&mut self) -> Result<Option<u8>, Errno> {
|
||||
match self.buffer.take() {
|
||||
Some(v) => Ok(Some(v)),
|
||||
None => self.getc(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,91 @@
|
||||
use core::ffi::c_int;
|
||||
use core::{
|
||||
alloc::{GlobalAlloc, Layout},
|
||||
ffi::{c_int, c_void},
|
||||
intrinsics,
|
||||
ptr::null_mut,
|
||||
};
|
||||
|
||||
use crate::{allocator::GLOBAL_ALLOCATOR, error};
|
||||
|
||||
use super::errno::ENOMEM;
|
||||
|
||||
pub const EXIT_SUCCESS: c_int = 0;
|
||||
pub const EXIT_FAILURE: c_int = 1;
|
||||
|
||||
unsafe fn alloc_inner(size: usize, offset: usize, align: usize) -> *mut c_void {
|
||||
let layout = Layout::from_size_align(size + offset, align).unwrap();
|
||||
let ptr = GLOBAL_ALLOCATOR.alloc(layout);
|
||||
|
||||
if ptr.is_null() {
|
||||
error::set_errno(ENOMEM);
|
||||
return ptr as *mut c_void;
|
||||
}
|
||||
|
||||
*(ptr as *mut u64) = (size + offset) as u64;
|
||||
*(ptr as *mut u64).add(1) = align as u64;
|
||||
|
||||
ptr.add(offset) as *mut c_void
|
||||
}
|
||||
|
||||
unsafe fn get_allocation(ptr: *mut c_void) -> (*mut u8, Layout) {
|
||||
let ptr = (ptr as *mut u64).offset(-2);
|
||||
let size = *ptr as usize;
|
||||
let align = *ptr.add(1) as usize;
|
||||
(
|
||||
ptr as *mut u8,
|
||||
Layout::from_size_align(size, align).unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn calloc(nmemb: usize, size: usize) -> *mut c_void {
|
||||
match nmemb.checked_mul(size) {
|
||||
Some(size) => {
|
||||
let ptr = malloc(size);
|
||||
if !ptr.is_null() {
|
||||
intrinsics::write_bytes(ptr as *mut u8, 0, size);
|
||||
}
|
||||
ptr
|
||||
}
|
||||
None => {
|
||||
error::set_errno(ENOMEM);
|
||||
null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn malloc(size: usize) -> *mut c_void {
|
||||
alloc_inner(size, 16, 8)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn realloc(ptr: *mut c_void, new_size: usize) -> *mut c_void {
|
||||
if ptr.is_null() {
|
||||
return malloc(new_size);
|
||||
}
|
||||
if new_size == 0 {
|
||||
free(ptr);
|
||||
return null_mut();
|
||||
}
|
||||
|
||||
// TODO: implement realloc in libyalloc?
|
||||
let (_, old_layout) = get_allocation(ptr);
|
||||
let new_ptr = malloc(new_size);
|
||||
|
||||
if !new_ptr.is_null() {
|
||||
yggdrasil_rt::memcpy(new_ptr as _, ptr as _, old_layout.size());
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
new_ptr
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn free(ptr: *mut c_void) {
|
||||
if ptr.is_null() {
|
||||
return;
|
||||
}
|
||||
let (allocation, layout) = get_allocation(ptr);
|
||||
GLOBAL_ALLOCATOR.dealloc(allocation, layout);
|
||||
}
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
c_variadic,
|
||||
try_trait_v2,
|
||||
slice_internals,
|
||||
core_intrinsics,
|
||||
arbitrary_self_types,
|
||||
new_uninit,
|
||||
maybe_uninit_slice
|
||||
)]
|
||||
#![allow(internal_features)]
|
||||
#![no_std]
|
||||
|
||||
use core::ffi::{c_char, c_int};
|
||||
|
||||
Reference in New Issue
Block a user