2010-02-26 14 views
8

Piszę interpretator rachunku lambda dla zabawy i ćwiczeń. Mam iostreams prawidłowo tokenize identyfikatorów dodając ctype aspekt, który określa jako białe znaki interpunkcyjne (! classic_table() prawdopodobnie być czystsze, ale to nie działa na OS X)przesłonięcie ctype <wchar_t>

struct token_ctype : ctype<char> { 
mask t[ table_size ]; 
token_ctype() 
: ctype<char>(t) { 
    for (size_t tx = 0; tx < table_size; ++ tx) { 
    t[tx] = isalnum(tx)? alnum : space; 
    } 
} 
}; 

A potem zamienić aspekt, gdy uderzyłem w identyfikator:

locale token_loc(in.getloc(), new token_ctype); 
… 
locale const &oldloc = in.imbue(token_loc); 
in.unget() >> token; 
in.imbue(oldloc); 

Wygląda na zaskakująco mały kod różniczkowy lambda w Internecie. Większość z tego, co dotąd odkryłem, jest pełna znaków Unicode λ. Pomyślałem, że spróbuję dodać obsługę Unicode.

Ale ctype<wchar_t> działa zupełnie inaczej niż ctype<char>. Nie ma tabeli głównej; istnieją cztery metody: do_is x2, do_scan_is i do_scan_not. Tak więc sam sposób:

struct token_ctype : ctype<wchar_t> { 
typedef ctype<wchar_t> base; 

bool do_is(mask m, char_type c) const { 
    return base::do_is(m,c) 
    || (m&space) && (base::do_is(punct,c) || c == L'λ'); 
} 

const char_type* do_is 
    (const char_type* lo, const char_type* hi, mask* vec) const { 
    base::do_is(lo,hi,vec); 
    for (mask *vp = vec; lo != hi; ++ vp, ++ lo) { 
    if (*vp & punct || *lo == L'λ') *vp |= space; 
    } 
    return hi; 
} 

const char_type *do_scan_is 
    (mask m, const char_type* lo, const char_type* hi) const { 
    if (m & space) m |= punct; 
    hi = do_scan_is(m,lo,hi); 
    if (m & space) hi = find(lo, hi, L'λ'); 
    return hi; 
} 

const char_type *do_scan_not 
    (mask m, const char_type* lo, const char_type* hi) const { 
    if (m & space) { 
    m |= punct; 
    while (* (lo = base::do_scan_not(m,lo,hi)) == L'λ' && lo != hi) 
    ++ lo; 
    return lo; 
    } 
    return base::do_scan_not(m,lo,hi); 
} 
}; 

(Przeproszenia do formatowania płaskimi podgląd przekształca zaczepy inaczej).

Kod jest o wiele mniej eleganckie. Lepiej wyrażam pogląd, że tylko interpunkcja jest dodatkowym odstępem, ale w oryginale byłoby to w porządku, gdybym miał classic_table.

Czy jest to prostszy sposób? Czy naprawdę potrzebuję wszystkich tych przeciążeń? (Testowanie pokazało, że jest tu obca, ale myślę szerzej.) Czy w pierwszej kolejności nadużywam aspektów? Czy powyższe jest poprawne? Czy lepszym stylem byłoby wprowadzanie mniejszej logiki?

+1

Pod względem przykładowego kodu, możesz chcieć spojrzeć na Schemat lub LISP, ponieważ oba te języki są oparte na rachunku lambda. Powinien istnieć jakiś kod LISP lub Scheme, w które możesz grać. –

+0

Wszystkie standardowe języki funkcyjne są ładne, ale miałem nadzieję, że znajdę kilka ciętych i wysuszonych źródeł w formie '(\ foo bar. Foo foo \ baz. ...' ad nauseam. Chciałbym grać z binarną lambda calculus (http://en.wikipedia.org/wiki/Binary_lambda_calculus, http://homepages.cwi.nl/~tromp/cl/cl.html) zamiast spędzać czas na pisaniu kodu ezoterycznego. Ale to pytanie dotyczy iostreams i rozbieżność elegancji pomiędzy szerokimi i wąskimi postaciami: – Potatoswatter

+0

Jesteś kimś, co nadużywasz faset do budowy tokenizującego parsera, w pełni oczekuję, że będzie on jednocześnie kruchy i powolny, to znowu jest najbardziej bezpośredni sposób na nauczenie się podsumowanie iostreams Dziękujemy za zgłoszenie – sehe

Odpowiedz

3

(To był rok bez odpowiedzi merytorycznej, a ja nauczyłem się wiele o iostreams w międzyczasie ...)

Zwyczaj aspekt istnieje wyłącznie służyć ciąg operator ekstrakcji in >> token. Ten operator jest zdefiniowany pod kątem use_facet< ctype<wchar_t> >(in.getloc()).is(ios::space, c) "dla następnego dostępnego znaku wejściowego c." (§21.3.7.9) ctype::is jest po prostu skrótem do ctype::do_is, więc wydaje się, że wystarczające jest do_is.

Mimo to najnowsze wersje biblioteki standardowej GCC implementują operator>> pod względem scan_is. Połów jest taki, że do_scan_is jest następnie implementowany jako seria wywołań do do_is, wirtualnej wysyłki i wszystkich. Plik nagłówka opisuje do_scan_is jako hak do optymalizacji użytkownika.

Wydaje się więc, że zasada "jak-coś" chroni implementację, która zapewnia tylko pierwsze nadpisanie.

Należy zauważyć, że drugie zastąpienie, które pobiera wartości maski, jest nieparzyste. Można go zaimplementować w kategoriach pierwszego, nieefektywnie budując maskę bit po bicie.W GCC jest zaimplementowany pod względem wywołań systemowych, nieefektywne budowanie maski bit po bicie z 15 połączeniami na znak. Wydaje się, że poświęca to zarówno wydajność, jak i zgodność. Na szczęście wygląda na to, że nikt go nie używa.


W każdym razie, to wszystko jest dobrze, ale po prostu pisząc tokenizera korzystając streambuf_iterator<wchar_t> jest łatwiej, o wiele bardziej rozciągliwe i upraszcza obsługę wyjątków.

+0

Hah, odpowiedziałeś na własne pytanie Nie zauważyłem, że był starszy. – sehe

2

IMHO kod, który wysłałeś, jest w porządku. Możesz zastosować niektóre metody używając innych, jeśli potrzebujesz prostszego kodu (może kosztem wydajności), ale sposób w jaki to zrobiłeś jest OK.

Ta różnica polega na tym, że ludzie nie chcą mieć kilku megabajtowych tabel w swoich programach UNICODE.