generated from musicparty/template
feat: primitive comment block parser
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
21
Cargo.lock
generated
Normal file
21
Cargo.lock
generated
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "apidoc"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"apidoc_parser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "apidoc_parser"
|
||||||
|
version = "0.1.0"
|
||||||
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[workspace]
|
||||||
|
resolver = "3"
|
||||||
|
members = ["src/apidoc", "src/apidoc_parser"]
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
public = false
|
||||||
|
|
||||||
|
[workspace.clippy]
|
||||||
|
strict = "warn"
|
||||||
8
src/apidoc/Cargo.toml
Normal file
8
src/apidoc/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "apidoc"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.100"
|
||||||
|
apidoc_parser = { version = "0.1.0", path = "../apidoc_parser" }
|
||||||
17
src/apidoc/src/main.rs
Normal file
17
src/apidoc/src/main.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
use std::{env::args, fs};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use apidoc_parser::parse;
|
||||||
|
|
||||||
|
fn main() -> anyhow::Result<()> {
|
||||||
|
let mut args = args().skip(1);
|
||||||
|
|
||||||
|
let file = fs::read_to_string(args.next().context("expected a file path argument")?)
|
||||||
|
.context("opening file")?;
|
||||||
|
|
||||||
|
println!("{file}");
|
||||||
|
|
||||||
|
parse(file);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
7
src/apidoc_parser/Cargo.toml
Normal file
7
src/apidoc_parser/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
[package]
|
||||||
|
name = "apidoc_parser"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
public.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
20
src/apidoc_parser/src/comment.rs
Normal file
20
src/apidoc_parser/src/comment.rs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Span {
|
||||||
|
lo: usize,
|
||||||
|
hi: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Span {
|
||||||
|
pub const fn new(range: std::ops::Range<usize>) -> Self {
|
||||||
|
Self {
|
||||||
|
lo: range.start,
|
||||||
|
hi: range.end,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Comment {
|
||||||
|
pub(crate) prefix: String,
|
||||||
|
pub(crate) span: Span,
|
||||||
|
}
|
||||||
75
src/apidoc_parser/src/cursor.rs
Normal file
75
src/apidoc_parser/src/cursor.rs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct Cursor {
|
||||||
|
source: String,
|
||||||
|
offset: usize,
|
||||||
|
byte_offset: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cursor {
|
||||||
|
pub const fn new(source: String) -> Self {
|
||||||
|
Self {
|
||||||
|
source,
|
||||||
|
offset: 0,
|
||||||
|
byte_offset: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn slice(&self) -> &'_ str {
|
||||||
|
self.source
|
||||||
|
.get(self.byte_offset..)
|
||||||
|
.expect("str should be built out of bounded chars")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bump(&mut self) -> &mut Self {
|
||||||
|
self.offset += 1;
|
||||||
|
self.byte_offset += self.slice().chars().next().map_or(0, char::len_utf8);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bump_newline(&mut self) -> bool {
|
||||||
|
while !self.slice().starts_with('\n') && !self.bump().is_finished() {}
|
||||||
|
|
||||||
|
if self.slice().starts_with('\n') {
|
||||||
|
self.bump();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn skip(&mut self, chars: usize) {
|
||||||
|
let len: usize = self.slice().chars().take(chars).map(char::len_utf8).sum();
|
||||||
|
|
||||||
|
self.offset += chars;
|
||||||
|
self.byte_offset += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn skip_whitespace(&mut self) {
|
||||||
|
let mut offset = 0;
|
||||||
|
let mut byte_offset = 0;
|
||||||
|
|
||||||
|
for char in self.slice().chars() {
|
||||||
|
if !char.is_ascii_whitespace() || char == '\n' {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += 1;
|
||||||
|
byte_offset += char.len_utf8();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.offset += offset;
|
||||||
|
self.byte_offset += byte_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn is_finished(&self) -> bool {
|
||||||
|
self.byte_offset >= self.source.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn offset(&self) -> usize {
|
||||||
|
self.offset
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fmt_offset(&self) -> String {
|
||||||
|
format!("{} (byte {})", self.offset, self.byte_offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/apidoc_parser/src/lib.rs
Normal file
55
src/apidoc_parser/src/lib.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
use crate::comment::{Comment, Span};
|
||||||
|
|
||||||
|
pub mod comment;
|
||||||
|
pub(crate) mod cursor;
|
||||||
|
|
||||||
|
use cursor::Cursor;
|
||||||
|
|
||||||
|
// TODO: (issue #3) Move these into some variable configuration
|
||||||
|
const APIDOC_PRAGMA: &str = "@apidoc";
|
||||||
|
const COMMENT_PREFIX: &str = "//";
|
||||||
|
|
||||||
|
pub fn parse(source: String) -> Vec<Comment> {
|
||||||
|
let mut comments = Vec::new();
|
||||||
|
|
||||||
|
let mut cursor = Cursor::new(source);
|
||||||
|
|
||||||
|
while !cursor.bump().is_finished() {
|
||||||
|
let slice = cursor.slice();
|
||||||
|
|
||||||
|
if slice.starts_with(COMMENT_PREFIX) {
|
||||||
|
println!("comment: at offset {}", cursor.fmt_offset());
|
||||||
|
|
||||||
|
let start = cursor.offset();
|
||||||
|
|
||||||
|
cursor.skip(COMMENT_PREFIX.len());
|
||||||
|
cursor.skip_whitespace();
|
||||||
|
if !cursor.slice().starts_with(APIDOC_PRAGMA) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let prefix = COMMENT_PREFIX;
|
||||||
|
|
||||||
|
println!(" pragma! {}", cursor.fmt_offset());
|
||||||
|
|
||||||
|
cursor.bump_newline();
|
||||||
|
let mut end = cursor.offset() - 1;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
cursor.skip_whitespace();
|
||||||
|
if !cursor.slice().starts_with(prefix) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cursor.bump_newline();
|
||||||
|
end = cursor.offset() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
comments.push(dbg!(Comment {
|
||||||
|
prefix: COMMENT_PREFIX.to_owned(),
|
||||||
|
span: Span::new(start..end)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
comments
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user