녹계산기는 간단한 녹계산기를 만든다

최근에 나는 Rust의lexer 해상도 생성기 lalrpop 를 만났다.저도 전에 C에서 flex bison pair를 사용한 적이 있는데 Lalrpop을 본 후에 Rust에서도 같은 용도가 있다고 생각했어요.초보자 프로젝트에 대해 나는 간단한 계산기를 만들었는데, 방정식을 풀 수도 있고, 변수를 저장하고 사용할 수도 있다.

최종 결과
>>>>> 5*2
10
>>>>> 2**8
256
>>>>> 3/2
1.5
>>>>> var1 = 5
5
>>>>> var1 *2
10
>>>>> var2 + 2
Error : Undefined Symbol
>>>>> var1 ** 2
25
>>>>> 


일부 (비공식) 정의:

  • Lexer는 텍스트 흐름을 받아서 표시 흐름을 주는 물건이다.

  • 기호화폐는 우리가 쓰고 있는'언어'의 일부분으로 여겨질 수 있다.이 계산기에 대해 언어는 수학, 숫자, 연산자는 기호로 볼 수 있다.더 큰 측면에서 보면 프로그래밍 언어에 있어 숫자, 문자열 문자, 변수, 키워드는 모두 표기이다.

  • 해석기는 영패의 흐름을 수신하고 정의된 패턴에 따라 그것들을 처리한다.이 계산기에서 모델은 '2' '+' '3'로 여겨질 수 있는데 그 중에서'2','3','+'는 표시이고 채택한 조작은 값의 가산법이 될 수 있다.
  • 이 프로젝트에 대해, 우리는 lalrpop crate가 제공하는 기본lexer를 사용하고, 해석기에서 해석에 사용할 규칙 (모드) 을 작성할 것입니다.라르보프 파일.

    첫걸음
    Rust 프로젝트를 만드는 것부터 시작하겠습니다.
    프로젝트 디렉토리에서 cargo init 실행
    cargo init .
    
    이후에 우리는 화물을 편집할 것이다.toml 파일, lalrpop 및 regex 종속성 추가:
    [dependencies]
    lalrpop-util = "0.19.1"
    regex = "1"
    
    Lalrpop에는 괜찮은 문서와 입문 안내서here가 있는데, 우리는 그들이 제공한 기본 코드를 사용하는 것부터 시작할 것이다.우리가 직접 쓰는 것은 결코 어렵지 않지만, 그것은 우리의 계산기에 매우 간단하고 빠른 출발점을 제공한다.

    파서 규칙 작성
    파서라는 파일을 만듭니다.llrpop은 src/에서 주어진 코드here를 복사하고 정수 방정식을 해석하기 위해 기점을 설정합니다.
    이를 더욱 통용시키기 위해 우리는 i32를 f32로 바꾸었다. 편의를 위해 명칭을 약간 줄인 후에 코드는 다음과 같다.
    use std::str::FromStr;
    
    grammar;
    
    pub Expr:f32 = {
        <e:Expr> "+" <f:Factor> => e+f,
        <e:Expr> "-" <f:Factor> => e-f,
        <Factor>
    };
    
    Factor:f32 = {
        <f:Factor> "*" <t:Term> => f*t,
        <f:Factor> "/" <t:Term> => f/t,
        <Term>
    };
    
    Term: f32 = {
        Num,
        "(" <Expr> ")",
    };
    
    Num: f32 = <s:r"[0-9]+\.?[0-9]*"> => f32::from_str(s).unwrap();
    
    
    이제 코드를 설명합니다.grammer는 입력을 해석하는 함수입니다.지금은 사용하지 않았지만, 우리는 잠시 후에 그것을 사용할 것이다.규칙은 넥타이를 깨는 데 사용되기 때문에 위에서 아래로 내려오는 방식으로 제시되지만 혼란스러운 방식으로 이해하기 쉽다.
  • Num는 가장 작고 가장 간단한 영패로 부동점수를 정의한다.
  • Term는 표현식의 최소 부분을 정의하는 데 사용되며 숫자일 수도 있고 괄호 안에 있는 Expr일 수도 있다.
  • FactorTerm 또는 기존Factor* 또는/aTerm일 수 있습니다.이것은 다음과 같다. 표현식'2*3/6'에 대해 우리는'2'를 보고 이를 Num로 분류할 것이다.이것은 한층 더 aTerm와 aFactor로 나눌 수 있다."*"를 본 후에 우리는 규칙을 일치시키려고 시도했는데 Factor의 첫 번째 규칙과 일치할 수 있다는 것을 발견했다. "2"가 하나Factor라는 것을 알고 우리는 2*3을 "6"으로 줄이고 다시 하나Factor로 분류할 수 있다."/"를 본 후, 우리는 규칙을 찾아 Factor 의 두 번째 규칙과 일치하도록 하고, 이를 "6/6"으로 1로 풀려고 했다.
  • ExprFactor 또는 기존Expr + 또는 -Factor이다.
  • 분리FactorExpr는 중요한 질서를 유지하기 위해서다.aExpr에 대해 우리는 aFactor가 필요하기 때문에 곱셈과 나눗셈은 가법과 감법보다 우선한다.
    이 기능을 실행하려면 먼저 lalrpop 규칙에서 Rust로 해석기를 컴파일해야 합니다. 이것은 lalrpop 박스에서 완성된 것이지만, 구축 부분에서 지정해야 합니다.
    #Cargo.toml
    build = "src/build.rs"
    
    [build-dependencies]
    lalrpop = { version = "0.19.1", features = ["lexer"] }
    
    우리는 또 판자 상자로 제품을 포장할 것이다.src/의rs 파일에는 주 기능이 있습니다.
    extern crate lalrpop;
    
    fn main() {
        lalrpop::process_root().unwrap();
    }
    
    이것은 우리 프로그램의 주요 기능이 아니라 컴파일러가 실행하기 전에 실행하는 프로그램이기 때문에build이라고 부른다.rs.이것은 프로젝트의 루트 폴더를 보고 lalrpop 파일을rust 파일로 컴파일하는 것입니다.
    프로그램을 테스트하려면 파일을main으로 설정하십시오.src/의rs:
    #[macro_use]
    extern crate lalrpop_util;
    lalrpop_mod!(pub parser); //defines parser mod
    fn main(){
         // Output 4
         match parser::ExprParser::new().parse("2+2") {
                Ok(v) => println!("{}", v),
                Err(e) => println!("Error : {}", e),
            }
    }
    
    이 이름ExprParser은lalrpop 파일에서Exprpub로 설정했기 때문입니다.lalrpop 파일에 발표된 모든 부분은parser에 해당하는 해상도가 있습니다.
    계산기에 멱 연산을 추가합시다.토큰Factor:
    Factor:f32 = {
        <f:Factor> "**" <t:Term> => f.powf(t),
        <f:Factor> "*" <t:Term> => f*t,
        <f:Factor> "/" <t:Term> => f/t,
        <Term>
    };
    
    지금 우리는 2*4를 써서 24를 얻을 수 있다.
    마지막으로 변수를 저장하고 사용하는 기능을 추가합시다.이를 위해, 우리는 우선lalrpop 파일에 변수나 Symbol 표시를 정의해야 한다.일반적으로 프로그래밍 언어에서, 우리는 변수의 밑줄이나 자모의 시작을 허용하고, 그 안에 자모, 밑줄, 숫자를 포함할 것이다.
    파서에서lalrpop 파일의 마지막 추가:
    Symbol:String = <s:r"[_a-zA-Z][_a-zA-Z0-9]*"> => s.to_owned();
    
    이제 HashMap을 사용하여 변수와 해당 값을 저장합니다.해석기의 매개 변수로 사용하기 위해서 앞에서 언급한 grammar 을 사용합니다.프로그램이 끝날 때까지 변수가 계속되기를 원하기 때문에 같은 HashMap을 한 번 또 한 번 사용해야 하기 때문에 값이 아닌 가변 참조를 통해
    use std::collections::HashMap;
    use lalrpop_util::ParseError;
    
    grammar<'s>(symtab:&'s mut HashMap<String,f32>);
    
    우리는 해시맵의 생존 기간이 일방통행 해석의 지속 시간과 같도록 요구함으로써 생존 기간을 제한한다.ParseError는 error로 돌아가야 합니다. 곧 볼 것입니다.
    이제 변수에 맞게 outTerm 정의를 수정해야 합니다.
    Term: f32 = {
        <Num>,
        "(" <Expr> ")",
        <s:Symbol> =>? match symtab.get(&s){
            Some(v)=>Ok(*v),
            None=>Err(ParseError::User{error:"Undefined Symbol"})
        }
    };
    
    여기 =>? 는 f32나 오류를 되돌릴 수 있음을 의미하며,lalrpop에서 Rust로 컴파일할 때 내부에서 결과로 변환됩니다.
    여기서 우리는 Term를 숫자, 괄호 안의 Expr 또는 Symbol로 정의할 것이다.기호가 out HashMap에 존재하는지 확인합니다. 존재하면 해당하는 값을 반환하고 존재하지 않으면 오류를 반환합니다.
    이것은 변수의 사용만 정의했다.처음에 변수에 값을 부여하기 위해서, 우리는 name = value 유형의 문장이 필요하다.이 규칙을 정의하기 위해서 우리는 Statement에 새로운 영패Expr를 정의하고 이를 공개하여 pub에서 삭제Expr했다.
    pub Statement: f32 = {
        Expr,
        <s:Symbol> "=" <e:Expr>  =>{
            symtab.insert(s,e);
            e
        }
    }
    
    여기에서 aStatement는 하나의 표현식으로 그 값만 되돌려줄 수도 있고 부여할 수도 있다. 이런 상황에서 우리는 이름의 값을 키로 삽입하고 값을 되돌려줄 것이다.변수가 이미 존재하면 자동으로 덮어씁니다.
    마지막으로 해상도입니다.라르보프는:
    use std::str::FromStr;
    use std::collections::HashMap;
    use lalrpop_util::ParseError;
    
    grammar<'s>(symtab:&'s mut HashMap<String,f32>);
    
    pub Statement: f32 = {
        Expr,
        <s:Symbol> "=" <e:Expr>  =>{
            symtab.insert(s,e);
            e
        }
    }
    
    Expr:f32 = {
        <e:Expr> "+" <f:Factor> => e+f,
        <e:Expr> "-" <f:Factor> => e-f,
        <Factor>
    };
    
    Factor:f32 = {
        <f:Factor> "**" <t:Term> => f.powf(t),
        <f:Factor> "*" <t:Term> => f*t,
        <f:Factor> "/" <t:Term> => f/t,
        <Term>
    };
    
    
    Term: f32 = {
        <Num>,
        "(" <Expr> ")",
        <s:Symbol> =>? match symtab.get(&s){
            Some(v)=>Ok(*v),
            None=>Err(ParseError::User{error:"Undefined Symbol"})
        }
    };
    
    Num: f32 = <s:r"[0-9]+\.?[0-9]*"> => f32::from_str(s).unwrap();
    
    Symbol:String = <s:r"[_a-zA-Z][_a-zA-Z0-9]*"> => s.to_owned();
    
    이제 기본 파일에서 입력기를 설정합니다.
    #[macro_use]
    extern crate lalrpop_util;
    use std::collections::HashMap;
    use std::io::BufRead;
    use std::io::Write;
    
    lalrpop_mod!(pub parser);
    
    fn main() {
        let mut symtab = HashMap::new();
        print!(">>>>> ");
        std::io::stdout().flush().unwrap();
        for line in std::io::stdin().lock().lines() {
            let line = line.expect("Input Error");
            match parser::StatementParser::new().parse(&mut symtab, line.trim()) {
                Ok(v) => println!("{}", v),
                Err(e) => println!("Error : {}", e),
            }
            print!(">>>>> ");
            std::io::stdout().flush().unwrap();
        }
    }
    
    이것은 입력 장식으로 '>> >> >' 을 인쇄한 다음 입력을 연속으로 읽고, 모든 줄을 해상도에 건네주고, 출력이 되돌아오는 결과나 오류를 출력합니다.
    이것은 내가 제기한 기본 프로젝트로, 목적은lalrpop을 어떻게 사용하는지 이해하는 것이다.상수나 함수를 추가하여 수정할 수 있습니다.
    즐겁게 읽었으면 좋겠어요!이 기능을 사용하여 어떤 내용을 만들었거나, 이전에lalrpop을 사용한 적이 있다면, 댓글에 알려 주십시오.
    감사합니다!

    좋은 웹페이지 즐겨찾기