Rust로 파티 그룹 만들어 보기(후편)
후편에서는 전편에서 제작한 파트너를 활용해 JSON Party를 제작한다.JSON의 문법은 "Rust로 파라 그룹 만들어 보기(전편)"에 쓰여 있기 때문에 충실하게 실시해야 한다.
json::Value
파티를 만들기 전에 먼저 JSON 값을 나타내는 struct를 정의합니다.
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Null,
False,
True,
Number(f64),
String(String),
Array(Vec<Value>),
Object(HashMap<String, Value>),
}
Rust는 강력한 enum을 가지고 있기 때문에 매우 자연스럽게 정의할 수 있다.편리 함수 정의
JSON Parser 설치
lexeme(string(..))
와 lexeme(character(..))
에 여러 번 나타날 수 있으므로 함수화하십시오.fn lstring(target: &'static str) -> impl parsers::Parser<()> {
parsers::lexeme(parsers::string(target))
}
fn lcharacter(c: char) -> impl parsers::Parser<()> {
parsers::lexeme(parsers::character(c))
}
여기서 설명한 parsers
모듈은 앞서 정의한 파트너를 저장하는 모듈입니다.null, false, true
그럼 JSON Parer를 만들어 봅시다.파라 콤비네이터의 장르에 맞춰 단순 파트를 만들어 합성한다는 방침으로 진행됐다.
우선
null
,false
,true
의 서비스부터 시작합시다.단순 키워드lstring
로만 지울 수 있다.그리고 결과의 값으로 바꾸기 위해서parsers::map
하면 됩니다.fn null(s: &str) -> Option<(Value, &str)> {
let p = lstring("null");
let p = parsers::map(p, |_| Value::Null);
p(s)
}
fn false_(s: &str) -> Option<(Value, &str)> {
let p = lstring("false");
let p = parsers::map(p, |_| Value::False);
p(s)
}
fn true_(s: &str) -> Option<(Value, &str)> {
let p = lstring("true");
let p = parsers::map(p, |_| Value::True);
p(s)
}
number다음은number입니다.JSON의number는 예상치 못한 복잡한 규격으로 일반적인 타법은 어려울 것 같지만 우리는
regex!
가 있기 때문에 한꺼번에 칠 수 있다.fn number(s: &str) -> Option<(Value, &str)> {
const PATTERN: &str = r"^-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?";
let p = crate::regex!(PATTERN, |s| s.parse::<f64>().ok());
let p = parsers::lexeme(p);
let p = parsers::map(p, |x| Value::Number(x));
p(s)
}
정규 표현식은 정말 위대하다.String
문자열의 퍼스도 어렵다.일반적인 문자와 탈출하는 문자가 있고, 탈출도 반사봉 + 특정한 문자와 반사봉 다음의'u'+4위hex'가 있다.또한 문자열 소양에 제어 문자를 쓸 수 없습니다.
실현은 다음과 같다.좀 길긴 했지만 최대한 일대일로 대응하는 스타일과 코드를 써 봤어요.
특별히
fn json_string(s: &str) -> Option<(Value, &str)> {
parsers::map(json_string_raw, Value::String)(s)
}
fn json_string_raw(s: &str) -> Option<(String, &str)> {
// string = '"' character* '"'
let p = crate::join![
parsers::character('"'),
parsers::many(json_character),
parsers::character('"')
];
let p = parsers::lexeme(p);
let p = parsers::map(p, |((_, chars), _)| {
chars.into_iter().collect()
});
p(s)
}
fn json_character(s: &str) -> Option<(char, &str)> {
// character = <Any codepoint except " or \ or control characters>
// | '\u' <4 hex digits>
// | '\"' | '\\' | '\/' | '\b' | '\f' | '\n' | '\r' | '\t'
crate::choice![
crate::regex!(r#"^[^"\\[:cntrl:]]"#, |s| s.chars().next()),
crate::regex!(r#"^\\u[0-9a-fA-F]{4}"#, hex_code),
crate::regex!(r#"^\\."#, escape)
](s)
}
fn hex_code(code: &str) -> Option<char> {
code.strip_prefix(r"\u").and_then(|hex|
u32::from_str_radix(hex, 16).ok().and_then(|cp|
char::from_u32(cp)
)
)
}
fn escape(s: &str) -> Option<char> {
match s {
"\\\"" => Some('"'),
"\\\\" => Some('\\'),
"\\/" => Some('/'),
"\\b" => Some('\x08'),
"\\f" => Some('\x0C'),
"\\n" => Some('\n'),
"\\r" => Some('\r'),
"\\t" => Some('\t'),
_ => None // undefined escape sequence
}
}
json_string
와 json_string_raw
를 분리한 것은 이후 Object의 퍼스에서 재사용하고 싶어서다.Array
Aray는 String보다 간단합니다.
fn array(s: &str) -> Option<(Value, &str)> {
let p = crate::join![
lcharacter('['),
parsers::separated(json_value, lcharacter(',')),
lcharacter(']')
];
let p = parsers::map(p, |((_, values), _)| Value::Array(values));
p(s)
}
본 설치에 나타난 json_value
는 임의의 JSON 값을 제거할 수 있는 Percer입니다.이 마지막 정의는Object
Object는 Arry와 비슷한 느낌으로 정의할 수도 있습니다.
fn object(s: &str) -> Option<(Value, &str)> {
let p = crate::join![
lcharacter('{'),
parsers::separated(key_value, lcharacter(',')),
lcharacter('}')
];
let p = parsers::map(p, |((_, key_values), _)| {
let h = HashMap::from_iter(key_values.into_iter());
Value::Object(h)
});
p(s)
}
fn key_value(s: &str) -> Option<((String, Value), &str)> {
// key_value = string ':' json_value
let p = crate::join![
json_string_raw,
lcharacter(':'),
json_value
];
let p = parsers::map(p, |((key, _), value)| (key, value));
p(s)
}
총결산마지막으로 지금까지 정의한 퍼서
choice!
를 하나의 퍼서로 정리하면 JSON 퍼서가 완성됩니다!직접 공개
fn json_value(s: &str) -> Option<(Value, &str)> {
crate::choice![
null,
false_,
true_,
number,
json_string,
array,
object
](s)
}
json_value
할 수 있지만 Option<(Value, &str)>
의 반환값은 일반적인 용도에서 미묘하기 때문에 반환Option<Value>
의 함수로 wrap을 진행하여 공개한다.하지만rest
부분에쓰레기가 남아있으면JSON이 정확하지 않아잘못될 수 있다.pub fn parse(s: &str) -> Option<Value> {
json_value(s).and_then(|(value, rest)| {
if rest.chars().all(|c| c.is_ascii_whitespace()) {
Some(value)
} else {
None
}
})
}
JSON Parer 설치가 완료되었습니다.끝말
어때요?퍼서 합성을 통해 복잡한 퍼서 그룹을 만들 수 있다는 강함이 실감을 얻었잖아(절반 정도는 정규적 표현의 힘이지만).
Reference
이 문제에 관하여(Rust로 파티 그룹 만들어 보기(후편)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/nojima/articles/e597d22660205d텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)