Compare commits

...

3 Commits

Author SHA1 Message Date
39ba4c9087
ls: Add colors to output
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-02 20:01:10 +02:00
c524dc8d58
libluna+kernel: Basic ANSI escape sequences 2023-09-02 19:35:42 +02:00
e76a91d5d0
libc+libluna: Move the scanf implementation from libc to libluna 2023-09-02 15:48:58 +02:00
11 changed files with 809 additions and 275 deletions

View File

@ -8,6 +8,7 @@
#include <os/FileSystem.h> #include <os/FileSystem.h>
#include <os/Mode.h> #include <os/Mode.h>
#include <pwd.h> #include <pwd.h>
#include <unistd.h>
void find_user_and_group(struct stat& st, StringView& owner, StringView& group) void find_user_and_group(struct stat& st, StringView& owner, StringView& group)
{ {
@ -47,18 +48,46 @@ int sort_reverse(const os::Directory::Entry* a, const os::Directory::Entry* b)
return 0; return 0;
} }
static Result<String> entry_join(const Vector<os::Directory::Entry>& vec, StringView delim) #define RESET_COLORS "\x1b[m"
#define SYMLINK_COLOR "\x1b[36m"
#define FILE_COLOR "\x1b[1;32m"
#define DIR_COLOR "\x1b[1;34m"
#define SOCKET_COLOR "\x1b[33m"
#define SPECIAL_COLOR "\x1b[35m"
#define STICKY_COLOR "\x1b[30;1;42m"
#define SETUID_COLOR "\x1b[30;1;41m"
#define EXEC_COLOR "\x1b[1;31m"
static const char* file_type_color(const os::Directory::Entry& entry)
{
if (entry.mode & S_ISVTX) return STICKY_COLOR;
if (entry.mode & S_ISUID || entry.mode & S_ISGID) return SETUID_COLOR;
switch (entry.mode & S_IFMT)
{
case S_IFREG: return entry.mode & S_IXUSR ? EXEC_COLOR : FILE_COLOR;
case S_IFDIR: return DIR_COLOR;
case S_IFLNK: return SYMLINK_COLOR;
case S_IFSOCK: return SOCKET_COLOR;
default: return SPECIAL_COLOR;
}
}
static Result<String> entry_join(const Vector<os::Directory::Entry>& vec, StringView delim, bool colors)
{ {
if (vec.size() == 0) return String {}; if (vec.size() == 0) return String {};
if (vec.size() == 1) return vec[0].name.clone();
StringBuilder sb; StringBuilder sb;
if (colors) TRY(sb.add(StringView { file_type_color(vec[0]) }));
TRY(sb.add(vec[0].name)); TRY(sb.add(vec[0].name));
if (colors) TRY(sb.add(StringView { RESET_COLORS }));
for (usize i = 1; i < vec.size(); i++) for (usize i = 1; i < vec.size(); i++)
{ {
TRY(sb.add(delim)); TRY(sb.add(delim));
if (colors) TRY(sb.add(StringView { file_type_color(vec[i]) }));
TRY(sb.add(vec[i].name)); TRY(sb.add(vec[i].name));
if (colors) TRY(sb.add(StringView { RESET_COLORS }));
} }
return sb.string(); return sb.string();
@ -75,6 +104,7 @@ Result<int> luna_main(int argc, char** argv)
bool follow_symlink_args { false }; bool follow_symlink_args { false };
bool one_per_line { false }; bool one_per_line { false };
bool list_directories { false }; bool list_directories { false };
bool no_colors { false };
StringView sort_type { "name" }; StringView sort_type { "name" };
@ -98,6 +128,8 @@ Result<int> luna_main(int argc, char** argv)
parser.add_switch_argument(list_directories, 'd', "directory"_sv, "list directories instead of their contents"_sv); parser.add_switch_argument(list_directories, 'd', "directory"_sv, "list directories instead of their contents"_sv);
parser.add_value_argument(sort_type, ' ', "sort"_sv, "sort by name, size or time"_sv); parser.add_value_argument(sort_type, ' ', "sort"_sv, "sort by name, size or time"_sv);
parser.add_switch_argument(reverse_sort, 'r', "reverse"_sv, "reverse order while sorting"_sv); parser.add_switch_argument(reverse_sort, 'r', "reverse"_sv, "reverse order while sorting"_sv);
parser.add_switch_argument(no_colors, ' ', "no-colors"_sv,
"disable coloring of output (defaults to true when not in a TTY)"_sv);
parser.parse(argc, argv); parser.parse(argc, argv);
Vector<os::Directory::Entry> files; Vector<os::Directory::Entry> files;
@ -154,11 +186,13 @@ Result<int> luna_main(int argc, char** argv)
if (!long_list) if (!long_list)
{ {
auto list = TRY(entry_join(files, one_per_line ? "\n"_sv : " "_sv)); auto list = TRY(entry_join(files, one_per_line ? "\n"_sv : " "_sv, !no_colors && isatty(STDIN_FILENO)));
if (!list.is_empty()) os::println("%s", list.chars()); if (!list.is_empty()) os::println("%s", list.chars());
} }
else else
{ {
bool colors = !no_colors && isatty(STDIN_FILENO);
for (const auto& file : files) for (const auto& file : files)
{ {
struct stat st; struct stat st;
@ -176,14 +210,32 @@ Result<int> luna_main(int argc, char** argv)
if (!human_readable && !si) if (!human_readable && !si)
{ {
os::println("%s %u %4s %4s %10lu %s%s%s", formatted_mode, st.st_nlink, owner.chars(), group.chars(), if (colors)
st.st_size, file.name.chars(), link.is_empty() ? "" : " -> ", link.chars()); {
os::println("%s %u %4s %4s %10lu %s%s" RESET_COLORS "%s" SYMLINK_COLOR "%s" RESET_COLORS,
formatted_mode, st.st_nlink, owner.chars(), group.chars(), st.st_size,
file_type_color(file), file.name.chars(), link.is_empty() ? "" : " -> ", link.chars());
}
else
{
os::println("%s %u %4s %4s %10lu %s%s%s", formatted_mode, st.st_nlink, owner.chars(), group.chars(),
st.st_size, file.name.chars(), link.is_empty() ? "" : " -> ", link.chars());
}
} }
else else
{ {
auto size = TRY(to_dynamic_unit(st.st_size, 10, false, si ? Unit::SI : Unit::Binary, false)); auto size = TRY(to_dynamic_unit(st.st_size, 10, false, si ? Unit::SI : Unit::Binary, false));
os::println("%s %u %4s %4s %6s %s%s%s", formatted_mode, st.st_nlink, owner.chars(), group.chars(), if (colors)
size.chars(), file.name.chars(), link.is_empty() ? "" : " -> ", link.chars()); {
os::println("%s %u %4s %4s %6s %s%s" RESET_COLORS "%s" SYMLINK_COLOR "%s" RESET_COLORS,
formatted_mode, st.st_nlink, owner.chars(), group.chars(), size.chars(),
file_type_color(file), file.name.chars(), link.is_empty() ? "" : " -> ", link.chars());
}
else
{
os::println("%s %u %4s %4s %6s %s%s%s", formatted_mode, st.st_nlink, owner.chars(), group.chars(),
size.chars(), file.name.chars(), link.is_empty() ? "" : " -> ", link.chars());
}
} }
} }
} }

View File

@ -3,6 +3,7 @@
#include "video/Framebuffer.h" #include "video/Framebuffer.h"
#include <luna/CString.h> #include <luna/CString.h>
#include <luna/CType.h> #include <luna/CType.h>
#include <luna/EscapeSequence.h>
#include <luna/Format.h> #include <luna/Format.h>
#include <luna/Result.h> #include <luna/Result.h>
#include <luna/ScopeGuard.h> #include <luna/ScopeGuard.h>
@ -13,20 +14,43 @@ extern const BOOTBOOT bootboot;
#include "video/BuiltinFont.h" #include "video/BuiltinFont.h"
static constexpr u32 BLACK = 0xff000000; // Default text color.
static constexpr u32 WHITE = 0xffffffff; static constexpr u32 WHITE = 0xffffffff;
// xterm color palette.
static constexpr u32 BLACK = 0xff000000;
static constexpr u32 RED = 0xffcd0000;
static constexpr u32 GREEN = 0xff00cd00;
static constexpr u32 YELLOW = 0xffcdcd00;
static constexpr u32 BLUE = 0xff0000ee;
static constexpr u32 MAGENTA = 0xffcd00cd;
static constexpr u32 CYAN = 0xff00cdcd;
static constexpr u32 GRAY = 0xffe5e5e5;
static constexpr u32 BRIGHT_BLACK = 0xff7f7f7f;
static constexpr u32 BRIGHT_RED = 0xffff0000;
static constexpr u32 BRIGHT_GREEN = 0xff00ff00;
static constexpr u32 BRIGHT_YELLOW = 0xffffff00;
static constexpr u32 BRIGHT_BLUE = 0xff5c5cff;
static constexpr u32 BRIGHT_MAGENTA = 0xffff00ff;
static constexpr u32 BRIGHT_CYAN = 0xff00ffff;
static constexpr u32 BRIGHT_GRAY = 0xffffffff;
static u32 g_background_color = BLACK; static u32 g_background_color = BLACK;
static u32 g_foreground_color = WHITE; static u32 g_foreground_color = WHITE;
static constexpr u32 FONT_HEIGHT = 16; static constexpr u32 FONT_HEIGHT = 16;
static constexpr u32 FONT_WIDTH = 8; static constexpr u32 FONT_WIDTH = 8;
static bool bold = false;
static u32 g_x_position = 0; static u32 g_x_position = 0;
static u32 g_y_position = 0; static u32 g_y_position = 0;
static Utf8StateDecoder utf8_decoder; static Utf8StateDecoder utf8_decoder;
static Option<EscapeSequenceParser> escape_sequence_parser;
static void putwchar_at(wchar_t c, u32 x, u32 y) static void putwchar_at(wchar_t c, u32 x, u32 y)
{ {
const u8* glyph = &font[c * 16]; const u8* glyph = &font[c * 16];
@ -87,6 +111,206 @@ static bool at_end_of_screen()
return (g_x_position + FONT_WIDTH) > Framebuffer::width(); return (g_x_position + FONT_WIDTH) > Framebuffer::width();
} }
static bool handle_escape_sequence(wchar_t c)
{
auto rc = escape_sequence_parser->advance(static_cast<u8>(c));
if (rc.has_error())
{
escape_sequence_parser = Option<EscapeSequenceParser> {};
return false;
}
if (!rc.value()) return true;
if (!escape_sequence_parser->valid())
{
escape_sequence_parser = Option<EscapeSequenceParser> {};
return false;
}
const auto& params = escape_sequence_parser->parameters();
switch (escape_sequence_parser->code())
{
case EscapeCode::CursorUp: {
int lines = params.size() ? params[0] : 1;
int pixels = lines * FONT_HEIGHT;
if ((u32)pixels > g_y_position) g_y_position = 0;
else
g_y_position -= pixels;
};
break;
case EscapeCode::CursorDown: {
int lines = params.size() ? params[0] : 1;
int pixels = lines * FONT_HEIGHT;
if (pixels + g_y_position >= Framebuffer::height()) g_y_position = Framebuffer::height() - FONT_HEIGHT;
else
g_y_position += pixels;
};
break;
case EscapeCode::CursorBack: {
int chars = params.size() ? params[0] : 1;
int pixels = chars * FONT_WIDTH;
if ((u32)pixels > g_x_position) g_x_position = 0;
else
g_x_position -= pixels;
};
break;
case EscapeCode::CursorForward: {
int chars = params.size() ? params[0] : 1;
int pixels = chars * FONT_WIDTH;
if (pixels + g_x_position >= Framebuffer::width()) g_x_position = Framebuffer::width() - FONT_WIDTH;
else
g_x_position += pixels;
};
break;
case EscapeCode::CursorNextLine: {
int lines = params.size() ? params[0] : 1;
int pixels = lines * FONT_HEIGHT;
if ((u32)pixels > g_y_position) g_y_position = 0;
else
g_y_position -= pixels;
g_x_position = 0;
};
break;
case EscapeCode::CursorPreviousLine: {
int lines = params.size() ? params[0] : 1;
int pixels = lines * FONT_HEIGHT;
if (pixels + g_y_position >= Framebuffer::height()) g_y_position = Framebuffer::height() - FONT_HEIGHT;
else
g_y_position += pixels;
g_x_position = 0;
};
break;
case EscapeCode::CursorHorizontalAbsolute: {
int line = (params.size() ? params[0] : 1) - 1;
if (line < 0) break;
u32 position = line * FONT_HEIGHT;
if (position >= Framebuffer::height()) position = Framebuffer::height() - FONT_HEIGHT;
g_y_position = position;
};
break;
case EscapeCode::SetCursorPosition: {
int x = (params.size() ? params[0] : 1) - 1;
int y = (params.size() > 1 ? params[1] : 1) - 1;
if (x < 0 || y < 0) break;
u32 x_position = x * FONT_WIDTH;
if (x_position >= Framebuffer::width()) x_position = Framebuffer::width() - FONT_HEIGHT;
g_x_position = x_position;
u32 y_position = y * FONT_HEIGHT;
if (y_position >= Framebuffer::height()) y_position = Framebuffer::height() - FONT_HEIGHT;
g_y_position = y_position;
};
break;
case EscapeCode::SelectGraphicRendition: {
if (!params.size())
{
g_foreground_color = WHITE;
g_background_color = BLACK;
bold = false;
break;
}
for (usize i = 0; i < params.size(); i++)
{
int arg = params[i];
switch (arg)
{
case 0: {
g_foreground_color = BLACK;
g_background_color = WHITE;
bold = false;
break;
}
case 1: {
bold = true;
break;
}
case 22: {
bold = false;
break;
}
case 30: {
g_foreground_color = bold ? BRIGHT_BLACK : BLACK;
break;
}
case 31: {
g_foreground_color = bold ? BRIGHT_RED : RED;
break;
}
case 32: {
g_foreground_color = bold ? BRIGHT_GREEN : GREEN;
break;
}
case 33: {
g_foreground_color = bold ? BRIGHT_YELLOW : YELLOW;
break;
}
case 34: {
g_foreground_color = bold ? BRIGHT_BLUE : BLUE;
break;
}
case 35: {
g_foreground_color = bold ? BRIGHT_MAGENTA : MAGENTA;
break;
}
case 36: {
g_foreground_color = bold ? BRIGHT_CYAN : CYAN;
break;
}
case 37: {
g_foreground_color = bold ? BRIGHT_GRAY : GRAY;
break;
}
case 39: {
g_foreground_color = WHITE;
break;
}
case 40: {
g_background_color = bold ? BRIGHT_BLACK : BLACK;
break;
}
case 41: {
g_background_color = bold ? BRIGHT_RED : RED;
break;
}
case 42: {
g_background_color = bold ? BRIGHT_GREEN : GREEN;
break;
}
case 43: {
g_background_color = bold ? BRIGHT_YELLOW : YELLOW;
break;
}
case 44: {
g_background_color = bold ? BRIGHT_BLUE : BLUE;
break;
}
case 45: {
g_background_color = bold ? BRIGHT_MAGENTA : MAGENTA;
break;
}
case 46: {
g_background_color = bold ? BRIGHT_CYAN : CYAN;
break;
}
case 47: {
g_background_color = bold ? BRIGHT_GRAY : GRAY;
break;
}
case 49: {
g_background_color = BLACK;
break;
}
default: break;
}
}
}
break;
default: break;
}
escape_sequence_parser = Option<EscapeSequenceParser> {};
return true;
}
namespace TextConsole namespace TextConsole
{ {
void putwchar(wchar_t c) void putwchar(wchar_t c)
@ -94,6 +318,11 @@ namespace TextConsole
// Unprintable (not in the built-in font) characters get represented as a box // Unprintable (not in the built-in font) characters get represented as a box
if (c > (wchar_t)255) c = (wchar_t)256; if (c > (wchar_t)255) c = (wchar_t)256;
if (escape_sequence_parser.has_value())
{
if (handle_escape_sequence(c)) return;
}
switch (c) switch (c)
{ {
case L'\n': { case L'\n': {
@ -113,6 +342,10 @@ namespace TextConsole
erase_current_char(); erase_current_char();
} }
break; break;
case L'\x1b':
case L'\x9b':
case L'\x90':
case L'\x9d': escape_sequence_parser = EscapeSequenceParser { (u8)c }; break;
default: { default: {
if (_iscntrl(c)) return; if (_iscntrl(c)) return;
putwchar_at(c, g_x_position, g_y_position); putwchar_at(c, g_x_position, g_y_position);

View File

@ -20,7 +20,6 @@ set(SOURCES
src/pwd.cpp src/pwd.cpp
src/grp.cpp src/grp.cpp
src/locale.cpp src/locale.cpp
src/scanf.cpp
src/signal.cpp src/signal.cpp
src/termios.cpp src/termios.cpp
src/utime.cpp src/utime.cpp

View File

@ -1,263 +0,0 @@
#include <errno.h>
#include <luna/CType.h>
#include <luna/NumberParsing.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FLAG_DISCARD (1 << 0)
#define FLAG_ALLOC (1 << 1)
#define FLAG_WIDTH (1 << 2)
#define FLAG_LONG (1 << 3)
#define FLAG_LONG_LONG (1 << 4)
#define FLAG_SHORT (1 << 5)
#define FLAG_CHAR (1 << 6)
static int parse_flags(const char** format)
{
int result = 0;
while (true)
{
switch (**format)
{
case '*':
result |= FLAG_DISCARD;
(*format)++;
break;
case 'm':
result |= FLAG_ALLOC;
(*format)++;
break;
default: return result;
}
}
}
static size_t parse_width(const char** format, int& flags)
{
size_t result = 0;
if (_isdigit(**format))
{
result = scan_unsigned_integer(format);
flags |= FLAG_WIDTH;
}
return result;
}
static void parse_type(const char** format, int& flags)
{
// FIXME: Support %j (intmax_t/uintmax_t)
switch (**format)
{
case 'h':
flags |= FLAG_SHORT;
(*format)++;
if (**format == 'h')
{
flags |= FLAG_CHAR;
(*format)++;
}
break;
case 'l':
flags |= FLAG_LONG;
(*format)++;
if (**format == 'l')
{
flags |= FLAG_LONG_LONG;
(*format)++;
}
break;
case 't':
flags |= (sizeof(ptrdiff_t) == sizeof(long)) ? FLAG_LONG : FLAG_LONG_LONG;
(*format)++;
break;
case 'z':
flags |= (sizeof(size_t) == sizeof(long)) ? FLAG_LONG : FLAG_LONG_LONG;
(*format)++;
break;
default: break;
}
}
static void write_parsed_signed_integer(ssize_t value, int flags, va_list ap)
{
if (flags & FLAG_LONG_LONG) *va_arg(ap, signed long long*) = (signed long long)value;
else if (flags & FLAG_LONG)
*va_arg(ap, signed long*) = (signed long)value;
else if (flags & FLAG_SHORT)
*va_arg(ap, signed int*) = (signed short)value;
else if (flags & FLAG_CHAR)
*va_arg(ap, signed int*) = (signed char)value;
else
*va_arg(ap, signed int*) = (signed int)value;
}
static void write_parsed_unsigned_integer(size_t value, int flags, va_list ap)
{
if (flags & FLAG_LONG_LONG) *va_arg(ap, unsigned long long*) = (unsigned long long)value;
else if (flags & FLAG_LONG)
*va_arg(ap, unsigned long*) = (unsigned long)value;
else if (flags & FLAG_SHORT)
*va_arg(ap, unsigned int*) = (unsigned short)value;
else if (flags & FLAG_CHAR)
*va_arg(ap, unsigned int*) = (unsigned char)value;
else
*va_arg(ap, unsigned int*) = (unsigned int)value;
}
#define WHITESPACE_CHARACTERS " \t\f\r\n\v"
static void skip_whitespace(const char** str)
{
*str += strspn(*str, WHITESPACE_CHARACTERS);
}
extern "C"
{
int vsscanf(const char* str, const char* format, va_list ap)
{
int parsed = 0;
const char* s = str; // Keep a pointer to the beginning of the string for %n
if (*str == 0) return EOF;
while (*format)
{
if (*format != '%')
{
normal:
if (!_isspace(*format))
{
if (*str != *format) return parsed;
str++;
format++;
if (*str == 0) return parsed;
continue;
}
skip_whitespace(&format);
skip_whitespace(&str);
if (*str == 0) return parsed;
continue;
}
else
{
format++;
if (*format == '%')
{
skip_whitespace(&str);
goto normal;
}
int flags = parse_flags(&format);
size_t width = parse_width(&format, flags);
parse_type(&format, flags);
char specifier = *format++;
if (!specifier) return parsed;
switch (specifier)
{
case 's': {
skip_whitespace(&str);
size_t chars = strcspn(str, WHITESPACE_CHARACTERS);
if (!chars) return parsed;
if ((flags & FLAG_WIDTH) && chars > width) chars = width;
if (!(flags & FLAG_DISCARD))
{
char* ptr;
if (flags & FLAG_ALLOC)
{
ptr = (char*)malloc(chars + 1);
if (!ptr) return parsed;
*va_arg(ap, char**) = ptr;
}
else
ptr = va_arg(ap, char*);
memcpy(ptr, str, chars);
ptr[chars] = 0;
}
str += chars;
parsed++;
break;
}
case 'c': {
if (strlen(str) < width) return parsed;
if (!(flags & FLAG_WIDTH)) width = 1;
if (!(flags & FLAG_DISCARD))
{
char* ptr;
if (flags & FLAG_ALLOC)
{
ptr = (char*)malloc(width);
if (!ptr) return parsed;
*va_arg(ap, char**) = ptr;
}
else
ptr = va_arg(ap, char*);
memcpy(ptr, str, width);
}
str += width;
parsed++;
break;
}
case 'd': {
skip_whitespace(&str);
ssize_t value = scan_signed_integer(&str, 10);
if (!(flags & FLAG_DISCARD)) write_parsed_signed_integer(value, flags, ap);
parsed++;
break;
}
case 'i': {
skip_whitespace(&str);
ssize_t value = scan_signed_integer(&str, 0);
if (!(flags & FLAG_DISCARD)) write_parsed_signed_integer(value, flags, ap);
parsed++;
break;
}
case 'o': {
skip_whitespace(&str);
size_t value = scan_unsigned_integer(&str, 8);
if (!(flags & FLAG_DISCARD)) write_parsed_unsigned_integer(value, flags, ap);
parsed++;
break;
}
case 'u': {
skip_whitespace(&str);
size_t value = scan_unsigned_integer(&str, 10);
if (!(flags & FLAG_DISCARD)) write_parsed_unsigned_integer(value, flags, ap);
parsed++;
break;
}
case 'X':
case 'x': {
skip_whitespace(&str);
size_t value = scan_unsigned_integer(&str, 16);
if (!(flags & FLAG_DISCARD)) write_parsed_unsigned_integer(value, flags, ap);
parsed++;
break;
}
case 'p': {
skip_whitespace(&str);
size_t value = scan_unsigned_integer(&str, 16);
if (!(flags & FLAG_DISCARD)) *va_arg(ap, void**) = (void*)value;
parsed++;
break;
}
case 'n': {
if (!(flags & FLAG_DISCARD)) *va_arg(ap, int*) = (int)(str - s);
break;
}
default: {
fprintf(stderr, "vsscanf: unknown conversion specifier: %%%c\n", specifier);
return parsed;
}
}
}
}
return parsed;
}
}

View File

@ -2,6 +2,7 @@
#include <fcntl.h> #include <fcntl.h>
#include <luna/Common.h> #include <luna/Common.h>
#include <luna/Format.h> #include <luna/Format.h>
#include <luna/Scanf.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -618,12 +619,17 @@ extern "C"
return rc; return rc;
} }
int vsscanf(const char* str, const char* format, va_list ap)
{
return scanf_impl(str, format, ap);
}
int sscanf(const char* str, const char* format, ...) int sscanf(const char* str, const char* format, ...)
{ {
va_list ap; va_list ap;
va_start(ap, format); va_start(ap, format);
int rc = vsscanf(str, format, ap); int rc = scanf_impl(str, format, ap);
va_end(ap); va_end(ap);
@ -634,7 +640,7 @@ extern "C"
{ {
char buf[BUFSIZ]; char buf[BUFSIZ];
if (!fgets(buf, sizeof(buf), stream)) return EOF; if (!fgets(buf, sizeof(buf), stream)) return EOF;
return vsscanf(buf, format, ap); return scanf_impl(buf, format, ap);
} }
int fscanf(FILE* stream, const char* format, ...) int fscanf(FILE* stream, const char* format, ...)

View File

@ -5,6 +5,7 @@ file(GLOB HEADERS include/luna/*.h)
set(FREESTANDING_SOURCES set(FREESTANDING_SOURCES
${HEADERS} ${HEADERS}
src/CRC32.cpp src/CRC32.cpp
src/EscapeSequence.cpp
src/Format.cpp src/Format.cpp
src/Sort.cpp src/Sort.cpp
src/NumberParsing.cpp src/NumberParsing.cpp
@ -14,6 +15,7 @@ set(FREESTANDING_SOURCES
src/SystemError.cpp src/SystemError.cpp
src/Bitmap.cpp src/Bitmap.cpp
src/Buffer.cpp src/Buffer.cpp
src/Scanf.cpp
src/Stack.cpp src/Stack.cpp
src/String.cpp src/String.cpp
src/StringBuilder.cpp src/StringBuilder.cpp

View File

@ -0,0 +1,67 @@
/**
* @file EscapeSequence.h
* @author apio (cloudapio.eu)
* @brief ANSI escape sequence parsing.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <luna/Vector.h>
enum class EscapeCode
{
SaveCursor,
RestoreCursor,
CursorUp,
CursorDown,
CursorForward,
CursorBack,
CursorNextLine,
CursorPreviousLine,
CursorHorizontalAbsolute,
SetCursorPosition,
SelectGraphicRendition,
};
class EscapeSequenceParser
{
public:
EscapeSequenceParser(u8 begin);
Result<bool> advance(u8 byte);
bool valid() const
{
return m_valid;
}
const Vector<int>& parameters() const
{
return m_parameters;
}
EscapeCode code() const
{
return m_escape_code;
}
private:
enum class SequenceType
{
ESC,
CSI,
DCS,
OSC,
};
Vector<u8> m_parameter;
Vector<int> m_parameters;
SequenceType m_sequence_type;
bool m_parsing_parameter { false };
bool m_valid { false };
EscapeCode m_escape_code;
};

View File

@ -0,0 +1,21 @@
/**
* @file Scanf.h
* @author apio (cloudapio.eu)
* @brief Scanf implementation.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <stdarg.h>
/**
* @brief scanf() implementation.
*
* @param str The string to read input from.
* @param format The format string.
* @param ap The variadic argument list.
* @return int The number of arguments read, or -1 if the string was empty.
*/
int scanf_impl(const char* str, const char* format, va_list ap);

View File

@ -0,0 +1,146 @@
/**
* @file EscapeSequence.cpp
* @author apio (cloudapio.eu)
* @brief ANSI escape sequence parsing.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <luna/CType.h>
#include <luna/Check.h>
#include <luna/EscapeSequence.h>
#include <luna/NumberParsing.h>
EscapeSequenceParser::EscapeSequenceParser(u8 begin)
{
switch (begin)
{
case 0x1b: m_sequence_type = SequenceType::ESC; break;
case 0x9b: m_sequence_type = SequenceType::CSI; break;
case 0x90: m_sequence_type = SequenceType::DCS; break;
case 0x9d: m_sequence_type = SequenceType::OSC; break;
default: fail("Unrecognized escape sequence type");
}
}
Result<bool> EscapeSequenceParser::advance(u8 byte)
{
switch (m_sequence_type)
{
case SequenceType::ESC: {
switch (byte)
{
case '[': {
m_sequence_type = SequenceType::CSI;
return false;
};
case 'P': {
m_sequence_type = SequenceType::DCS;
return false;
};
case ']': {
m_sequence_type = SequenceType::OSC;
return false;
};
case '7': {
m_escape_code = EscapeCode::SaveCursor;
m_valid = true;
return true;
};
case '8': {
m_escape_code = EscapeCode::RestoreCursor;
m_valid = true;
return true;
};
default: {
m_valid = false;
return true;
}
}
};
break;
case SequenceType::CSI: {
if (_isdigit(byte))
{
m_parsing_parameter = true;
TRY(m_parameter.try_append(byte));
return false;
}
if (!m_parsing_parameter && byte == ';')
{
TRY(m_parameters.try_append(0));
return false;
}
if (m_parsing_parameter)
{
TRY(m_parameter.try_append(0));
int value = static_cast<int>(parse_unsigned_integer((const char*)m_parameter.data(), nullptr, 10));
m_parameter.clear();
TRY(m_parameters.try_append(value));
}
switch (byte)
{
case 'A': {
m_escape_code = EscapeCode::CursorUp;
m_valid = true;
return true;
};
case 'B': {
m_escape_code = EscapeCode::CursorDown;
m_valid = true;
return true;
};
case 'C': {
m_escape_code = EscapeCode::CursorForward;
m_valid = true;
return true;
};
case 'D': {
m_escape_code = EscapeCode::CursorBack;
m_valid = true;
return true;
};
case 'E': {
m_escape_code = EscapeCode::CursorNextLine;
m_valid = true;
return true;
};
case 'F': {
m_escape_code = EscapeCode::CursorPreviousLine;
m_valid = true;
return true;
};
case 'G': {
m_escape_code = EscapeCode::CursorHorizontalAbsolute;
m_valid = true;
return true;
};
case 'H': {
m_escape_code = EscapeCode::SetCursorPosition;
m_valid = true;
return true;
};
case 'm': {
m_escape_code = EscapeCode::SelectGraphicRendition;
m_valid = true;
return true;
};
case ';': {
return false;
};
default: {
m_valid = false;
return true;
}
}
};
break;
case SequenceType::DCS: todo();
case SequenceType::OSC: todo();
default: todo();
}
}

270
libluna/src/Scanf.cpp Normal file
View File

@ -0,0 +1,270 @@
/**
* @file Scanf.cpp
* @author apio (cloudapio.eu)
* @brief Scanf implementation.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <luna/CString.h>
#include <luna/CType.h>
#include <luna/DebugLog.h>
#include <luna/Heap.h>
#include <luna/NumberParsing.h>
#include <luna/SystemError.h>
#include <sys/types.h>
#define FLAG_DISCARD (1 << 0)
#define FLAG_ALLOC (1 << 1)
#define FLAG_WIDTH (1 << 2)
#define FLAG_LONG (1 << 3)
#define FLAG_LONG_LONG (1 << 4)
#define FLAG_SHORT (1 << 5)
#define FLAG_CHAR (1 << 6)
static int parse_flags(const char** format)
{
int result = 0;
while (true)
{
switch (**format)
{
case '*':
result |= FLAG_DISCARD;
(*format)++;
break;
case 'm':
result |= FLAG_ALLOC;
(*format)++;
break;
default: return result;
}
}
}
static size_t parse_width(const char** format, int& flags)
{
size_t result = 0;
if (_isdigit(**format))
{
result = scan_unsigned_integer(format);
flags |= FLAG_WIDTH;
}
return result;
}
static void parse_type(const char** format, int& flags)
{
// FIXME: Support %j (intmax_t/uintmax_t)
switch (**format)
{
case 'h':
flags |= FLAG_SHORT;
(*format)++;
if (**format == 'h')
{
flags |= FLAG_CHAR;
(*format)++;
}
break;
case 'l':
flags |= FLAG_LONG;
(*format)++;
if (**format == 'l')
{
flags |= FLAG_LONG_LONG;
(*format)++;
}
break;
case 't':
flags |= (sizeof(ptrdiff_t) == sizeof(long)) ? FLAG_LONG : FLAG_LONG_LONG;
(*format)++;
break;
case 'z':
flags |= (sizeof(size_t) == sizeof(long)) ? FLAG_LONG : FLAG_LONG_LONG;
(*format)++;
break;
default: break;
}
}
static void write_parsed_signed_integer(ssize_t value, int flags, va_list ap)
{
if (flags & FLAG_LONG_LONG) *va_arg(ap, signed long long*) = (signed long long)value;
else if (flags & FLAG_LONG)
*va_arg(ap, signed long*) = (signed long)value;
else if (flags & FLAG_SHORT)
*va_arg(ap, signed int*) = (signed short)value;
else if (flags & FLAG_CHAR)
*va_arg(ap, signed int*) = (signed char)value;
else
*va_arg(ap, signed int*) = (signed int)value;
}
static void write_parsed_unsigned_integer(size_t value, int flags, va_list ap)
{
if (flags & FLAG_LONG_LONG) *va_arg(ap, unsigned long long*) = (unsigned long long)value;
else if (flags & FLAG_LONG)
*va_arg(ap, unsigned long*) = (unsigned long)value;
else if (flags & FLAG_SHORT)
*va_arg(ap, unsigned int*) = (unsigned short)value;
else if (flags & FLAG_CHAR)
*va_arg(ap, unsigned int*) = (unsigned char)value;
else
*va_arg(ap, unsigned int*) = (unsigned int)value;
}
#define WHITESPACE_CHARACTERS " \t\f\r\n\v"
static void skip_whitespace(const char** str)
{
*str += strspn(*str, WHITESPACE_CHARACTERS);
}
int scanf_impl(const char* str, const char* format, va_list ap)
{
int parsed = 0;
const char* s = str; // Keep a pointer to the beginning of the string for %n
if (*str == 0) return -1;
while (*format)
{
if (*format != '%')
{
normal:
if (!_isspace(*format))
{
if (*str != *format) return parsed;
str++;
format++;
if (*str == 0) return parsed;
continue;
}
skip_whitespace(&format);
skip_whitespace(&str);
if (*str == 0) return parsed;
continue;
}
else
{
format++;
if (*format == '%')
{
skip_whitespace(&str);
goto normal;
}
int flags = parse_flags(&format);
size_t width = parse_width(&format, flags);
parse_type(&format, flags);
char specifier = *format++;
if (!specifier) return parsed;
switch (specifier)
{
case 's': {
skip_whitespace(&str);
size_t chars = strcspn(str, WHITESPACE_CHARACTERS);
if (!chars) return parsed;
if ((flags & FLAG_WIDTH) && chars > width) chars = width;
if (!(flags & FLAG_DISCARD))
{
char* ptr;
if (flags & FLAG_ALLOC)
{
ptr = (char*)malloc_impl(chars + 1).value_or(nullptr);
if (!ptr) return parsed;
*va_arg(ap, char**) = ptr;
}
else
ptr = va_arg(ap, char*);
memcpy(ptr, str, chars);
ptr[chars] = 0;
}
str += chars;
parsed++;
break;
}
case 'c': {
if (strlen(str) < width) return parsed;
if (!(flags & FLAG_WIDTH)) width = 1;
if (!(flags & FLAG_DISCARD))
{
char* ptr;
if (flags & FLAG_ALLOC)
{
ptr = (char*)malloc_impl(width).value_or(nullptr);
if (!ptr) return parsed;
*va_arg(ap, char**) = ptr;
}
else
ptr = va_arg(ap, char*);
memcpy(ptr, str, width);
}
str += width;
parsed++;
break;
}
case 'd': {
skip_whitespace(&str);
ssize_t value = scan_signed_integer(&str, 10);
if (!(flags & FLAG_DISCARD)) write_parsed_signed_integer(value, flags, ap);
parsed++;
break;
}
case 'i': {
skip_whitespace(&str);
ssize_t value = scan_signed_integer(&str, 0);
if (!(flags & FLAG_DISCARD)) write_parsed_signed_integer(value, flags, ap);
parsed++;
break;
}
case 'o': {
skip_whitespace(&str);
size_t value = scan_unsigned_integer(&str, 8);
if (!(flags & FLAG_DISCARD)) write_parsed_unsigned_integer(value, flags, ap);
parsed++;
break;
}
case 'u': {
skip_whitespace(&str);
size_t value = scan_unsigned_integer(&str, 10);
if (!(flags & FLAG_DISCARD)) write_parsed_unsigned_integer(value, flags, ap);
parsed++;
break;
}
case 'X':
case 'x': {
skip_whitespace(&str);
size_t value = scan_unsigned_integer(&str, 16);
if (!(flags & FLAG_DISCARD)) write_parsed_unsigned_integer(value, flags, ap);
parsed++;
break;
}
case 'p': {
skip_whitespace(&str);
size_t value = scan_unsigned_integer(&str, 16);
if (!(flags & FLAG_DISCARD)) *va_arg(ap, void**) = (void*)value;
parsed++;
break;
}
case 'n': {
if (!(flags & FLAG_DISCARD)) *va_arg(ap, int*) = (int)(str - s);
break;
}
default: {
dbgln("vsscanf: unknown conversion specifier: %%%c\n", specifier);
return parsed;
}
}
}
}
return parsed;
}

View File

@ -133,7 +133,8 @@ Result<int> luna_main(int argc, char** argv)
if (interactive) if (interactive)
{ {
auto cwd = TRY(os::FileSystem::working_directory()); auto cwd = TRY(os::FileSystem::working_directory());
os::print("%s@%s:%s%c ", username, hostname, cwd.chars(), prompt_end); os::print("\x1b[%dm%s\x1b[m@\x1b[36m%s\x1b[m:\x1b[1;34m%s\x1b[m%c ", getuid() == 0 ? 31 : 35, username,
hostname, cwd.chars(), prompt_end);
} }
auto maybe_cmd = input_file->read_line(); auto maybe_cmd = input_file->read_line();