Something went wrong. Try again.
atproto Thingiverse but good
Something went wrong. Try again.
13 kB · 422 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423use crate::types::{Diagnostic, DiagnosticCode, LdrawLimits, ParseError, Severity, Span};use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]pub enum TokenKind { Zero, One, Two, Three, Four, Five, Step, Rotstep, Pause, Write, Clear, Save, Colour, Code, Value, Edge, Alpha, Luminance, Chrome, Pearlescent, Rubber, MatteMetallic, Metal, Material, File, NoFile, Bfc, Certify, NoCertify, Clip, NoClip, InvertNext, Orientation, Texmap, Start, Next, Fallback, End, Stop, Number, Identifier, QuotedIdentifier, Garbage,}
#[derive(Clone, Copy, Debug, PartialEq)]pub struct Token<'src> { pub kind: TokenKind, pub text: &'src str, pub number: Option<f64>, pub span: Span,}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]pub enum LineEnding { Lf, CrLf, Cr, None,}impl LineEnding { pub(crate) fn as_str(&self) -> &'static str { match self { Self::Lf => "\n", Self::CrLf => "\r\n", Self::Cr => "\r", Self::None => "", } }
pub fn as_bytes(&self) -> &'static [u8] { self.as_str().as_bytes() }}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]pub enum LineType { Zero, One, Two, Three, Four, Five,}
impl From<LineType> for u8 { fn from(lt: LineType) -> u8 { match lt { LineType::Zero => 0, LineType::One => 1, LineType::Two => 2, LineType::Three => 3, LineType::Four => 4, LineType::Five => 5, } }}
impl From<u8> for LineType { fn from(v: u8) -> Self { match v { 0 => LineType::Zero, 1 => LineType::One, 2 => LineType::Two, 3 => LineType::Three, 4 => LineType::Four, 5 => LineType::Five, _ => panic!("invalid line type: {v}"), } }}
impl serde::Serialize for LineType { fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> { s.serialize_u8((*self).into()) }}
impl<'de> serde::Deserialize<'de> for LineType { fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> { let v = u8::deserialize(d)?; match v { 0 => Ok(LineType::Zero), 1 => Ok(LineType::One), 2 => Ok(LineType::Two), 3 => Ok(LineType::Three), 4 => Ok(LineType::Four), 5 => Ok(LineType::Five), _ => Err(serde::de::Error::custom(format!("invalid line type: {v}"))), } }}
#[derive(Clone, Debug, PartialEq)]pub struct ScannedLine<'src> { pub line_type: Option<LineType>, pub raw: &'src [u8], pub raw_tail: &'src [u8], pub tokens: Vec<Token<'src>>, pub span: Span, pub ending: LineEnding, pub blank: bool,}
fn keyword(text: &str) -> Option<TokenKind> { Some(match text { "0" => TokenKind::Zero, "1" => TokenKind::One, "2" => TokenKind::Two, "3" => TokenKind::Three, "4" => TokenKind::Four, "5" => TokenKind::Five, "STEP" => TokenKind::Step, "ROTSTEP" => TokenKind::Rotstep, "PAUSE" => TokenKind::Pause, "WRITE" => TokenKind::Write, "CLEAR" => TokenKind::Clear, "SAVE" => TokenKind::Save, "!COLOUR" => TokenKind::Colour, "CODE" => TokenKind::Code, "VALUE" => TokenKind::Value, "EDGE" => TokenKind::Edge, "ALPHA" => TokenKind::Alpha, "LUMINANCE" => TokenKind::Luminance, "CHROME" => TokenKind::Chrome, "PEARLESCENT" => TokenKind::Pearlescent, "RUBBER" => TokenKind::Rubber, "MATTE_METALLIC" => TokenKind::MatteMetallic, "METAL" => TokenKind::Metal, "MATERIAL" => TokenKind::Material, "FILE" => TokenKind::File, "NOFILE" => TokenKind::NoFile, "BFC" => TokenKind::Bfc, "CERTIFY" => TokenKind::Certify, "NOCERTIFY" => TokenKind::NoCertify, "CLIP" => TokenKind::Clip, "NOCLIP" => TokenKind::NoClip, "INVERTNEXT" => TokenKind::InvertNext, "CW" | "CCW" => TokenKind::Orientation, "!TEXMAP" => TokenKind::Texmap, "START" => TokenKind::Start, "NEXT" => TokenKind::Next, "FALLBACK" => TokenKind::Fallback, "END" => TokenKind::End, "STOP" => TokenKind::Stop, _ => return None, })}
pub fn scan_lines<'src>( bytes: &'src [u8], limits: &LdrawLimits,) -> Result<Vec<ScannedLine<'src>>, ParseError> { let mut out = Vec::new(); let mut offset = 0usize; let mut line_no = 1u32; while offset < bytes.len() { let start = offset; let mut end = offset; let ending; while end < bytes.len() && bytes[end] != b'\n' && bytes[end] != b'\r' { end += 1; } if end == bytes.len() { ending = LineEnding::None; offset = end; } else if bytes[end] == b'\r' && end + 1 < bytes.len() && bytes[end + 1] == b'\n' { ending = LineEnding::CrLf; offset = end + 2; } else { ending = if bytes[end] == b'\r' { LineEnding::Cr } else { LineEnding::Lf }; offset = end + 1; } let raw = &bytes[start..end]; let line_len = u64::try_from(raw.len()).map_err(|_| ParseError::Overflow("line bytes"))?; let start_u32 = u32::try_from(start).map_err(|_| ParseError::Overflow("line span start"))?; let end_u32 = u32::try_from(end).map_err(|_| ParseError::Overflow("line span end"))?; if line_len > limits.line_bytes { return Err(ParseError::Diagnostic(Diagnostic { code: DiagnosticCode::LineBytesLimit, severity: Severity::Error, message: "physical line exceeds the configured limit".into(), span: Some(Span { start: start_u32, end: end_u32, line: line_no, column: 1, }), })); } let text = std::str::from_utf8(raw).map_err(|_| { ParseError::Diagnostic(Diagnostic { code: DiagnosticCode::InvalidUtf8, severity: Severity::Error, message: "line is not valid UTF-8".into(), span: Some(Span { start: start_u32, end: end_u32, line: line_no, column: 1, }), }) })?; out.push(scan_one(text, raw, start_u32, line_no, ending)?); line_no = line_no .checked_add(1) .ok_or(ParseError::Overflow("line number"))?; } if bytes.is_empty() { out.push(scan_one("", &[], 0, 1, LineEnding::None)?); } Ok(out)}
fn scan_one<'src>( text: &'src str, raw: &'src [u8], start: u32, line: u32, ending: LineEnding,) -> Result<ScannedLine<'src>, ParseError> { let trimmed = text.trim(); let blank = trimmed.is_empty(); if text .bytes() .any(|byte| byte.is_ascii_control() && !byte.is_ascii_whitespace()) { return Err(ParseError::Diagnostic(Diagnostic { code: DiagnosticCode::InvalidLineType, severity: Severity::Error, message: "line contains an ASCII control character".into(), span: Some(Span { start, end: start .checked_add( u32::try_from(raw.len()) .map_err(|_| ParseError::Overflow("control character span end"))?, ) .ok_or(ParseError::Overflow("control character span end"))?, line, column: 1, }), })); } let mut tokens = Vec::new(); let mut i = 0usize; let bytes = text.as_bytes(); while i < bytes.len() { while i < bytes.len() && bytes[i].is_ascii_whitespace() { i += 1; } if i >= bytes.len() { break; } let begin = i; let value = if bytes[i] == b'"' { i += 1; let inner = i; while i < bytes.len() && bytes[i] != b'"' { i += 1; } if i >= bytes.len() { return Err(ParseError::Diagnostic(Diagnostic { code: DiagnosticCode::InvalidLineType, severity: Severity::Error, message: "unterminated quoted token".into(), span: Some(Span { start, end: start .checked_add( u32::try_from(bytes.len()) .map_err(|_| ParseError::Overflow("quoted token span end"))?, ) .ok_or(ParseError::Overflow("quoted token span end"))?, line, column: u32::try_from(begin) .map_err(|_| ParseError::Overflow("quoted token column"))? .checked_add(1) .ok_or(ParseError::Overflow("quoted token column"))?, }), })); } let value = &text[inner..i]; i += 1; let begin_u32 = u32::try_from(begin).map_err(|_| ParseError::Overflow("token span start"))?; let end_u32 = u32::try_from(i).map_err(|_| ParseError::Overflow("token span end"))?; let column = begin_u32 .checked_add(1) .ok_or(ParseError::Overflow("token column"))?; let span = Span { start: start .checked_add(begin_u32) .ok_or(ParseError::Overflow("token span start"))?, end: start .checked_add(end_u32) .ok_or(ParseError::Overflow("token span end"))?, line, column, }; tokens.push(Token { kind: TokenKind::QuotedIdentifier, text: value, number: None, span, }); continue; } else { while i < bytes.len() && !bytes[i].is_ascii_whitespace() { i += 1; } &text[begin..i] }; let number = value.parse::<f64>().ok().filter(|n| n.is_finite()); let kind = keyword(value) .or_else(|| number.map(|_| TokenKind::Number)) .unwrap_or(TokenKind::Identifier); let begin_u32 = u32::try_from(begin).map_err(|_| ParseError::Overflow("token span start"))?; let end_u32 = u32::try_from(i).map_err(|_| ParseError::Overflow("token span end"))?; let column = begin_u32 .checked_add(1) .ok_or(ParseError::Overflow("token column"))?; tokens.push(Token { kind, text: value, number, span: Span { start: start .checked_add(begin_u32) .ok_or(ParseError::Overflow("token span start"))?, end: start .checked_add(end_u32) .ok_or(ParseError::Overflow("token span end"))?, line, column, }, }); } let line_type = tokens.first().and_then(|t| match t.kind { TokenKind::Zero => Some(LineType::Zero), TokenKind::One => Some(LineType::One), TokenKind::Two => Some(LineType::Two), TokenKind::Three => Some(LineType::Three), TokenKind::Four => Some(LineType::Four), TokenKind::Five => Some(LineType::Five), _ => None, }); let tail_start = tokens .first() .and_then(|t| usize::try_from(t.span.end.checked_sub(start)?).ok()) .unwrap_or(0); let raw_tail = raw.get(tail_start..).unwrap_or_default(); Ok(ScannedLine { line_type, raw, raw_tail, tokens, span: Span { start, end: start .checked_add( u32::try_from(raw.len()).map_err(|_| ParseError::Overflow("line span end"))?, ) .ok_or(ParseError::Overflow("line span end"))?, line, column: 1, }, ending, blank, })}