Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
MDLC01 committed Mar 19, 2024
0 parents commit 5814f62
Show file tree
Hide file tree
Showing 27 changed files with 1,532 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.vscode/
2 changes: 2 additions & 0 deletions plugin/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[build]
target = "wasm32-unknown-unknown"
3 changes: 3 additions & 0 deletions plugin/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.idea/

target/
54 changes: 54 additions & 0 deletions plugin/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions plugin/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "board-n-pieces-plugin"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-minimal-protocol = { git = "https://github.com/astrale-sharp/wasm-minimal-protocol", rev = "637508c" }

[profile.release]
lto = true
strip = true
opt-level = "z"
codegen-units = 1
panic = "abort"
173 changes: 173 additions & 0 deletions plugin/src/abi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
use std::borrow::Cow;
use std::iter;

pub type Result<T> = std::result::Result<T, String>;

pub trait Abi: Sized {
fn descriptor() -> Cow<'static, str>;

fn from_bytes(bytes: &mut impl Iterator<Item = u8>) -> Result<Self>;

fn from_slice(slice: &[u8]) -> Result<Self> {
let mut iterator = slice.iter().copied();
let value = Self::from_bytes(&mut iterator)?;
if iterator.next().is_some() {
Err(format!(
"Too many bytes when reading {}",
Self::descriptor()
))?
}
Ok(value)
}

fn to_bytes(self) -> impl IntoIterator<Item = u8>;

fn to_vec(self) -> Vec<u8> {
self.to_bytes().into_iter().collect()
}
}

pub trait ByteAbi: Sized {
fn descriptor() -> Cow<'static, str>;

fn from_byte(byte: u8) -> Result<Self>;

fn to_byte(self) -> u8;
}

impl<T: ByteAbi> Abi for T {
fn descriptor() -> Cow<'static, str> {
T::descriptor()
}

fn from_bytes(bytes: &mut impl Iterator<Item = u8>) -> Result<Self> {
let byte = bytes
.next()
.ok_or(format!("Expected byte for {}", Self::descriptor()))?;
Self::from_byte(byte)
}

fn to_bytes(self) -> impl IntoIterator<Item = u8> {
[self.to_byte()]
}
}

pub trait Decode<T> {
fn decode(&self) -> Result<T>;
}

impl<T: Abi> Decode<T> for [u8] {
fn decode(&self) -> Result<T> {
T::from_slice(self)
}
}

impl<T: ByteAbi> Decode<T> for u8 {
fn decode(&self) -> Result<T> {
T::from_byte(*self)
}
}

impl ByteAbi for u8 {
fn descriptor() -> Cow<'static, str> {
"u8".into()
}

fn from_byte(byte: u8) -> Result<Self> {
Ok(byte)
}

fn to_byte(self) -> u8 {
self
}
}

impl Abi for u32 {
fn descriptor() -> Cow<'static, str> {
"u32".into()
}

fn from_bytes(bytes: &mut impl Iterator<Item = u8>) -> Result<Self> {
let [b0, b1, b2, b3] = bytes
.take((Self::BITS / u8::BITS) as usize)
.collect::<Vec<_>>()[..]
else {
Err("Expected 4 bytes for u32")?
};
Ok(Self::from_be_bytes([b0, b1, b2, b3]))
}

fn to_bytes(self) -> impl IntoIterator<Item = u8> {
self.to_be_bytes()
}
}

impl<T: Abi> Abi for Option<T> {
fn descriptor() -> Cow<'static, str> {
format!("optional {}", T::descriptor()).into()
}

fn from_bytes(bytes: &mut impl Iterator<Item = u8>) -> Result<Self> {
if bytes.next().ok_or("Expected byte")? == 0 {
Ok(None)
} else {
Ok(Some(T::from_bytes(bytes)?))
}
}

fn to_bytes(self) -> impl IntoIterator<Item = u8> {
match self {
None => vec![0],
Some(v) => iter::once(0).chain(v.to_bytes()).collect(),
}
}
}

#[macro_export]
macro_rules! abi {
(
$type:ty as $name:literal {
$( $field:ident ), * $(,)?
}
) => {
impl $crate::abi::Abi for $type {
fn descriptor() -> Cow<'static, str> {
$name.into()
}

fn from_bytes(bytes: &mut impl Iterator<Item = u8>) -> abi::Result<Self> {
Ok(Self {
$( $field: <_ as $crate::abi::Abi>::from_bytes(bytes)?, ) +
})
}

fn to_bytes(self) -> impl Iterator<Item = u8> {
iter::empty()
$( .chain($crate::abi::Abi::to_bytes(self.$field)) ) +
}
}
};

(
$type:ty {
$( $field:ident ), * $(,)?
}
) => {
impl $crate::abi::Abi for $type {
fn descriptor() -> Cow<'static, str> {
stringify!($type).into()
}

fn from_bytes(bytes: &mut impl Iterator<Item = u8>) -> abi::Result<Self> {
Ok(Self {
$( $field: <_ as $crate::abi::Abi>::from_bytes(bytes)?, ) +
})
}

fn to_bytes(self) -> impl Iterator<Item = u8> {
iter::empty()
$( .chain($crate::abi::Abi::to_bytes(self.$field)) ) +
}
}
}
}
93 changes: 93 additions & 0 deletions plugin/src/fen.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
use crate::abi;
use crate::abi::{Abi, ByteAbi};
use crate::model::{Board, CastlingAvailabilities, Color, Piece, Position, Square, SquareContent};
use crate::utils::SliceExt;
use std::iter;
use std::str::FromStr;

fn parse_board(fen: &[u8]) -> abi::Result<Board> {
let mut squares = Vec::new();
for fen_rank in fen.split_on(b'/') {
let fen_rank: &[u8] = fen_rank;
let mut rank = Vec::new();
for c in fen_rank {
if let Some(n) = (*c as char).to_digit(10) {
rank.extend(iter::repeat(SquareContent::Empty).take(n as usize))
} else {
rank.push(SquareContent::Piece(Piece::from_byte(*c)?))
}
}
squares.push(rank)
}
squares.reverse();
Ok(Board::new(squares))
}

fn parse_castling_availabilities(fen: &[u8]) -> abi::Result<CastlingAvailabilities> {
Ok(CastlingAvailabilities {
white_king_side: fen.contains(&b'K'),
white_queen_side: fen.contains(&b'Q'),
black_king_side: fen.contains(&b'k'),
black_queen_side: fen.contains(&b'q'),
})
}

fn parse_en_passant_target_square(fen: &[u8]) -> abi::Result<Option<Square>> {
if fen == b"-" {
return Ok(None);
}
Ok(Some(Square::from_slice(fen)?))
}

fn parse_int(fen: &[u8]) -> abi::Result<u32> {
u32::from_str(
std::str::from_utf8(fen)
.map_err(|_| "Unreachable: FEN was already checked to be valid ASCII.")?,
)
.map_err(|err| err.to_string())
}

pub fn parse_fen(fen: &[u8]) -> abi::Result<Position> {
if !fen.is_ascii() {
Err("Invalid FEN: expected ASCII")?
}

let mut parts = fen.split_on(b' ').peekable();

let board = parse_board(parts.next().ok_or("Invalid FEN: missing board info")?)?;

if parts.peek().is_none() {
return Ok(Position::default_with_board(board));
}

let active = Color::from_slice(parts.next().ok_or("Invalid FEN: missing active player")?)?;

let castling_availabilities = parse_castling_availabilities(
parts
.next()
.ok_or("Invalid FEN: missing castling availabilities")?,
)?;

let en_passant_target_square = parse_en_passant_target_square(
parts
.next()
.ok_or("Invalid FEN: missing en passant target square")?,
)?;

let halfmove = parse_int(parts.next().ok_or("Invalid FEN: expected fullmove")?)?;

let fullmove = parse_int(parts.next().ok_or("Invalid FEN: expected fullmove")?)?;

if parts.next().is_some() {
Err("Invalid FEN: too many parts")?
}

Ok(Position {
board,
active,
castling_availabilities,
en_passant_target_square,
halfmove,
fullmove,
})
}
20 changes: 20 additions & 0 deletions plugin/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
mod abi;
mod fen;
mod model;
mod utils;

use crate::abi::Abi;
use crate::model::Position;
use wasm_minimal_protocol::{initiate_protocol, wasm_func};

initiate_protocol!();

#[wasm_func]
pub fn starting_position() -> Vec<u8> {
Position::default().to_vec()
}

#[wasm_func]
pub fn parse_fen(fen: &[u8]) -> abi::Result<Vec<u8>> {
Ok(fen::parse_fen(fen)?.to_vec())
}
Loading

0 comments on commit 5814f62

Please sign in to comment.