ANTLR'de 'anlamsal yüklem' nedir?


102

ANTLR'de anlamsal yüklem nedir ?


3
Anlamsal yüklemin ne olduğunu bilmek isteyen biri için yayınlayacak düzgün bir çevrimiçi kaynak bulamadığımdan , soruyu buraya kendim göndermeye karar verdiğime dikkat edin (kısaca kendim de yanıtlayacağım).
Bart Kiers

1
Bunu yaptığınız için teşekkürler; İnsanların kendi sorularını yanıtlamasını her zaman seviyorum, özellikle de soruyu bu şekilde yanıtlamak için sorarlarsa.
Daniel H

1
Kitabı oku. Kesin ANTLR 4 Referansı Bölüm 11 semantik yüklemler üzerinedir. Kitap yok mu? Anla! Her dolara değer.
james.garriss

Yanıtlar:


169

ANTLR 4

ANTLR 4'teki tahminler için, bu yığın taşması Soru ve Cevaplarına göz atın :


ANTLR 3

Bir semantik yüklem yalın kodu kullanarak dilbilgisi eylemleri üzerine ekstra (semantik) kuralları uygulamak için bir yoldur.

3 tür anlamsal yüklem vardır:

  • anlamsal yüklemleri doğrulama ;
  • geçitli anlamsal yüklemler;
  • belirsizliği ortadan kaldıran anlamsal yüklemler.

Örnek dilbilgisi

Diyelim ki beyaz boşlukları göz ardı ederek, yalnızca virgülle ayrılmış sayılardan oluşan bir metin bloğunuz var. Numaraların en fazla 3 basamaklı "uzun" (en fazla 999) olduğundan emin olarak bu girişi ayrıştırmak istersiniz. Aşağıdaki gramer ( Numbers.g) böyle bir şey yapar:

grammar Numbers;

// entry point of this parser: it parses an input string consisting of at least 
// one number, optionally followed by zero or more comma's and numbers
parse
  :  number (',' number)* EOF
  ;

// matches a number that is between 1 and 3 digits long
number
  :  Digit Digit Digit
  |  Digit Digit
  |  Digit
  ;

// matches a single digit
Digit
  :  '0'..'9'
  ;

// ignore spaces
WhiteSpace
  :  (' ' | '\t' | '\r' | '\n') {skip();}
  ;

Test yapmak

Dilbilgisi aşağıdaki sınıfla test edilebilir:

import org.antlr.runtime.*;

public class Main {
    public static void main(String[] args) throws Exception {
        ANTLRStringStream in = new ANTLRStringStream("123, 456, 7   , 89");
        NumbersLexer lexer = new NumbersLexer(in);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        NumbersParser parser = new NumbersParser(tokens);
        parser.parse();
    }
}

Sözcük ve ayrıştırıcı oluşturarak, tüm .javadosyaları derleyerek ve Mainsınıfı çalıştırarak test edin :

java -cp antlr-3.2.jar org.antlr.Tool Numbers.g
javac -cp antlr-3.2.jar * .java
java -cp.: antlr-3.2.jar Ana

Bunu yaparken, konsola hiçbir şey yazdırılmaz, bu da hiçbir şeyin yanlış gitmediğini gösterir. Değiştirmeyi deneyin:

ANTLRStringStream in = new ANTLRStringStream("123, 456, 7   , 89");

içine:

ANTLRStringStream in = new ANTLRStringStream("123, 456, 7777   , 89");

ve testi tekrar yapın: dizeden hemen sonra konsolda görünen bir hata göreceksiniz 777.


Anlamsal Dayanaklar

Bu bizi anlamsal yüklemlere getirir. 1-10 basamaklı sayıları ayrıştırmak istediğinizi varsayalım. Şöyle bir kural:

number
  :  Digit Digit Digit Digit Digit Digit Digit Digit Digit Digit
  |  Digit Digit Digit Digit Digit Digit Digit Digit Digit
     /* ... */
  |  Digit Digit Digit
  |  Digit Digit
  |  Digit
  ;

hantal hale gelirdi. Anlamsal yüklemler bu tür bir kuralı basitleştirmeye yardımcı olabilir.


1. Anlamsal Tahminleri Doğrulama

Bir doğrulama anlamsal yüklem bir soru işareti gelen kod bloğu başka bir şey değildir:

RULE { /* a boolean expression in here */ }?

Doğrulayıcı bir anlamsal yüklem kullanarak yukarıdaki sorunu çözmek numberiçin dilbilgisindeki kuralı şu şekilde değiştirin :

number
@init { int N = 0; }
  :  (Digit { N++; } )+ { N <= 10 }?
  ;

Parçalar { int N = 0; }ve { N++; }ilk ayrıştırıcı "girer" ne zaman başlatılır olan düz Java ifadelerdir numberkuralı. Asıl dayanak şudur: { N <= 10 }?çözümleyicinin, FailedPredicateException bir sayı 10 basamaktan uzun olduğunda bir atmasına neden olur .

Aşağıdakileri kullanarak test edin ANTLRStringStream:

// all equal or less than 10 digits
ANTLRStringStream in = new ANTLRStringStream("1,23,1234567890"); 

bu bir istisna oluşturmaz, ancak aşağıdaki durum bir istisna oluşturmaz:

// '12345678901' is more than 10 digits
ANTLRStringStream in = new ANTLRStringStream("1,23,12345678901");

2. Geçitli Anlamsal Tahminler

Bir kapı semantik yüklem bir benzer doğrulama semantik yüklem , sadece kapı sürümü yerine bir sözdizim hatası üretir FailedPredicateException.

Geçitli bir anlamsal yüklemin sözdizimi şöyledir:

{ /* a boolean expression in here */ }?=> RULE

Bunun yerine yukarıdaki sorunu 10 haneye kadar olan sayıları eşleştirmek için geçitli tahminler kullanarak çözmek için şunu yazarsınız:

number
@init { int N = 1; }
  :  ( { N <= 10 }?=> Digit { N++; } )+
  ;

Her ikisiyle de tekrar test edin:

// all equal or less than 10 digits
ANTLRStringStream in = new ANTLRStringStream("1,23,1234567890"); 

ve:

// '12345678901' is more than 10 digits
ANTLRStringStream in = new ANTLRStringStream("1,23,12345678901");

ve sonuncunun bir hata atacağını göreceksiniz.


3. Anlamsal Yordamların Belirsizliği

Son yüklem türü, belirsizliği ortadan kaldıran bir anlamsal yüklemdir , bu biraz doğrulayıcı yüklem ( {boolean-expression}?) gibi görünür , ancak daha çok kapılı bir anlamsal yüklem gibi davranır (boole ifadesi değerlendirildiğinde istisna atılmaz false). Bir kuralın bazı özelliklerini kontrol etmek ve ayrıştırıcının söz konusu kuralla eşleşip eşleşmemesini sağlamak için bir kuralın başlangıcında bunu kullanabilirsiniz.

Örnek dilbilgisinin Number0..999 aralığındaki sayılarla eşleşecek jetonlar (ayrıştırıcı kuralı yerine sözcük kuralı) oluşturduğunu varsayalım. Şimdi ayrıştırıcıda, düşük ve yüksek sayılar arasında bir ayrım yapmak istersiniz (düşük: 0..500, yüksek: 501..999). Bu, düşük mü yoksa yüksek mi olduğunu kontrol etmek için akışta ( ) sonraki belirteci incelediğiniz, belirsizliği ortadan kaldıran bir anlamsal tahmin kullanılarak yapılabilir input.LT(1).

Bir demo:

grammar Numbers;

parse
  :  atom (',' atom)* EOF
  ;

atom
  :  low  {System.out.println("low  = " + $low.text);}
  |  high {System.out.println("high = " + $high.text);}
  ;

low
  :  {Integer.valueOf(input.LT(1).getText()) <= 500}? Number
  ;

high
  :  Number
  ;

Number
  :  Digit Digit Digit
  |  Digit Digit
  |  Digit
  ;

fragment Digit
  :  '0'..'9'
  ;

WhiteSpace
  :  (' ' | '\t' | '\r' | '\n') {skip();}
  ;

Şimdi dizeyi ayrıştırırsanız "123, 999, 456, 700, 89, 0", aşağıdaki çıktıyı görürsünüz:

low  = 123
high = 999
low  = 456
high = 700
low  = 89
low  = 0

12
Dostum,
ANTLR'ye

5
@Bart Kiers: Lütfen ANTLR hakkında bir kitap yazın
santosh singh

2
ANTLR v4 input.LT(1)için getCurrentToken()artık :-)
Xiao Jia

Harika ... Bu, belgelerde olması gereken türden ayrıntılı açıklama ve örneklerdir!
Ezekiel Victor

+1. Bu cevap, Kesin ANTLR 4 Referans kitabından çok daha iyidir. Bu cevap, güzel örneklerle konseptin tam üzerinde.
asyncwait

11

Wincent.com'daki ANTLR tahminlerine kısa referansı her zaman kılavuzum olarak kullandım.


6
Evet, mükemmel bir bağlantı! Ancak, belirttiğiniz gibi, ANTLR'ye yeni (nispeten) yeni biri için biraz zor olabilir. Umarım cevabım ANTLR çim-hunisi için (biraz) daha dostça olur. :)
Bart Kiers
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.