First commit
CI / fmt (push) Has been cancelled
CI / clippy (push) Has been cancelled
CI / doc (push) Has been cancelled
CI / test macos-latest (push) Has been cancelled
CI / test windows-latest (push) Has been cancelled

This commit is contained in:
2026-08-08 18:06:13 +02:00
committed by surv
commit 0f1194aeca
18 changed files with 1068 additions and 0 deletions
+17
View File
@@ -0,0 +1,17 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
# Maintain dependencies for Cargo
- package-ecosystem: "cargo"
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
# Maintain dependencies for GitHub Actions
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
+90
View File
@@ -0,0 +1,90 @@
name: CI
on:
pull_request:
push:
branches:
- main
- master
- develop
env:
CARGO_TERM_COLOR: always
# ensure that the workflow is only triggered once per PR, subsequent pushes to the PR will cancel
# and restart the workflow. See https://docs.github.com/en/actions/using-jobs/using-concurrency
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
fmt:
name: fmt
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- name: check formatting
run: cargo fmt -- --check
- name: Cache Cargo dependencies
uses: Swatinem/rust-cache@v2
clippy:
name: clippy
runs-on: ubuntu-latest
permissions:
contents: read
checks: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- name: Run clippy action
uses: clechasseur/rs-clippy-check@v3
- name: Cache Cargo dependencies
uses: Swatinem/rust-cache@v2
doc:
# run docs generation on nightly rather than stable. This enables features like
# https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html which allows an
# API be documented as only available in some specific platforms.
name: doc
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@nightly
- name: Run cargo doc
run: cargo doc --no-deps --all-features
env:
RUSTDOCFLAGS: --cfg docsrs
test:
runs-on: ${{ matrix.os }}
name: test ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest]
steps:
# if your project needs OpenSSL, uncomment this to fix Windows builds.
# it's commented out by default as the install command takes 5-10m.
# - run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append
# if: runner.os == 'Windows'
# - run: vcpkg install openssl:x64-windows-static-md
# if: runner.os == 'Windows'
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
# enable this ci template to run regardless of whether the lockfile is checked in or not
- name: cargo generate-lockfile
if: hashFiles('Cargo.lock') == ''
run: cargo generate-lockfile
- name: cargo test --locked
run: cargo test --locked --all-features --all-targets
- name: Cache Cargo dependencies
uses: Swatinem/rust-cache@v2
+2
View File
@@ -0,0 +1,2 @@
target
Cargo.lock
+19
View File
@@ -0,0 +1,19 @@
[package]
name = "binocle"
version = "0.1.0"
description = "A TUI to do binary computations"
authors = ["Julien Thillard <julien.thillard38@gmail.com>"]
license = "MIT"
edition = "2024"
[dependencies]
color-eyre = "0.6"
crossterm = "0.29"
ratatui = "0.30"
winnow = "1.0.4"
[profile.release]
codegen-units = 1
lto = true
opt-level = "s"
strip = true
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) supersurviveur <julien.thillard38@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+14
View File
@@ -0,0 +1,14 @@
# binocle
This is a [Ratatui] app generated by the [Hello World template].
[Ratatui]: https://ratatui.rs
[Hello World Template]: https://github.com/ratatui/templates/tree/main/hello-world
## License
Copyright (c) supersurviveur <julien.thillard38@gmail.com>
This project is licensed under the MIT license ([LICENSE] or <http://opensource.org/licenses/MIT>)
[LICENSE]: ./LICENSE
+1
View File
@@ -0,0 +1 @@
edition = "2024"
+249
View File
@@ -0,0 +1,249 @@
use std::io;
use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind, MouseEvent};
use ratatui::{
DefaultTerminal, Frame,
layout::{Constraint, Direction, Layout, Rect},
style::Stylize,
symbols::border,
text::{Line, Text},
widgets::{Block, Paragraph, Widget},
};
use winnow::Parser;
use crate::{
binary::Binary, binary_repr::BinaryRepr, hexadecimal::Hexadecimal, parser, signed::Signed,
twos_complement::TwosComplement, unsigned::Unsigned,
};
#[derive(Debug, Default, PartialEq, Eq)]
pub enum Input {
#[default]
Number,
BitSize,
}
#[derive(Debug, Default)]
pub struct App {
exit: bool,
character_index: u16,
bit_index: u16,
input: String,
bit_size_input: String,
select: Input,
bit_size: usize,
parsed_input: BinaryRepr,
number_area: Rect,
size_area: Rect,
// Components
twos_complement: TwosComplement,
hexadecimal: Hexadecimal,
binary: Binary,
unsigned: Unsigned,
signed: Signed,
}
impl App {
pub fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> {
self.bit_size = 32;
self.bit_size_input = "32".to_string();
self.input = "0".to_string();
self.character_index = 1;
self.bit_index = 2;
while !self.exit {
terminal.draw(|frame| self.draw(frame))?;
self.handle_events()?;
}
Ok(())
}
fn draw(&mut self, frame: &mut Frame) {
let area = frame.area();
let buf = frame.buffer_mut();
let layout = Layout::new(
Direction::Vertical,
[Constraint::Fill(1), Constraint::Length(3)],
);
let [output_area, input_area] = area.layout(&layout);
let output_layout = Layout::new(
Direction::Vertical,
[
Constraint::Fill(1),
Constraint::Fill(1),
Constraint::Fill(1),
Constraint::Fill(1),
Constraint::Fill(1),
],
);
let [
twos_complement_area,
hexadecimal_area,
binary_area,
unsigned_area,
signed_area,
] = output_area.layout(&output_layout);
self.twos_complement.render(twos_complement_area, buf);
self.hexadecimal.render(hexadecimal_area, buf);
self.binary.render(binary_area, buf);
self.unsigned.render(unsigned_area, buf);
self.signed.render(signed_area, buf);
let input_layout = Layout::new(
Direction::Horizontal,
[Constraint::Fill(1), Constraint::Length(10)],
);
let [input_area, size_area] = input_area.layout(&input_layout);
let input_text = Text::from(self.input.clone().yellow());
let input_block = Block::bordered()
.title(Line::from("input".bold()))
.border_set(border::PLAIN);
Paragraph::new(input_text)
.block(input_block)
.render(input_area, buf);
let size_text = Text::from(self.bit_size_input.clone().yellow());
let size_block = Block::bordered()
.title(Line::from("size".bold()))
.border_set(border::PLAIN);
Paragraph::new(size_text)
.block(size_block)
.render(size_area, buf);
self.size_area = size_area;
self.number_area = input_area;
if self.select == Input::Number {
frame.set_cursor_position((input_area.x + 1 + self.character_index, input_area.y + 1));
} else if self.select == Input::BitSize {
frame.set_cursor_position((size_area.x + 1 + self.bit_index, size_area.y + 1));
}
}
fn handle_events(&mut self) -> io::Result<()> {
match event::read()? {
Event::Key(key_event) if key_event.kind == KeyEventKind::Press => {
self.handle_key_event(key_event);
}
Event::Mouse(mouse_event) => self.handle_mouse_event(mouse_event),
_ => {}
}
Ok(())
}
fn handle_key_event(&mut self, key_event: KeyEvent) {
match key_event.code {
KeyCode::Char('q') => self.exit(),
KeyCode::Left => {
if self.select == Input::Number {
self.character_index = self.character_index.saturating_sub(1);
} else if self.select == Input::BitSize {
self.bit_index = self.bit_index.saturating_sub(1);
}
}
KeyCode::Right => {
if self.select == Input::Number {
self.character_index += 1;
self.character_index = self
.character_index
.min(u16::try_from(self.input.len()).unwrap());
} else if self.select == Input::BitSize {
self.bit_index += 1;
self.bit_index = self
.bit_index
.min(u16::try_from(self.bit_size_input.len()).unwrap());
}
}
KeyCode::Char(to_insert) => self.enter_char(to_insert),
KeyCode::Backspace => self.delete_char(),
KeyCode::Tab => {
self.select = if self.select == Input::Number {
Input::BitSize
} else {
Input::Number
}
}
_ => {}
}
match key_event.code {
KeyCode::Char(_) | KeyCode::Backspace => self.update(),
_ => {}
}
}
const fn handle_mouse_event(&mut self, mouse_event: MouseEvent) {
let position = ratatui::layout::Position {
x: mouse_event.column,
y: mouse_event.row,
};
if let event::MouseEventKind::Down(_button) = mouse_event.kind {
if self.size_area.contains(position) {
self.select = Input::BitSize;
} else if self.number_area.contains(position) {
self.select = Input::Number;
}
}
}
fn enter_char(&mut self, input: char) {
if self.select == Input::Number {
self.input.insert(self.character_index as usize, input);
self.character_index += 1;
} else if self.select == Input::BitSize && input.is_ascii_digit() {
self.bit_size_input.insert(self.bit_index as usize, input);
self.bit_index += 1;
}
}
fn delete_char(&mut self) {
if self.select == Input::Number {
let is_not_cursor_leftmost = self.character_index != 0;
if is_not_cursor_leftmost {
self.character_index -= 1;
self.input.remove(self.character_index as usize);
}
} else if self.select == Input::BitSize {
let is_not_cursor_leftmost = self.bit_index != 0;
if is_not_cursor_leftmost {
self.bit_index -= 1;
self.bit_size_input.remove(self.bit_index as usize);
}
}
}
fn update(&mut self) {
self.bit_size = self.bit_size_input.parse().unwrap_or(0);
let mut parser = parser::parser(self.bit_size);
if !self.input.is_empty()
&& let Ok(input) = parser.parse(&self.input)
{
self.parsed_input = input.into_binary_repr();
} else {
return;
}
self.twos_complement.update(&self.parsed_input);
self.hexadecimal.update(&self.parsed_input);
self.binary.update(&self.parsed_input);
self.unsigned.update(&self.parsed_input);
self.signed.update(&self.parsed_input);
}
const fn exit(&mut self) {
self.exit = true;
}
}
+38
View File
@@ -0,0 +1,38 @@
use ratatui::{
buffer::Buffer,
layout::Rect,
style::Stylize,
symbols::border,
text::Line,
widgets::{Block, Paragraph, Widget, Wrap},
};
use crate::{binary_repr::BinaryRepr, color::colorize};
#[derive(Debug, Default)]
pub struct Binary {
repr: BinaryRepr,
}
impl Binary {
pub fn update(&mut self, input: &BinaryRepr) {
self.repr = input.clone();
}
}
impl Widget for &Binary {
fn render(self, area: Rect, buf: &mut Buffer) {
let input_block = Block::bordered()
.title(Line::from("binary".bold()))
.border_set(border::PLAIN);
let mut line = Line::from("0b".gray());
let mut binding = self.repr.to_binary();
colorize(&binding, 8, &mut line);
Paragraph::new(line)
.wrap(Wrap { trim: false })
.block(input_block)
.render(area, buf);
}
}
+336
View File
@@ -0,0 +1,336 @@
use std::ops::{Add, Mul, Neg, Not, Shl, Sub};
use winnow::{
Parser,
ascii::{digit1, hex_digit1, oct_digit1},
combinator::{alt, preceded, repeat},
error::ContextError,
};
#[derive(Debug, Default, Clone)]
pub struct BinaryRepr {
pub bits: Vec<bool>,
pub size: usize,
}
impl BinaryRepr {
pub const fn new(bits: Vec<bool>, size: usize) -> Self {
Self { bits, size }
}
pub fn to_hex(&self) -> String {
let mut hex = String::new();
for chunk in self.bits.chunks(4) {
let nibble = chunk
.iter()
.enumerate()
.fold(0, |acc, (i, bit)| acc + (u8::from(*bit) << i));
hex.insert(
0,
[
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
][nibble as usize],
);
}
hex
}
pub fn to_binary(&self) -> String {
let mut bin = String::new();
for bit in self.bits.iter().rev() {
if *bit {
bin.push('1');
} else {
bin.push('0');
}
}
bin
}
pub fn to_unsigned(&self) -> String {
let mut digits: Vec<u8> = vec![0];
for bit in self.bits.iter().rev() {
let mut carry = u8::from(*bit);
for digit in &mut digits {
let value = *digit * 2 + carry;
*digit = value % 10;
carry = value / 10;
}
while carry > 0 {
digits.push(carry % 10);
carry /= 10;
}
}
while digits.len() > 1 && *digits.last().unwrap() == 0 {
digits.pop();
}
digits
.iter()
.rev()
.map(|digit| (digit + b'0') as char)
.collect()
}
pub fn to_signed(&self) -> String {
let negative = self.size != 0 && self.bits[self.size - 1];
// For negatives, take the two's-complement negation (invert + 1)
// over the full width to recover the true magnitude bits.
let mag_bits: Vec<bool> = if negative {
let mut inverted: Vec<bool> = self.bits[..self.size].iter().map(|b| !b).collect();
let mut carry = true; // adding 1
for bit in &mut inverted {
let sum = u8::from(*bit) + u8::from(carry);
*bit = sum & 1 == 1;
carry = sum > 1;
}
inverted
} else {
self.bits[..self.size].to_vec()
};
let mut digits: Vec<u8> = vec![0];
for bit in mag_bits.iter().rev() {
let mut carry = u8::from(*bit);
for digit in &mut digits {
let value = *digit * 2 + carry;
*digit = value % 10;
carry = value / 10;
}
while carry > 0 {
digits.push(carry % 10);
carry /= 10;
}
}
while digits.len() > 1 && *digits.last().unwrap() == 0 {
digits.pop();
}
let sign = if negative { "-" } else { "" };
sign.to_owned()
+ &digits
.iter()
.rev()
.map(|digit| (digit + b'0') as char)
.collect::<String>()
}
pub fn from_u128(mut value: u128, size: usize) -> Self {
Self::new(
(0..size)
.map(|_| {
let out = value & 1 != 0;
value >>= 1;
out
})
.collect(),
size,
)
}
}
impl Not for BinaryRepr {
type Output = Self;
fn not(self) -> Self::Output {
Self::new(self.bits.iter().map(|a| !a).collect(), self.size)
}
}
impl Not for &BinaryRepr {
type Output = BinaryRepr;
fn not(self) -> Self::Output {
!self.clone()
}
}
impl Neg for BinaryRepr {
type Output = Self;
fn neg(self) -> Self::Output {
let size = self.size;
!self + Self::from_u128(1, size)
}
}
impl Add<&Self> for BinaryRepr {
type Output = Self;
fn add(mut self, rhs: &Self) -> Self::Output {
assert_eq!(self.size, rhs.size);
let mut carry = false;
self.bits
.iter_mut()
.zip(rhs.bits.iter())
.for_each(|(a, b)| {
let half = *a ^ b;
let out = half ^ carry;
carry = (*a & b) || (half & carry);
*a = out;
});
self
}
}
impl Add for BinaryRepr {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
self + &rhs
}
}
impl Add<u8> for BinaryRepr {
type Output = Self;
fn add(self, rhs: u8) -> Self::Output {
let size = self.size;
self + Self::from_u128(u128::from(rhs), size)
}
}
impl Sub<&Self> for BinaryRepr {
type Output = Self;
fn sub(self, rhs: &Self) -> Self::Output {
assert_eq!(self.size, rhs.size);
let size = self.size;
self + &!rhs + Self::from_u128(1, size)
}
}
impl Sub for BinaryRepr {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
self - &rhs
}
}
impl Shl<usize> for BinaryRepr {
type Output = Self;
fn shl(mut self, rhs: usize) -> Self::Output {
for _ in 0..rhs {
self.bits.insert(0, false);
}
self
}
}
impl Mul<&Self> for BinaryRepr {
type Output = Self;
fn mul(self, rhs: &Self) -> Self::Output {
assert_eq!(self.size, rhs.size);
let mut result = Self::new(vec![false; self.size], self.size);
for i in 0..self.size {
if rhs.bits[i] {
result = result + (self.clone() << i);
}
}
result
}
}
impl Mul for BinaryRepr {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
self * &rhs
}
}
impl Mul<usize> for BinaryRepr {
type Output = Self;
fn mul(self, rhs: usize) -> Self::Output {
let mut result = Self::new(vec![false; self.size], self.size);
for i in 0..usize::BITS - rhs.leading_zeros() {
if rhs & (1 << i) != 0 {
result = result + (self.clone() << (i as usize));
}
}
result
}
}
impl BinaryRepr {
pub fn twos_complement(&self) -> Self {
!self + Self::from_u128(1, self.size)
}
}
pub fn parse_binary_repr<'i>(size: usize) -> impl Parser<&'i str, BinaryRepr, ContextError> {
move |input: &mut &str| {
alt((
preceded("0x", hex_digit1).map(|t: &str| {
let size = size.max(t.len() * 4);
let len = t.len() * 4;
let mut vec = vec![false; size];
t.chars().enumerate().for_each(|(i, c)| {
let nibble = if c <= '9' {
c as u8 - b'0'
} else {
c.to_lowercase().next().unwrap() as u8 - b'a' + 10
};
vec[len - i * 4 - 4] = nibble & 0b0001 == 1;
vec[len - i * 4 - 3] = nibble & 0b0010 == 2;
vec[len - i * 4 - 2] = nibble & 0b0100 == 4;
vec[len - i * 4 - 1] = nibble & 0b1000 == 8;
});
BinaryRepr::new(vec, size)
}),
preceded("0o", oct_digit1).map(|t: &str| {
let size = size.max(t.len() * 3);
let len = t.len() * 3;
let mut vec = vec![false; size];
t.chars().enumerate().for_each(|(i, c)| {
let nibble = c as u8 - b'0';
vec[len - i * 3 - 3] = nibble & 0b0001 == 1;
vec[len - i * 3 - 2] = nibble & 0b0010 == 2;
vec[len - i * 3 - 1] = nibble & 0b0100 == 4;
});
BinaryRepr::new(vec, size)
}),
preceded(
"0b",
repeat(1.., alt(('0', '1'))).map(|t: Vec<_>| {
let size = size.max(t.len());
let len = t.len();
let mut vec = vec![false; size];
t.into_iter()
.enumerate()
.for_each(|(i, c)| vec[len - i - 1] = c == '1');
BinaryRepr::new(vec, size)
}),
),
digit1.map(|t: &str| {
let mut result = BinaryRepr::new(vec![false; size], size);
for digit in t.chars() {
result = result * 10 + (digit as u8 - b'0');
}
result
}),
))
.parse_next(input)
}
}
+19
View File
@@ -0,0 +1,19 @@
use ratatui::{style::Stylize, text::Line};
pub fn colorize<'a>(mut txt: &'a str, width: usize, out: &mut Line<'a>) {
let mut color = false;
if !txt.len().is_multiple_of(width) {
*out += txt[..txt.len() % width].red();
color = !color;
txt = &txt[txt.len() % width..];
}
for _ in 0..txt.len() / width {
if color {
*out += txt[0..width].cyan();
} else {
*out += txt[0..width].red();
}
txt = &txt[width..];
color = !color;
}
}
+43
View File
@@ -0,0 +1,43 @@
use std::ops::{Add, Mul, Neg, Not, Sub};
use crate::binary_repr::BinaryRepr;
pub enum BinOp {
Plus,
Sub,
Mul,
Div,
}
pub enum UnaryOp {
Not,
Neg,
}
pub enum Expr {
Constant(BinaryRepr),
BinaryOp(Box<Self>, BinOp, Box<Self>),
UnaryOp(UnaryOp, Box<Self>),
Group(Box<Self>),
}
impl Expr {
pub fn into_binary_repr(self) -> BinaryRepr {
match self {
Self::Constant(binary_repr) => binary_repr,
Self::BinaryOp(expr, bin_op, expr1) => {
(match bin_op {
BinOp::Plus => <BinaryRepr as Add>::add,
BinOp::Sub => <BinaryRepr as Sub>::sub,
BinOp::Mul => <BinaryRepr as Mul>::mul,
BinOp::Div => todo!(),
})(expr.into_binary_repr(), expr1.into_binary_repr())
}
Self::UnaryOp(unary_op, expr) => (match unary_op {
UnaryOp::Neg => <BinaryRepr as Neg>::neg,
UnaryOp::Not => <BinaryRepr as Not>::not,
})(expr.into_binary_repr()),
Self::Group(group) => group.into_binary_repr(),
}
}
}
+38
View File
@@ -0,0 +1,38 @@
use ratatui::{
buffer::Buffer,
layout::Rect,
style::Stylize,
symbols::border,
text::Line,
widgets::{Block, Paragraph, Widget, Wrap},
};
use crate::{binary_repr::BinaryRepr, color::colorize};
#[derive(Debug, Default)]
pub struct Hexadecimal {
repr: BinaryRepr,
}
impl Hexadecimal {
pub fn update(&mut self, input: &BinaryRepr) {
self.repr = input.clone();
}
}
impl Widget for &Hexadecimal {
fn render(self, area: Rect, buf: &mut Buffer) {
let input_block = Block::bordered()
.title(Line::from("hexadecimal".bold()))
.border_set(border::PLAIN);
let mut line = Line::from("0x".gray());
let binding = self.repr.to_hex();
colorize(&binding, 2, &mut line);
Paragraph::new(line)
.wrap(Wrap { trim: false })
.block(input_block)
.render(area, buf);
}
}
+28
View File
@@ -0,0 +1,28 @@
#![deny(clippy::nursery, clippy::pedantic)]
use std::io::stdout;
use crate::app::App;
mod app;
mod binary_repr;
mod expr;
mod hexadecimal;
mod parser;
mod signed;
mod twos_complement;
mod unsigned;
mod binary;
mod color;
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
crossterm::execute!(stdout(), crossterm::event::EnableMouseCapture)?;
ratatui::run(|terminal| App::default().run(terminal))?;
exit()
}
fn exit() -> color_eyre::Result<()> {
crossterm::execute!(stdout(), crossterm::event::DisableMouseCapture)?;
Ok(())
}
+45
View File
@@ -0,0 +1,45 @@
use winnow::Parser;
use winnow::ascii::multispace0;
use winnow::combinator::{Infix, Prefix, peek};
use winnow::combinator::{delimited, dispatch, expression, fail};
use winnow::error::ContextError;
use winnow::token::any;
use crate::binary_repr::parse_binary_repr;
use crate::expr::{BinOp, Expr, UnaryOp};
pub fn parser<'i>(size: usize) -> impl Parser<&'i str, Expr, ContextError> {
move |i: &mut &str| {
use Infix::Left;
expression(delimited(
multispace0,
dispatch! {peek(any);
'(' => delimited('(', parser(size).map(|e| Expr::Group(Box::new(e))), ')'),
_ => parse_binary_repr(size).map(Expr::Constant),
},
multispace0,
))
.prefix(delimited(
multispace0,
dispatch! {any;
'-' => Prefix(12, |_, a| Ok(Expr::UnaryOp(UnaryOp::Neg, Box::new(a)))),
'!' => Prefix(12, |_, a| Ok(Expr::UnaryOp(UnaryOp::Not, Box::new(a)))),
_ => fail,
},
multispace0,
))
.infix(delimited(
multispace0,
dispatch! {any;
'+' => Left(5, |_, a, b| Ok(Expr::BinaryOp(Box::new(a), BinOp::Plus, Box::new(b)))),
'-' => Left(5, |_, a, b| Ok(Expr::BinaryOp(Box::new(a), BinOp::Sub, Box::new(b)))),
'*' => Left(7, |_, a, b| Ok(Expr::BinaryOp(Box::new(a), BinOp::Mul, Box::new(b)))),
'/' => Left(7, |_, a, b| Ok(Expr::BinaryOp(Box::new(a), BinOp::Div, Box::new(b)))),
_ => fail,
},
multispace0,
))
.parse_next(i)
}
}
+35
View File
@@ -0,0 +1,35 @@
use ratatui::{
buffer::Buffer,
layout::Rect,
style::Stylize,
symbols::border,
text::{Line, Text},
widgets::{Block, Paragraph, Widget},
};
use crate::binary_repr::BinaryRepr;
#[derive(Debug, Default)]
pub struct Signed {
repr: BinaryRepr,
}
impl Signed {
pub fn update(&mut self, input: &BinaryRepr) {
self.repr = input.clone();
}
}
impl Widget for &Signed {
fn render(self, area: Rect, buf: &mut Buffer) {
let input_text = Text::from(self.repr.to_signed().yellow());
let input_block = Block::bordered()
.title(Line::from("signed".bold()))
.border_set(border::PLAIN);
Paragraph::new(input_text)
.block(input_block)
.render(area, buf);
}
}
+38
View File
@@ -0,0 +1,38 @@
use ratatui::{
buffer::Buffer,
layout::Rect,
style::Stylize,
symbols::border,
text::Line,
widgets::{Block, Paragraph, Widget, Wrap},
};
use crate::{binary_repr::BinaryRepr, color::colorize};
#[derive(Debug, Default)]
pub struct TwosComplement {
repr: BinaryRepr,
}
impl TwosComplement {
pub fn update(&mut self, input: &BinaryRepr) {
self.repr = input.twos_complement();
}
}
impl Widget for &TwosComplement {
fn render(self, area: Rect, buf: &mut Buffer) {
let input_block = Block::bordered()
.title(Line::from("2's complement".bold()))
.border_set(border::PLAIN);
let mut line = Line::from("0x".gray());
let binding = self.repr.to_hex();
colorize(&binding, 2, &mut line);
Paragraph::new(line)
.wrap(Wrap { trim: false })
.block(input_block)
.render(area, buf);
}
}
+35
View File
@@ -0,0 +1,35 @@
use ratatui::{
buffer::Buffer,
layout::Rect,
style::Stylize,
symbols::border,
text::{Line, Text},
widgets::{Block, Paragraph, Widget},
};
use crate::binary_repr::BinaryRepr;
#[derive(Debug, Default)]
pub struct Unsigned {
repr: BinaryRepr,
}
impl Unsigned {
pub fn update(&mut self, input: &BinaryRepr) {
self.repr = input.clone();
}
}
impl Widget for &Unsigned {
fn render(self, area: Rect, buf: &mut Buffer) {
let input_text = Text::from(self.repr.to_unsigned().yellow());
let input_block = Block::bordered()
.title(Line::from("unsigned".bold()))
.border_set(border::PLAIN);
Paragraph::new(input_text)
.block(input_block)
.render(area, buf);
}
}