opam-file-rs-0.1.5/.cargo_vcs_info.json0000644000000001120000000000000133230ustar { "git": { "sha1": "962eed883119fc6f17e9db090c794a26b37425b9" } } opam-file-rs-0.1.5/.gitignore000064400000000000000000000000330000000000000140630ustar 00000000000000/target Cargo.lock *.pest opam-file-rs-0.1.5/Cargo.toml0000644000000021240000000000000113260ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "opam-file-rs" version = "0.1.5" authors = ["Naoki Kaneko "] build = "build.rs" exclude = ["tests/*", ".github/*", "target/*"] description = "Parser and printer for the opam file syntax with Rust" readme = "README.md" keywords = ["ocaml", "opam", "parser"] categories = ["encoding", "parser-implementations"] license = "MIT" repository = "https://github.com/puripuri2100/opam-file-rs" [dependencies.lalrpop-util] version = "0.19.4" [dependencies.thiserror] version = "1.0.23" [build-dependencies.lalrpop] version = "0.19.4" opam-file-rs-0.1.5/Cargo.toml.orig000064400000000000000000000012040000000000000147630ustar 00000000000000[package] name = "opam-file-rs" version = "0.1.5" authors = ["Naoki Kaneko "] edition = "2018" build = "build.rs" license = "MIT" readme = "README.md" repository = "https://github.com/puripuri2100/opam-file-rs" keywords = ["ocaml", "opam", "parser"] categories = ["encoding", "parser-implementations"] description = "Parser and printer for the opam file syntax with Rust" exclude = ["tests/*", ".github/*", "target/*"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] lalrpop-util = "0.19.4" thiserror = "1.0.23" [build-dependencies] lalrpop = "0.19.4" opam-file-rs-0.1.5/LICENSE000064400000000000000000000021110000000000000130770ustar 00000000000000The MIT License Copyright (c) 2021 Naoki Kaneko (a.k.a. "puripuri2100") 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. opam-file-rs-0.1.5/README.md000064400000000000000000000037330000000000000133640ustar 00000000000000# opam-file-rs: Parser and printer for the opam file syntax with Rust [![crates.io][crates-badge]][crates] [![docs.rs][docs-badge]][docs] [![Build Status][ci-badge]][ci] [![source badge][source-badge]][source] [![license badge][license-badge]][license] [crates]: https://crates.io/crates/opam-file-rs [crates-badge]: https://img.shields.io/crates/v/opam-file-rs [docs]: https://docs.rs/opam-file-rs/ [docs-badge]: https://img.shields.io/badge/docs.rs-opam_file_rs-blue [ci]: https://github.com/puripuri2100/opam-file-rs/actions?query=workflow%3ACI [ci-badge]: https://github.com/puripuri2100/opam-file-rs/workflows/CI/badge.svg?branch=master [source]: https://github.com/puripuri2100/opam-file-rs [source-badge]: https://img.shields.io/badge/source-github-blue [license]: https://github.com/puripuri2100/opam-file-rs/blob/master/LICENSE [license-badge]: https://img.shields.io/badge/license-MIT-blue # Parsing OPAM Parse OPAM file. ```rust use opam_file_rs; fn main () { let opam = r#" opam-version: "2.0" version: "0.1.0" name: "opam-file-rs" dev-repo: "git+https://github.com/puripuri2100/opam-file-rs" license: "MIT" maintainer: "Naoki Kaneko " depends: [ "lalrpop-util" {>= "0.19.4"} "thiserror" {>= "1.0.23"} ] "#; assert!(opam_file_rs::parse(opam).is_ok()); } ``` # Convert to a OPAM file format. A data structure can be converted to an OPAM file format by `value::format_opam_file`. ```rust use opam_file_rs; fn main() { let opam_str = r#" opam-version: "2.0" version: "0.1.0" name: "opam-file-rs" dev-repo: "git+https://github.com/puripuri2100/opam-file-rs" license: "MIT" maintainer: "Naoki Kaneko " depends: [ "lalrpop-util" {>= "0.19.4"} "thiserror" {>= "1.0.23"} ] "#; let opam = opam_file_rs::parse(opam_str).unwrap(); println!("{}", opam_file_rs::value::format_opam_file(opam)); } ``` --- (c) 2021 Naoki Kaneko (a.k.a. "puripuri2100") opam-file-rs-0.1.5/build.rs000064400000000000000000000001110000000000000135350ustar 00000000000000extern crate lalrpop; fn main() { lalrpop::process_root().unwrap(); } opam-file-rs-0.1.5/rustfmt.toml000064400000000000000000000000150000000000000144740ustar 00000000000000tab_spaces=2 opam-file-rs-0.1.5/src/lexer.rs000064400000000000000000000302120000000000000143510ustar 00000000000000use thiserror::Error; use super::value; #[allow(unused)] pub type Token = (TokenKind, usize, usize); #[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)] #[allow(non_camel_case_types)] #[allow(unused)] pub enum TokenKind { EOF, STRING(String), IDENT(String), BOOL(bool), INT(isize), LBRACKET, RBRACKET, LPAR, RPAR, LBRACE, RBRACE, COLON, AND, OR, RELOP(value::RelOpKind), PFXOP(value::PfxOpKind), ENVOP(value::EnvUpdateOpKind), } #[allow(unused)] pub fn get_value_bool(kind: TokenKind) -> Option { match kind { TokenKind::BOOL(b) => Some(b), _ => None, } } #[allow(unused)] pub fn get_value_string(kind: TokenKind) -> Option { match kind { TokenKind::STRING(s) => Some(s), TokenKind::IDENT(s) => Some(s), _ => None, } } #[allow(unused)] pub fn get_value_isize(kind: TokenKind) -> Option { match kind { TokenKind::INT(i) => Some(i), _ => None, } } #[allow(unused)] pub fn get_value_pfxop(kind: TokenKind) -> Option { match kind { TokenKind::PFXOP(p) => Some(p), _ => None, } } #[allow(unused)] pub fn get_value_relop(kind: TokenKind) -> Option { match kind { TokenKind::RELOP(r) => Some(r), _ => None, } } #[allow(unused)] pub fn get_value_env(kind: TokenKind) -> Option { match kind { TokenKind::ENVOP(e) => Some(e), _ => None, } } #[derive(Debug, Clone, PartialEq, Eq, Hash, Error)] pub enum LexErrorKind { #[error("invalid char: {0}")] InvalidChar(char), #[error("EOF")] Eof, } pub type LexError = (LexErrorKind, usize, usize); fn error_invalid_char(c: char, start: usize, end: usize) -> LexError { (LexErrorKind::InvalidChar(c), start, end) } fn error_eof(start: usize) -> LexError { (LexErrorKind::Eof, start, start + 1) } #[allow(unused)] pub fn lex(input: &str) -> Result, LexError> { let mut tokens = Vec::new(); let input = input.chars().collect::>(); let mut pos = 0; let (token_lst, new_pos) = lex_token(&input, pos)?; pos = new_pos; let mut l = token_lst; tokens.append(&mut l); tokens.push((TokenKind::EOF, pos, pos + 1)); Ok(tokens) } fn lex_token(input: &[char], pos: usize) -> Result<(Vec, usize), LexError> { let mut pos = pos; let mut tokens = Vec::new(); while pos < input.len() { match input[pos] { ':' => match input.get(pos + 1) { Some('=') => { tokens.push(( TokenKind::ENVOP(value::EnvUpdateOpKind::ColonEq), pos, pos + 2, )); pos += 2; } _ => { tokens.push((TokenKind::COLON, pos, pos + 1)); pos += 1; } }, '{' => { tokens.push((TokenKind::LBRACE, pos, pos + 1)); pos += 1; } '}' => { tokens.push((TokenKind::RBRACE, pos, pos + 1)); pos += 1; } '[' => { tokens.push((TokenKind::LBRACKET, pos, pos + 1)); pos += 1; } ']' => { tokens.push((TokenKind::RBRACKET, pos, pos + 1)); pos += 1; } '(' => match input.get(pos + 1) { Some('*') => { let new_pos = lex_comment(1, input, pos + 1); pos = new_pos; } _ => { tokens.push((TokenKind::LPAR, pos, pos + 1)); pos += 1; } }, ')' => { tokens.push((TokenKind::RPAR, pos, pos + 1)); pos += 1; } '#' => { let new_pos = lex_line_comment(input, pos + 1); pos = new_pos; } '&' => { tokens.push((TokenKind::AND, pos, pos + 1)); pos += 1; } '|' => { tokens.push((TokenKind::OR, pos, pos + 1)); pos += 1; } '?' => { tokens.push((TokenKind::PFXOP(value::PfxOpKind::Defined), pos, pos + 1)); pos += 1; } '!' => match input.get(pos + 1) { Some('=') => { tokens.push((TokenKind::RELOP(value::RelOpKind::Neq), pos, pos + 2)); pos += 2; } _ => { tokens.push((TokenKind::PFXOP(value::PfxOpKind::Not), pos, pos + 1)); pos += 1; } }, '>' => match input.get(pos + 1) { Some('=') => { tokens.push((TokenKind::RELOP(value::RelOpKind::Geq), pos, pos + 2)); pos += 2; } _ => { tokens.push((TokenKind::RELOP(value::RelOpKind::Gt), pos, pos + 1)); pos += 1; } }, '<' => match input.get(pos + 1) { Some('=') => { tokens.push((TokenKind::RELOP(value::RelOpKind::Leq), pos, pos + 2)); pos += 2; } _ => { tokens.push((TokenKind::RELOP(value::RelOpKind::Lt), pos, pos + 1)); pos += 1; } }, '~' => { tokens.push((TokenKind::RELOP(value::RelOpKind::Sem), pos, pos + 1)); pos += 1; } '=' => match input.get(pos + 1) { Some(':') => { tokens.push(( TokenKind::ENVOP(value::EnvUpdateOpKind::EqColon), pos, pos + 2, )); pos += 2; } Some('+') => match input.get(pos + 2) { Some('=') => { tokens.push(( TokenKind::ENVOP(value::EnvUpdateOpKind::EqPlusEq), pos, pos + 3, )); pos += 3; } _ => { tokens.push(( TokenKind::ENVOP(value::EnvUpdateOpKind::EqPlus), pos, pos + 2, )); pos += 2; } }, _ => { tokens.push((TokenKind::RELOP(value::RelOpKind::Eq), pos, pos + 1)); pos += 1; } }, '+' => match input.get(pos + 1) { Some('=') => { tokens.push(( TokenKind::ENVOP(value::EnvUpdateOpKind::PlusEq), pos, pos + 2, )); pos += 2; } _ => return Err(error_invalid_char('+', pos, pos + 1)), }, '"' => match (input.get(pos + 1), input.get(pos + 2)) { (Some('"'), Some('"')) => { let (token, new_pos) = lex_string_triple(input, pos + 3)?; tokens.push(token); pos = new_pos; } _ => { let (token, new_pos) = lex_string(input, pos)?; tokens.push(token); pos = new_pos; } }, ' ' | '\n' | '\t' | '\r' => { pos += 1; } '-' => match input.get(pos + 1) { Some(c) if c.is_ascii_digit() => { let (token, new_pos) = lex_int(true, input, pos); tokens.push(token); pos = new_pos; } _ => return Err(error_invalid_char('-', pos, pos + 1)), }, c if c.is_ascii_digit() => { let (token, new_pos) = lex_int(false, input, pos); tokens.push(token); pos = new_pos; } c if c.is_ascii_alphabetic() => { let (token, new_pos) = lex_ident(input, pos); tokens.push(token); pos = new_pos; } c => return Err(error_invalid_char(c, pos, pos + 1)), } } Ok((tokens, pos)) } fn lex_comment(depth: usize, input: &[char], pos: usize) -> usize { let mut pos = pos; while pos < input.len() { match input[pos] { '*' => match input.get(pos + 1) { Some(')') => { pos += 1; break; } _ => { pos += 1; } }, '(' => match input.get(pos + 1) { Some('*') => { pos += 1; let new_pos = lex_comment(depth + 1, input, pos); pos = new_pos; } _ => { pos += 1; } }, _ => pos += 1, }; } pos } fn lex_line_comment(input: &[char], pos: usize) -> usize { let mut pos = pos; while pos < input.len() && !('\n' == input[pos]) { pos += 1; } pos } fn lex_string(input: &[char], pos: usize) -> Result<(Token, usize), LexError> { let mut str = String::new(); let start = pos + 1; let mut s_pos = start; loop { match input.get(s_pos) { None => return Err(error_eof(pos)), Some(c) => match c { '\\' => { let (escape_str, new_pos) = lex_escape(input, pos)?; s_pos = new_pos; str.push_str(&escape_str) } '"' => { s_pos += 1; break; } _ => { s_pos = s_pos + 1; str.push(*c) } }, } } Ok(((TokenKind::STRING(str), start - 1, s_pos), s_pos)) } fn lex_string_triple(input: &[char], pos: usize) -> Result<(Token, usize), LexError> { let mut str = String::new(); let start = pos; let mut s_pos = start; loop { match input.get(s_pos) { None => return Err(error_eof(pos)), Some(c) => match c { '\\' => { let (escape_str, new_pos) = lex_escape(input, pos)?; s_pos = new_pos; str.push_str(&escape_str) } '"' => match (input.get(s_pos + 1), input.get(s_pos + 2)) { (Some('"'), Some('"')) => { s_pos += 3; break; } (Some('"'), _) => { s_pos += 2; str.push_str("\"\""); } _ => { s_pos += 1; str.push_str("\""); } }, _ => { str.push(*c); s_pos = s_pos + 1; } }, } } Ok(((TokenKind::STRING(str), start, s_pos), s_pos)) } fn lex_escape(input: &[char], pos: usize) -> Result<(String, usize), LexError> { match input.get(pos + 1) { Some('\\') => Ok(("\\".to_string(), pos + 2)), Some('"') => Ok(("\"".to_string(), pos + 2)), Some('\'') => Ok(("\'".to_string(), pos + 2)), Some('n') => Ok(("\n".to_string(), pos + 2)), Some('r') => Ok(("\r".to_string(), pos + 2)), Some('t') => Ok(("\t".to_string(), pos + 2)), Some('b') => Ok(("\u{0008}".to_string(), pos + 2)), Some('x') => match (input.get(pos + 2), input.get(pos + 3)) { (Some(c1), Some(c2)) if (c1.is_ascii_hexdigit() && c2.is_ascii_hexdigit()) => { let hex = vec![*c1, *c2].iter().collect::(); let hex_i64 = i64::from_str_radix(&hex, 16).unwrap(); let str = String::from_utf8(vec![hex_i64 as u8]).unwrap(); Ok((str, pos + 4)) } _ => Err(error_invalid_char('x', pos, pos + 1)), }, Some(c) if c.is_ascii_digit() => match (input.get(pos + 2), input.get(pos + 3)) { (Some(c1), Some(c2)) if (c1.is_ascii_digit() && c2.is_ascii_digit()) => { let hex = vec![*c, *c1, *c2].iter().collect::(); let hex_i64 = i64::from_str_radix(&hex, 10).unwrap(); let str = String::from_utf8(vec![hex_i64 as u8]).unwrap(); Ok((str, pos + 4)) } _ => Err(error_invalid_char(*c, pos, pos + 1)), }, Some(c) => Err(error_invalid_char(*c, pos + 1, pos + 2)), None => Err(error_eof(pos + 1)), } } #[test] fn check_lex_escape_n() { assert_eq!( lex_escape(&"\\n".chars().collect::>(), 0), Ok(("\n".to_string(), 2)) ) } #[test] fn check_lex_escape_hex_unicode() { assert_eq!( lex_escape(&"\\x4E".chars().collect::>(), 0), Ok(("N".to_string(), 4)) ) } #[test] fn check_lex_escape_digit_unicode() { assert_eq!( lex_escape(&"\\078".chars().collect::>(), 0), Ok(("N".to_string(), 4)) ) } fn lex_int(is_minus: bool, input: &[char], pos: usize) -> (Token, usize) { let start = pos; let mut pos = pos; let mut str = if is_minus { String::new() } else { "-".to_string() }; while pos < input.len() { if input[pos].is_ascii_digit() { str.push(input[pos]); pos += 1; } else { break; } } let int = str.parse::().unwrap(); ((TokenKind::INT(int), start, pos), pos) } fn lex_ident(input: &[char], pos: usize) -> (Token, usize) { let start = pos; let mut pos = pos; let mut str = String::new(); while pos < input.len() { let c = input[pos]; if c.is_ascii_alphabetic() || c.is_ascii_digit() || c == '_' || c == '-' { str.push(c); pos += 1; } else { break; } } if str == "true".to_string() { ((TokenKind::BOOL(true), start, pos), pos) } else if str == "false".to_string() { ((TokenKind::BOOL(false), start, pos), pos) } else { ((TokenKind::IDENT(str), start, pos), pos) } } opam-file-rs-0.1.5/src/lib.rs000064400000000000000000000056130000000000000140070ustar 00000000000000/*! # opam-file-rs: Parser and printer for the opam file syntax with Rust [![crates.io][crates-badge]][crates] [![docs.rs][docs-badge]][docs] [![Build Status][ci-badge]][ci] [![source badge][source-badge]][source] [![license badge][license-badge]][license] [crates]: https://crates.io/crates/opam-file-rs [crates-badge]: https://img.shields.io/crates/v/opam-file-rs [docs]: https://docs.rs/opam-file-rs/ [docs-badge]: https://img.shields.io/badge/docs.rs-opam_file_rs-blue [ci]: https://github.com/puripuri2100/opam-file-rs/actions?query=workflow%3ACI [ci-badge]: https://github.com/puripuri2100/opam-file-rs/workflows/CI/badge.svg?branch=master [source]: https://github.com/puripuri2100/opam-file-rs [source-badge]: https://img.shields.io/badge/source-github-blue [license]: https://github.com/puripuri2100/opam-file-rs/blob/master/LICENSE [license-badge]: https://img.shields.io/badge/license-MIT-blue # Parsing OPAM Parse OPAM file. ```rust, ignore use opam_file_rs; fn main () { let opam = r#" opam-version: "2.0" version: "0.1.0" name: "opam-file-rs" dev-repo: "git+https://github.com/puripuri2100/opam-file-rs" license: "MIT" maintainer: "Naoki Kaneko " depends: [ "lalrpop-util" {>= "0.19.4"} "thiserror" {>= "1.0.23"} ] "#; assert!(opam_file_rs::parse(opam).is_ok()); } ``` # Convert to a OPAM file format. A data structure can be converted to an OPAM file format by `value::format_opam_file`. ```rust, ignore use opam_file_rs; fn main() { let opam_str = r#" opam-version: "2.0" version: "0.1.0" name: "opam-file-rs" dev-repo: "git+https://github.com/puripuri2100/opam-file-rs" license: "MIT" maintainer: "Naoki Kaneko " depends: [ "lalrpop-util" {>= "0.19.4"} "thiserror" {>= "1.0.23"} ] "#; let opam = opam_file_rs::parse(opam_str).unwrap(); println!("{}", opam_file_rs::value::format_opam_file(opam)); } ``` --- (c) 2021 Naoki Kaneko (a.k.a. "puripuri2100") */ #[macro_use] extern crate lalrpop_util; use thiserror::Error; mod lexer; pub mod value; mod tests; lalrpop_mod!(parser); #[derive(Debug, Clone, PartialEq, Eq, Hash, Error)] pub enum OpamFileError { #[error("invalid char: {0}")] LexInvalidChar(char, usize, usize), #[error("EOF")] LexEof, #[error("parse error")] Parse, } /// See more [Common file format](https://opam.ocaml.org/doc/Manual.html#Common-file-format) pub fn parse(input: &str) -> Result { let lex_result = lexer::lex(input); let lex = match lex_result { Ok(lex) => lex, Err((lexer::LexErrorKind::InvalidChar(c), start, end)) => { return Err(OpamFileError::LexInvalidChar(c, start, end)) } Err((lexer::LexErrorKind::Eof, _, _)) => return Err(OpamFileError::LexEof), }; match parser::mainParser::new().parse(lex) { Ok(file) => Ok(file), Err(_) => Err(OpamFileError::Parse), } } opam-file-rs-0.1.5/src/parser.lalrpop000064400000000000000000000154740000000000000155700ustar 00000000000000use super::lexer; use super::value; grammar; extern { enum lexer::Token { Tok_EOF => (lexer::TokenKind::EOF , _, _), Tok_STRING => (lexer::TokenKind::STRING(_) , _, _), Tok_IDENT => (lexer::TokenKind::IDENT(_) , _, _), Tok_BOOL => (lexer::TokenKind::BOOL(_) , _, _), Tok_INT => (lexer::TokenKind::INT(_) , _, _), Tok_LBRACKET => (lexer::TokenKind::LBRACKET , _, _), Tok_RBRACKET => (lexer::TokenKind::RBRACKET , _, _), Tok_LPAR => (lexer::TokenKind::LPAR , _, _), Tok_RPAR => (lexer::TokenKind::RPAR , _, _), Tok_LBRACE => (lexer::TokenKind::LBRACE , _, _), Tok_RBRACE => (lexer::TokenKind::RBRACE , _, _), Tok_COLON => (lexer::TokenKind::COLON , _, _), Tok_AND => (lexer::TokenKind::AND , _, _), Tok_OR => (lexer::TokenKind::OR , _, _), Tok_RELOP => (lexer::TokenKind::RELOP(_) , _, _), Tok_PFXOP => (lexer::TokenKind::PFXOP(_) , _, _), Tok_ENVOP => (lexer::TokenKind::ENVOP(_) , _, _), } } pub main: value::OpamFile = { <_eof: Tok_EOF> => { let mut v = items; v.reverse(); value::OpamFile {file_contents: v} }, }; items: Vec = { => { let mut v = items; v.push(item); v }, => Vec::new(), } item: value::OpamFileItem = { <_colon: Tok_COLON> => { let start_pos = ident.1; let end_pos = value.pos.1; let pos = (start_pos, end_pos); let ident = lexer::get_value_string(ident.0).unwrap(); value::OpamFileItem::Variable(pos, ident, value) }, <_lbrace: Tok_LBRACE> => { let start_pos = ident.1; let end_pos = rbrace.2; let pos = (start_pos, end_pos); let ident_kind = ident.0; let ident_string = lexer::get_value_string(ident_kind).unwrap(); let sec_value = value::OpamFileSection { section_kind : ident_string, section_name : None, section_item : items, }; value::OpamFileItem::Section(pos, sec_value) }, <_lbrace: Tok_LBRACE> => { let start_pos = ident.1; let end_pos = rbrace.2; let pos = (start_pos, end_pos); let ident_kind = ident.0; let ident_string = lexer::get_value_string(ident_kind).unwrap(); let string_kind = string.0; let string_string = lexer::get_value_string(string_kind).unwrap(); let sec_value = value::OpamFileSection { section_kind : ident_string, section_name : Some(string_string), section_item : items, }; value::OpamFileItem::Section(pos, sec_value) }, } lorand: value::Value = { => { let start = value1.pos.0; let end = value1.pos.1; let logop = value::LogOp { kind : value::LogOpKind::Or, pos : (or.1, or.2), }; value::Value { pos : (start, end), kind : value::ValueKind::LogOp(logop, Box::new(value1), Box::new(value2)) } }, => { let start = value1.pos.0; let end = value1.pos.1; let logop = value::LogOp { kind : value::LogOpKind::And, pos : (and.1, and.2), }; value::Value { pos : (start, end), kind : value::ValueKind::LogOp(logop, Box::new(value1), Box::new(value2)) } }, => value, } value: value::Value = { => atom, => { let start = lpar.1; let end = rpar.2; let mut v = values; v.reverse(); value::Value { pos : (start, end), kind : value::ValueKind::Group(v) } }, => { let start = lpar.1; let end = rpar.2; let mut v = values; v.reverse(); value::Value { pos : (start, end), kind : value::ValueKind::List(v) } }, <_lpar: Tok_LBRACE> => { let start = value.pos.0; let end = rpar.2; let mut values = values; values.reverse(); value::Value { pos : (start, end), kind : value::ValueKind::Option(Box::new(value), values) } }, // => { // let start = atom1.pos.0; // let end = atom2.pos.1; // let kind = // value::ValueKind::RelOp( // value::RelOp{ // kind: lexer::get_value_relop(relop.0).unwrap(), // pos: (relop.1, relop.2) // }, // Box::new(atom1), // Box::new(atom2) // ); // value::Value { // pos : (start, end), // kind : kind // } //}, => { let start = atom1.pos.0; let end = atom2.pos.1; let kind = value::ValueKind::EnvBinding( Box::new(atom1), value::EnvUpdateOp{ kind: lexer::get_value_env(envop.0).unwrap(), pos: (envop.1, envop.2) }, Box::new(atom2) ); value::Value { pos : (start, end), kind } }, => { let start = pfxop.1; let end = atom.pos.1; let kind = value::ValueKind::PfxOp( value::PfxOp{ kind: lexer::get_value_pfxop(pfxop.0).unwrap(), pos: (pfxop.1, pfxop.2) }, Box::new(atom) ); value::Value { pos : (start, end), kind } }, => { let start = relop.1; let end = atom.pos.1; let kind = value::ValueKind::PrefixRelOp( value::RelOp{ kind: lexer::get_value_relop(relop.0).unwrap(), pos: (relop.1, relop.2) }, Box::new(atom) ); value::Value { pos : (start, end), kind } }, } values: Vec = { => { let mut v = values; v.push(value); v }, => Vec::new(), } atom : value::Value = { => { let kind = b_tok.0; let b = lexer::get_value_bool(kind).unwrap(); value::Value { pos : (b_tok.1, b_tok.2), kind : value::ValueKind::Bool(b) } }, => { let kind = i_tok.0; let i = lexer::get_value_isize(kind).unwrap(); value::Value { pos : (i_tok.1, i_tok.2), kind : value::ValueKind::Int(i) } }, => { let kind = i_tok.0; let i = lexer::get_value_string(kind).unwrap(); value::Value { pos : (i_tok.1, i_tok.2), kind : value::ValueKind::Ident(i) } }, => { let kind = s_tok.0; let i = lexer::get_value_string(kind).unwrap(); value::Value { pos : (s_tok.1, s_tok.2), kind : value::ValueKind::String(i) } }, } opam-file-rs-0.1.5/src/tests.rs000064400000000000000000000024650000000000000144050ustar 00000000000000#[test] fn check_parse() { use crate::value::*; let opam_str = r#" opam-version: "2.0" depends: [ "lalrpop-util" {>= "0.19.4"} ] "#; let opam_data = OpamFile { file_contents: vec![ OpamFileItem::Variable( (5, 24), "opam-version".to_string(), Value { kind: ValueKind::String("2.0".to_string()), pos: (19, 24), }, ), OpamFileItem::Variable( (29, 80), "depends".to_string(), Value { kind: ValueKind::List(vec![Value { kind: ValueKind::Option( Box::new(Value { kind: ValueKind::String("lalrpop-util".to_string()), pos: (46, 60), }), vec![Value { kind: ValueKind::PrefixRelOp( RelOp { kind: RelOpKind::Geq, pos: (62, 64), }, Box::new(Value { kind: ValueKind::String("0.19.4".to_string()), pos: (65, 73), }), ), pos: (62, 73), }], ), pos: (46, 74), }]), pos: (38, 80), }, ), ], }; assert_eq!(crate::parse(opam_str).unwrap(), opam_data); } opam-file-rs-0.1.5/src/value.rs000064400000000000000000000144170000000000000143570ustar 00000000000000#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct OpamFile { pub file_contents: Vec, } #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum OpamFileItem { Section(Pos, OpamFileSection), Variable(Pos, String, Value), } #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct OpamFileSection { pub section_kind: String, pub section_name: Option, pub section_item: Vec, } #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct Value { pub kind: ValueKind, pub pos: Pos, } #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum ValueKind { Bool(bool), Int(isize), String(String), /// RelOp: `=`, `!=`, `>=`, `<`, `<=`, `~` /// BNF: ` ` RelOp(RelOp, Box, Box), /// RelOp: `=`, `!=`, `>=`, `<`, `<=` and `~` /// BNF: ` `, ` ` /// Example: `"foo" {>= "1.1.0"}` PrefixRelOp(RelOp, Box), /// PfxOp: `&`, `|` /// BNF: ` `, ` `, ... /// Example: `"bar" {>= "1.1.0" & < "2.0.0"}` LogOp(LogOp, Box, Box), /// PfxOp: `!`, `?` /// BNF: `"!" `, `"?" ` PfxOp(PfxOp, Box), Ident(String), /// BNF: `"[" * "]"` List(Vec), /// BNF: `"(" * ")"` Group(Vec), /// BNF: ` "{" * "}"` /// Example: `"baz" { "build" }` Option(Box, Vec), /// EnvUpdateOp: `+=`, `=+`, `:=`, `=:`. `=+=` EnvBinding(Box, EnvUpdateOp, Box), } #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct RelOp { pub kind: RelOpKind, pub pos: Pos, } #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum RelOpKind { /// `=` Eq, /// `!=` Neq, /// `>=` Geq, /// `>` Gt, /// `<=` Leq, /// `<` Lt, /// `~` Sem, } #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct LogOp { pub kind: LogOpKind, pub pos: Pos, } #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum LogOpKind { /// `&` And, /// `|` Or, } #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct PfxOp { pub kind: PfxOpKind, pub pos: Pos, } #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum PfxOpKind { /// `!` Not, /// `?` Defined, } #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct EnvUpdateOp { pub kind: EnvUpdateOpKind, pub pos: Pos, } #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum EnvUpdateOpKind { ///// `=` //Eq, /// `+=` PlusEq, /// `=+` EqPlus, /// `:=` ColonEq, /// `=:` EqColon, /// `=+=` EqPlusEq, } pub type Pos = (usize, usize); /// Convert to an OPAM file format. pub fn format_opam_file(input: &OpamFile) -> String { let file_contents = &input.file_contents; opam_file_item_vec_to_string(file_contents) } fn opam_file_item_vec_to_string(value: &Vec) -> String { value .iter() .map(|file_content| { let str = match file_content { OpamFileItem::Section(_, opam_file_section) => { let kind = &opam_file_section.section_kind; let section_name = opam_file_section .clone() .section_name .unwrap_or_else(|| String::new()); let section_item_str = opam_file_item_vec_to_string(&opam_file_section.section_item); format!("{} {} {{{}}}", kind, section_name, section_item_str) } OpamFileItem::Variable(_, ident, value) => { format!("{} : {}", ident, value_to_string(value)) } }; format!("{}\n", str) }) .collect::() } fn value_to_string(value: &Value) -> String { match &value.kind { ValueKind::Bool(b) => b.to_string(), ValueKind::Int(i) => i.to_string(), ValueKind::String(str) => format!("{:?}", str), ValueKind::Ident(str) => str.to_string(), ValueKind::List(lst) => { format!( "[{}]", lst .iter() .map(|value| format!("{} ", value_to_string(value))) .collect::() ) } ValueKind::Group(lst) => { format!( "({})", lst .iter() .map(|value| format!("{} ", value_to_string(value))) .collect::() ) } ValueKind::Option(v, lst) => { format!( "{} {{{}}}", lst .iter() .map(|value| format!("{} ", value_to_string(value))) .collect::(), value_to_string(v) ) } ValueKind::RelOp(op, l, r) => { format!( "{} {} {}", value_to_string(l), relop_to_string(&op.kind), value_to_string(r), ) } ValueKind::PrefixRelOp(op, r) => { format!("{} {}", relop_to_string(&op.kind), value_to_string(r),) } ValueKind::LogOp(op, l, r) => { format!( "{} {} {}", value_to_string(l), logop_to_string(&op.kind), value_to_string(r), ) } ValueKind::PfxOp(op, r) => { format!("{} {}", pfxop_to_string(&op.kind), value_to_string(r),) } ValueKind::EnvBinding(l, op, r) => { format!( "{} {} {}", value_to_string(l), envop_to_string(&op.kind), value_to_string(r), ) } } } fn relop_to_string(op: &RelOpKind) -> String { match op { RelOpKind::Eq => "=".to_string(), RelOpKind::Neq => "!=".to_string(), RelOpKind::Geq => ">=".to_string(), RelOpKind::Gt => ">".to_string(), RelOpKind::Leq => "<=".to_string(), RelOpKind::Lt => "<".to_string(), RelOpKind::Sem => "~".to_string(), } } fn logop_to_string(op: &LogOpKind) -> String { match op { LogOpKind::And => "&".to_string(), LogOpKind::Or => "|".to_string(), } } fn pfxop_to_string(op: &PfxOpKind) -> String { match op { PfxOpKind::Not => "!".to_string(), PfxOpKind::Defined => "?".to_string(), } } fn envop_to_string(op: &EnvUpdateOpKind) -> String { match op { EnvUpdateOpKind::PlusEq => "+=".to_string(), EnvUpdateOpKind::EqPlus => "=+".to_string(), EnvUpdateOpKind::ColonEq => ":=".to_string(), EnvUpdateOpKind::EqColon => "=:".to_string(), EnvUpdateOpKind::EqPlusEq => "=+=".to_string(), } }