First commit
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
target
|
||||
Cargo.lock
|
||||
+19
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
edition = "2024"
|
||||
+249
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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(())
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user