// Bit Tool v0.1.0 //
/*
Used for showing raw bits without reading thousands of pages of documentation.
*/
// was for practicing but turned out to be real
// Copyright C 2026 Johnryzon Z. Abejero
// ==========> MIT License <==========
// </> Code --->
#include <stdio.h> // for printing and inputting
#include <stdlib.h> // for malloc and free
// shouldn't include <malloc.h> since it's deprecated
// MACROS
// Just some flavors :)
#define print printf // aliases printf to print
// using func instead of typing the return type directly
#define func(type) type
// define custom boolean macros to prevent including <stdbool.h>
#define bool _Bool
// in programming booleans are just integers
#define true 1 // true is equal to 1
#define false 0 // false is equal to 0
// ^MACROS^
// The "Label"
/* has the needed token types for main to
know what the type was without comparing it's lexeme */
typedef enum TokenType { // most of this types here are operators
// Don't mind to read it
number, bitshift_left, bitshift_right, bit_xor,
bit_not, bit_or, bit_and, plus, minus, times, divide,
greater, less, /* ------> */ scan_error // <--- for error reporting
} TokenType; // enum
// Token structure
// Used by the lexer
typedef struct Token { // -> struct
// a type label instead of comparing strings which is slow
TokenType type;
// stores the line number which is useful for error reporting
int line;
/* stores the length of the lexeme (useful to print the lexeme
without printing text from the start of the token to the very end) */
int length;
// stores the "lexeme" from the start of the token to the very end
char* lexeme;
// Optional: for error messages
char* message; // stores error messages
} Token; // struct
// Holder for the scanner
typedef struct Scanner { // -> struct
char* start; // The start for the token
char* current; // the current character (The moving pointer)
int line; // The current line -- For error reporting
}
Scanner; // struct
// for moving the current character
func(void) nextChar(Scanner* scanner) { // -> none(void)
scanner->current++; // add 1 to current's memory address
} // func(void) nextChar(Scanner* scanner)
// for looking at the current character
func(char) peek(Scanner* scanner) { // -> character
if (!scanner) { // if scanner was not initialized
// return the none character '\0' and print a message
print("BUG: Scanner was not initialized."); // SECURITY feature
return '\0'; // return none
} // if (!scanner)
return *scanner->current; // return the current character
} // func(char) peek(Scanner* scanner)
// for looking at the next character without moving current
func(char) peekNext(Scanner* scanner) {
if (peek(scanner) == '\0') { // if the current character is at the end
return '\0'; // return nothing to stop segfaults
} // if (peek(scanner) == '\0')
// using '+ 1' instead of just '[1]'
return *(scanner->current + 1); // a more pointer world
} // func(char) peekNext(Scanner* scanner)
/* move the current character and
return true if the next character is the same as the expected character
or else just return false
*/
func(bool) match(Scanner* scanner, char expected) {
// if the next character is equal to the given expected character
if (peekNext(scanner) == expected) {
// move the current pointer
nextChar(scanner);
nextChar(scanner);
// and return true
return true;
}
// but if it not then return false
return false;
}
// Checks if the given character is a number
func(bool) isnum(char c) {
if (c >= '0' && c <= '9')
return true;
return false;
}
// For comparing strings without loading the huge cstring.h header
func(bool) strcompr(char* str1, char* str2) { // -> bool
// while the first string character is not in the end and also the second string
while (*str1 != '\0' && *str2 != '\0') {
/* if the current character in string 1 is not equal
to the curent character in string 2 then return false */
if (*str1 != *str2) return false; // return false
// increment string 1 and string 2's current character
str1++; // add 1
str2++; // add 1
} // while (*str1 != '\0 && *str2 != '\0')
// return true only if string 1 and string 2 stopped at the same length
return (*str1 == '\0' && *str2 == '\0'); // give the result
} // func(bool) strcompr(char* str1, char* str2)
// Converts a token to a number
func(double) token_to_num(Token* token) { // -> double
// declare result to 0
double result = 0;
// used for tracking the decimal
double fraction = 0.1;
// for telling the program if a number has a decimal
bool hasdot = false;
// a loop from 0 to the token's length
for (int i = 0; i < token->length; i++) {
// set c to the current lexeme
char c = token->lexeme[i]; // no need to type the whole 'token->lexeme[i]' everytime
// check for a dot
if (c == '.') {
// set hasdot to true
hasdot = true;
continue; // skip the rest of the code and loop back
} // if (c == '.')
// if it doesn't have a dot
if (!hasdot) {
// set result by result * 10 and add the character - hex of '0'
result = (result * 10) + (c - '0'); // convert to number
} else { // but if has a decimal point
// result will be result + c - hex of '0' * the location of the decimal
result = result + (c - '0') * fraction; /* convert to number and add
fraction to turn it to decimal */
fraction /= 10.0; // divide fraction by 10
} // if (!hasdot) {} else
} // for (int i = 0; i < token->length; i++)
// give back result
return result; // return
} // func(double) token_to_num(Token* token)
// Creates a new token, set needed information and gives it back
func(Token) createToken(Scanner* scanner, TokenType type) { // -> Token
// Create an uninitialized tokem
Token token; // uninitialized
// set the token's type to the given type
token.type = type; // type to given type
// set the line to the scanner's current line
token.line = scanner->line; // line to scanner's line
// set the length by subtracting current's memory address and start's memory address
token.length = scanner->current - scanner->start; // length to current - start
// store start to the lexeme
token.lexeme = scanner->start; // lexeme to start
// set message to nothing since we don't use it on regular tokens
token.message = ""; // none
// return the initilized token
return token; // return
} // func(Token) createToken(Scanner* scanner, TokenType type)
/* creates a token, put needed things in it
(including the error message) and gives it back */
func(Token) scanError(Scanner* scanner, char* message) { // -> Token
// Create an uninitialized tokem
Token token; // uninitialized
// set the token's type to error mode
token.type = scan_error; // type to error mode
// set the line to the scanner's current line
token.line = scanner->line; // line to scanner's line
// set the length by subtracting current's memory address and start's memory address
token.length = scanner->current - scanner->start; // length by the current - start
// store start to the lexeme
token.lexeme = scanner->start; // lexeme to start
// set message to the given message
token.message = message; // message to the given message
// return the initialized token
return token; // give it back
} // func(Token) scanError(Scanner* scanner, char* message)
// initilizes the scanner
func(void) initScanner(Scanner* scanner, char* src) { // -> none
scanner->start = src; // start to given source
scanner->current = src; // current to given source
scanner->line = 1; // line to 1
} // func(void) initScanner(Scanner* scanner, char* src)
// Skips spaces/other things that is not needed
func(void) skipSpaces(Scanner* scanner) { // -> none
// set an infinite loop
while (1) {
// store the current char at c
char c = peek(scanner);
// switch instead of a giant if and else statements
switch (c) {
// check for a space, a tab and a carriage return(enter) and if found then skip it
case ' ':
case '\t':
case '\r':
nextChar(scanner);
break;
// but if found a newline skip it and add 1 to line
case '\n':
nextChar(scanner);
scanner->line++;
break;
// for the default i just return so no other symbol bugs
default:
return;
}
}
}
// scans a single token
func(Token) scanToken(Scanner* scanner) {
// set start to the current character
scanner->start = scanner->current;
// use c instead of using peek everytime
char c = peek(scanner);
// set a switch
switch (c) {
case '+':
nextChar(scanner);
return createToken(scanner, plus);
break;
case '-':
nextChar(scanner);
return createToken(scanner, minus);
break;
case '*':
nextChar(scanner);
return createToken(scanner, times);
break;
case '/':
nextChar(scanner);
return createToken(scanner, divide);
break;
case '<':
if (match(scanner, '<'))
return createToken(scanner, bitshift_left);
nextChar(scanner);
return createToken(scanner, less);
case '>':
if (match(scanner, '>'))
return createToken(scanner, bitshift_right);
nextChar(scanner);
return createToken(scanner, greater);
case '|':
nextChar(scanner);
return createToken(scanner, bit_or);
case '&':
nextChar(scanner);
return createToken(scanner, bit_and);
case '^':
nextChar(scanner);
return createToken(scanner, bit_xor);
case '~':
nextChar(scanner);
return createToken(scanner, bit_not);
default: {
if (isnum(c)) {
while(isnum(peek(scanner))) nextChar(scanner);
// if the current character is a dot consume it
if (peek(scanner) == '.') {
// consume '.'
nextChar(scanner);
while (isnum(peek(scanner))) nextChar(scanner);
}
return createToken(scanner, number);
}
nextChar(scanner);
return scanError(scanner, "Unexpected character.");
}
}
return scanError(scanner, "BUG: Switch didn't run for some reason.");
}
// Shows the moving of bytes in an operation
func(void) visualBits(double n) {
print("=========== Visual bits ==========\n");
print("<---------------------------------\n");
for (int i = 31; i >= 0; i--) {
int bit = ((int)n >> i) & 1;
print("%d", bit);
if (i % 8 == 0) print(" ");
}
print("\n");
}
// Shows the raw bytes of a number
func(void) rawBytes(double n) {
unsigned char *p = (unsigned char *)&n;
for (size_t i = 0; i < sizeof(double); i++) {
printf("%02X ", p[i]);
}
printf("\n");
}
func(void) REPL(Scanner* scanner) {
double result = 0;
bool hasleft = false;
bool didMath = false;
bool isCond = false; // is it a Condition like >
TokenType currentOp;
while (peek(scanner) != '\0' && peek(scanner) != '\n') {
skipSpaces(scanner);
Token tok = scanToken(scanner);
if (tok.type == scan_error) {
print("Error: %s\n", tok.message);
return;
}
if (tok.type == number) {
double val = token_to_num(&tok);
if (currentOp == bit_not) {
val = ~(int)val;
didMath = true;
currentOp = scan_error;
}
if (!hasleft) {
result = val;
hasleft = true;
} else {
isCond = true;
hasleft = false;
if (currentOp == greater) {
result = result > token_to_num(&tok);
if (result) print("Result: True\n");
else print("Result: False\n");
} else if (currentOp == less) {
result = result < token_to_num(&tok);
if (result) print("Result: True\n");
else print("Result: False\n");
}
if (currentOp != greater && currentOp != less) {
isCond = false;
didMath = true;
hasleft = true;
}
if (currentOp == plus) result += val;
else if (currentOp == minus) result -= val;
else if (currentOp == times) result *= val;
else if (currentOp == divide) result /= val;
else if (currentOp == bit_and) result = (int)result & (int)val;
else if (currentOp == bit_or) result = (int)result | (int)val;
else if (currentOp == bitshift_left) result = (int)result << (int)val;
else if (currentOp == bitshift_right) result = (int)result >> (int)val;
else if (currentOp == bit_xor) result = (int)result ^ (int)val;
}
} else {
currentOp = tok.type;
}
}
if (didMath) {
print("Warning: Bit operators don't allow decimals.\n\
Note: Using decimal numbers may cause bittool to round it off.\n\n");
visualBits(result);
print("Result: %lg\n", result);
print("Result (hex): 0x%X\n", (unsigned int)result);
print("Result (scientific hex): %a\n", result);
print("Result (raw bytes): ");
rawBytes(result);
} else if (hasleft) {
print("Number: %lg\n", result);
print("Number (hex): 0x%X\n", (unsigned int)result);
print("Number (scientific hex): %a\n", result);
print("Number (raw bytes): ");
rawBytes(result);
} else if (!isCond) {
print("Warning: Operators cannot be on their own.\n");
}
}
// I got tired of making comments :)
func(int) main(int argc, char* argv[]) {
// misc
char input[100];
bool fromArgs = false;
Scanner* scanner = malloc(sizeof(Scanner));
if (argc > 2) {
print("Usage: bittool [command]\n");
return 1;
}
print("====== Bit Tool in C v0.1.0 ======\n");
print("- 64 bit representation\n");
if (argc == 2) fromArgs = true;
if (!fromArgs) {
print("Enter 'clear' to clear the screen and 'exit' to exit.\n");
print("Note: 'clear' may not work on ide terminals or some old terminals.\n");
} else if (fromArgs) {
initScanner(scanner, argv[1]);
REPL(scanner);
return 0;
}
while (true) {
// read
print(">> ");
fgets(input, sizeof(input), stdin);
// check for a newline to skip lexing
if (input[0] == '\n' || input[0] == '\0') continue;
// check for clear
if (strcompr(input, "clear\n")) {
// print the "magic" ASCII sequece
print("\033[H\033[2J");
continue;
} else if (strcompr(input, "exit\n")) { // check for exit
break; // exit the loop
}
initScanner(scanner, input);
REPL(scanner);
}
// get the memory back
free(scanner);
return 0;
}