Rust로 파티 그룹 만들어 보기(후편)

43108 단어 Rustparsertech
네.
후편에서는 전편에서 제작한 파트너를 활용해 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_stringjson_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 설치가 완료되었습니다.
끝말
어때요?퍼서 합성을 통해 복잡한 퍼서 그룹을 만들 수 있다는 강함이 실감을 얻었잖아(절반 정도는 정규적 표현의 힘이지만).

좋은 웹페이지 즐겨찾기