Jak wiarygodnie określić typ zmiennej zadeklarowanej przy użyciu zmiennej w czasie projektowania?

109

Pracuję nad narzędziem uzupełniania (Intellisense) dla języka C # w emacsie.

Chodzi o to, że jeśli użytkownik wpisze fragment, a następnie poprosi o uzupełnienie za pomocą określonej kombinacji naciśnięć klawiszy, narzędzie uzupełniania użyje odbicia .NET do określenia możliwych uzupełnień.

Aby to zrobić, trzeba znać rodzaj realizowanej rzeczy. Jeśli jest to ciąg znaków, istnieje znany zestaw możliwych metod i właściwości; jeśli jest to Int32, ma oddzielny zestaw i tak dalej.

Używając semantycznego pakietu leksera / parsera kodu dostępnego w emacsie, mogę zlokalizować deklaracje zmiennych i ich typy. Biorąc to pod uwagę, łatwo jest użyć odbicia, aby uzyskać metody i właściwości typu, a następnie przedstawić listę opcji użytkownikowi. (Ok, nie jest to całkiem proste do zrobienia w emacsie, ale używając możliwości uruchomienia procesu PowerShell wewnątrz emacsa , staje się znacznie łatwiejsze. Piszę niestandardowy zespół .NET, aby wykonać odbicie, załadować go do PowerShell, a następnie elisp uruchomić w nim emacs może wysyłać polecenia do programu PowerShell i odczytywać odpowiedzi przez comint. W rezultacie emacs może szybko uzyskać wyniki refleksji).

Problem pojawia się, gdy kod używa varw deklaracji rzeczy do wykonania. Oznacza to, że typ nie jest jawnie określony, a uzupełnianie nie będzie działać.

W jaki sposób mogę wiarygodnie określić używany typ, gdy zmienna jest zadeklarowana za pomocą varsłowa kluczowego? Żeby było jasne, nie muszę tego określać w czasie wykonywania. Chcę to określić w „czasie projektowania”.

Do tej pory mam te pomysły:

  1. kompiluj i wywołuj:
    • wyodrębnij deklarację, np. `var foo =" wartość ciągu ";`
    • konkatenuje instrukcję `foo.GetType ();`
    • dynamicznie skompiluj wynikowy C # fragment go do nowego zestawu
    • Załaduj zestaw do nowej domeny AppDomain, uruchom ramkę i pobierz zwracany typ.
    • rozładować i wyrzucić zespół

    Wiem, jak to wszystko zrobić. Ale brzmi to strasznie ciężko, dla każdego żądania uzupełnienia w edytorze.

    Przypuszczam, że nie potrzebuję za każdym razem nowej domeny AppDomain. Mógłbym ponownie użyć pojedynczej domeny AppDomain do wielu tymczasowych zestawów i zamortyzować koszty jego konfiguracji i zerwania w wielu żądaniach ukończenia. To bardziej modyfikacja podstawowej idei.

  2. skompiluj i sprawdź IL

    Po prostu skompiluj deklarację do modułu, a następnie sprawdź IL, aby określić rzeczywisty typ, który został wywnioskowany przez kompilator. Jak byłoby to możliwe? Czego użyłbym do zbadania IL?

Są jakieś lepsze pomysły? Komentarze? propozycje?


EDYCJA - myśląc o tym dalej, kompiluj i wywołuj jest nie do przyjęcia, ponieważ wywołanie może mieć skutki uboczne. Dlatego pierwszą opcję należy wykluczyć.

Myślę też, że nie mogę zakładać obecności .NET 4.0.


AKTUALIZACJA - Prawidłowa odpowiedź, niewymieniona powyżej, ale delikatnie wskazana przez Erica Lipperta, polega na wdrożeniu systemu wnioskowania o pełnej wierności. Jest to jedyny sposób na wiarygodne określenie typu zmiennej w czasie projektowania. Ale to też nie jest łatwe. Ponieważ nie mam złudzeń, że chcę spróbować zbudować coś takiego, wybrałem skrót do opcji 2 - wyodrębnij odpowiedni kod deklaracji i skompiluj go, a następnie sprawdź wynikowy IL.

To faktycznie działa w przypadku sporego podzbioru scenariuszy zakończenia.

Na przykład załóżmy, że w poniższych fragmentach kodu? to pozycja, na której użytkownik prosi o wypełnienie. To działa:

var x = "hello there"; 
x.?

Ukończenie uświadamia sobie, że x jest ciągiem i zapewnia odpowiednie opcje. Robi to, generując, a następnie kompilując następujący kod źródłowy:

namespace N1 {
  static class dmriiann5he { // randomly-generated class name
    static void M1 () {
       var x = "hello there"; 
    }
  }
}

... a następnie inspekcja IL z prostą refleksją.

Działa to również:

var x = new XmlDocument();
x.? 

Silnik dodaje odpowiednie klauzule using do wygenerowanego kodu źródłowego tak, aby kompilował się poprawnie, a następnie inspekcja IL jest taka sama.

To też działa:

var x = "hello"; 
var y = x.ToCharArray();    
var z = y.?

Oznacza to po prostu, że inspekcja IL musi znaleźć typ trzeciej zmiennej lokalnej zamiast pierwszej.

I to:

var foo = "Tra la la";
var fred = new System.Collections.Generic.List<String>
    {
        foo,
        foo.Length.ToString()
    };
var z = fred.Count;
var x = z.?

... czyli o jeden poziom głębiej niż w poprzednim przykładzie.

Ale to, co nie działa, to uzupełnianie dowolnej zmiennej lokalnej, której inicjalizacja zależy w dowolnym momencie od elementu członkowskiego instancji lub argumentu metody lokalnej. Lubić:

var foo = this.InstanceMethod();
foo.?

Ani składnia LINQ.

Muszę pomyśleć o tym, jak cenne są te rzeczy, zanim rozważę rozwiązanie ich za pomocą zdecydowanie „ograniczonego projektu” (grzeczne słowo oznaczające hack).

Podejściem do rozwiązania problemu z zależnościami od argumentów metod lub metod instancji byłoby zastąpienie w fragmencie kodu, który jest generowany, kompilowany, a następnie analizowany IL, odniesienia do tych rzeczy „syntetycznymi” lokalnymi zmiennymi tego samego typu.


Kolejna aktualizacja - teraz działa aktualizacja zmiennych zależnych od członków instancji.

To, co zrobiłem, to przesłuchanie typu (za pomocą semantycznej), a następnie wygenerowanie syntetycznych członków zastępczych dla wszystkich istniejących członków. Dla bufora C # takiego jak ten:

public class CsharpCompletion
{
    private static int PrivateStaticField1 = 17;

    string InstanceMethod1(int index)
    {
        ...lots of code here...
        return result;
    }

    public void Run(int count)
    {
        var foo = "this is a string";
        var fred = new System.Collections.Generic.List<String>
        {
            foo,
            foo.Length.ToString()
        };
        var z = fred.Count;
        var mmm = count + z + CsharpCompletion.PrivateStaticField1;
        var nnn = this.InstanceMethod1(mmm);
        var fff = nnn.?

        ...more code here...

... wygenerowany kod, który jest kompilowany, dzięki czemu mogę dowiedzieć się z wyjścia IL typu lokalnego var nnn, wygląda następująco:

namespace Nsbwhi0rdami {
  class CsharpCompletion {
    private static int PrivateStaticField1 = default(int);
    string InstanceMethod1(int index) { return default(string); }

    void M0zpstti30f4 (int count) {
       var foo = "this is a string";
       var fred = new System.Collections.Generic.List<String> { foo, foo.Length.ToString() };
       var z = fred.Count;
       var mmm = count + z + CsharpCompletion.PrivateStaticField1;
       var nnn = this.InstanceMethod1(mmm);
      }
  }
}

Wszystkie elementy członkowskie instancji i typu statycznego są dostępne w kodzie szkieletowym. Kompiluje się pomyślnie. W tym momencie określenie typu zmiennej lokalnej jest proste dzięki odbiciu.

Umożliwia to:

  • możliwość uruchomienia programu PowerShell w emacsie
  • kompilator C # jest naprawdę szybki. Na moim komputerze kompilacja zestawu w pamięci zajmuje około 0,5 sekundy. Niewystarczająco szybki do analizy między naciśnięciami klawiszy, ale wystarczająco szybki, aby obsługiwać generowanie list ukończenia na żądanie.

Nie zajrzałem jeszcze do LINQ.
Będzie to znacznie większy problem, ponieważ semantyczny lekser / parser, który emacs ma dla C #, nie „robi” LINQ.

Cheeso
źródło
4
Typ foo jest obliczany i wypełniany przez kompilator poprzez wnioskowanie o typie. Podejrzewam, że mechanizmy są zupełnie inne. Być może silnik wnioskowania o typie ma haczyk? Przynajmniej użyłbym „wnioskowania o typie” jako tagu.
George Mauer
3
Twoja technika tworzenia „fałszywego” modelu obiektowego, który ma wszystkie typy, ale żadna z semantyki rzeczywistych obiektów nie jest dobra. Tak kiedyś zrobiłem IntelliSense dla JScript w Visual InterDev; tworzymy „fałszywą” wersję modelu obiektowego IE, która ma wszystkie metody i typy, ale nie ma żadnych skutków ubocznych, a następnie uruchamiamy mały interpreter na przeanalizowanym kodzie w czasie kompilacji i sprawdzamy, jaki typ wróci.
Eric Lippert

Odpowiedzi:

202

Mogę ci opisać, jak robimy to efektywnie w "prawdziwym" C # IDE.

Pierwszą rzeczą, jaką robimy, jest uruchomienie przepustki, która analizuje tylko rzeczy „najwyższego poziomu” w kodzie źródłowym. Pomijamy wszystkie treści metody. To pozwala nam szybko zbudować bazę danych zawierającą informacje o przestrzeni nazw, typach i metodach (oraz konstruktorach itp.) W kodzie źródłowym programu. Analizowanie każdego wiersza kodu w każdej treści metody zajęłoby zbyt dużo czasu, jeśli próbujesz to zrobić między naciśnięciami klawiszy.

Kiedy IDE musi wypracować typ konkretnego wyrażenia w treści metody - powiedzmy, że wpisałeś „foo”. i musimy dowiedzieć się, kim są członkowie foo - robimy to samo; pomijamy tak dużo pracy, jak to tylko możliwe.

Zaczynamy od przebiegu, który analizuje tylko deklaracje zmiennych lokalnych w ramach tej metody. Kiedy uruchamiamy ten przebieg, wykonujemy mapowanie z pary „zakresu” i „nazwy” na „wyznacznik typu”. „Określacz typu” to obiekt, który reprezentuje pojęcie „Mogę określić typ tego lokalnego, jeśli zajdzie taka potrzeba”. Wypracowanie typu lokalnego może być kosztowne, więc w razie potrzeby chcemy odłożyć tę pracę.

Mamy teraz leniwie budowaną bazę danych, która może nam powiedzieć typ każdego lokalnego. A więc wracając do tego „foo”. - ustalamy, w której instrukcji znajduje się odpowiednie wyrażenie, a następnie uruchamiamy analizator semantyczny w odniesieniu do tej instrukcji. Na przykład załóżmy, że masz treść metody:

String x = "hello";
var y = x.ToCharArray();
var z = from foo in y where foo.

a teraz musimy ustalić, że foo jest typu char. Tworzymy bazę danych, która ma wszystkie metadane, metody rozszerzające, typy kodu źródłowego i tak dalej. Budujemy bazę danych zawierającą wyznaczniki typów dla x, y i z. Analizujemy stwierdzenie zawierające interesujące wyrażenie. Zaczynamy od przekształcenia go składniowo na

var z = y.Where(foo=>foo.

Aby obliczyć typ foo, musimy najpierw znać typ y. Więc w tym miejscu pytamy osobę określającą typ „jaki jest typ y”? Następnie uruchamia ewaluator wyrażeń, który analizuje x.ToCharArray () i pyta „jaki jest typ x”? Mamy wyznacznik typu dla tego, który mówi "Muszę wyszukać" Ciąg "w bieżącym kontekście". W bieżącym typie nie ma typu String, więc szukamy w przestrzeni nazw. Tam też go nie ma, więc przeglądamy dyrektywy using i odkrywamy, że istnieje „using System” i że System ma typ String. OK, więc to jest typ x.

Następnie wyszukujemy metadane System.String pod kątem typu ToCharArray i mówi się, że jest to System.Char []. Wspaniały. Mamy więc typ na y.

Teraz pytamy „czy System.Char [] ma metodę Where?” Nie. Więc przyjrzymy się dyrektywom using; już wstępnie obliczyliśmy bazę danych zawierającą wszystkie metadane dla metod rozszerzających, które mogłyby być użyte.

Teraz mówimy "OK, istnieje osiemnaście tuzinów metod rozszerzających o nazwie Gdzie w zakresie, czy którakolwiek z nich ma pierwszy parametr formalny, którego typ jest zgodny z System.Char []?" Rozpoczynamy więc rundę testów konwertowalności. Jednak metody rozszerzające Where są ogólne , co oznacza, że ​​musimy wykonać wnioskowanie o typie.

Napisałem specjalny silnik wnioskujący o typie, który radzi sobie z wyciąganiem niepełnych wniosków z pierwszego argumentu do metody rozszerzającej. Uruchamiamy funkcję wnioskującą o typie i odkrywamy, że istnieje metoda Where, która pobiera an IEnumerable<T>, i że możemy wywnioskować z System.Char [] to IEnumerable<System.Char>, więc T to System.Char.

Sygnatura tej metody to Where<T>(this IEnumerable<T> items, Func<T, bool> predicate)i wiemy, że T to System.Char. Wiemy również, że pierwszym argumentem wewnątrz nawiasów do metody rozszerzającej jest lambda. Więc zaczynamy wnioskowanie typu wyrażenia lambda, które mówi, że "zakłada się, że parametr formalny foo to System.Char", wykorzystaj ten fakt podczas analizy reszty lambda.

Mamy teraz wszystkie informacje potrzebne do przeanalizowania treści lambda, czyli „foo.”. Sprawdzamy typ foo, odkrywamy, że zgodnie z wiązaniem lambda jest to System.Char i gotowe; wyświetlamy informacje o typie dla System.Char.

Robimy wszystko z wyjątkiem analizy „najwyższego poziomu” pomiędzy naciśnięciami klawiszy . To jest naprawdę trudne. Właściwie napisanie całej analizy nie jest trudne; sprawia, że ​​jest na tyle szybki, że można to zrobić z prędkością pisania, co jest naprawdę trudne.

Powodzenia!

Eric Lippert
źródło
8
Eric, dzięki za pełną odpowiedź. Trochę mi otworzyłeś oczy. W przypadku emacsa nie dążyłem do stworzenia dynamicznego silnika między naciśnięciami klawiszy, który konkurowałby z Visual Studio pod względem jakości wrażeń użytkownika. Po pierwsze, ze względu na opóźnienie ~ 0,5 s nieodłącznie związane z moim projektem, narzędzie oparte na emacsie jest i pozostanie tylko na żądanie; brak sugestii do automatycznego uzupełniania. Po drugie - zaimplementuję podstawowe wsparcie dla var locals, ale z radością wykręcę się, gdy coś stanie się owłosione lub gdy wykres zależności przekroczy pewien limit. Nie wiem jeszcze, jaki jest ten limit. Dzięki jeszcze raz.
Cheeso
13
Szczerze zadziwia mnie, że wszystko to może działać tak szybko i niezawodnie, szczególnie w przypadku wyrażeń lambda i wnioskowania o typie ogólnym. Byłem właściwie dość zaskoczony, gdy pierwszy raz napisałem wyrażenie lambda, a Intellisense znał typ mojego parametru, gdy nacisnąłem., Mimo że instrukcja nie była jeszcze kompletna i nigdy nie określiłem jawnie ogólnych parametrów metod rozszerzających. Dzięki za to małe zajrzenie do magii.
Dan Bryant
21
@Dan: Widziałem (lub napisałem) kod źródłowy i zadziwia mnie, że on też działa. :-) Jest tam trochę włochatych rzeczy.
Eric Lippert
11
Faceci z Eclipse prawdopodobnie robią to lepiej, ponieważ są bardziej niesamowici niż kompilator C # i zespół IDE.
Eric Lippert
23
W ogóle nie pamiętam, żebym robił ten głupi komentarz. To nawet nie ma sensu. Musiałem być pijany. Przepraszam.
Tomas Andrle
15

Mogę z grubsza powiedzieć, jak Delphi IDE współpracuje z kompilatorem Delphi w celu wykonania funkcji Intellisense (Delphi nazywa to wglądem w kod). Nie jest w 100% odpowiedni do C #, ale jest to interesujące podejście, które zasługuje na rozważenie.

Większość analiz semantycznych w Delphi jest wykonywana w samym parserze. Wyrażenia są wpisywane podczas ich analizowania, z wyjątkiem sytuacji, w których nie jest to łatwe - w takim przypadku parsowanie antycypacyjne jest używane do ustalenia, co jest zamierzone, a następnie ta decyzja jest używana w analizie.

Analiza jest w dużej mierze rekursywną metodą LL (2), z wyjątkiem wyrażeń, które są analizowane przy użyciu pierwszeństwa operatorów. Jedną z wyróżniających cech Delphi jest to, że jest to język jednoprzebiegowy, więc konstrukcje muszą zostać zadeklarowane przed użyciem, więc nie jest potrzebne żadne przejście najwyższego poziomu, aby wydobyć te informacje.

Ta kombinacja funkcji oznacza, że ​​parser ma z grubsza wszystkie informacje potrzebne do wglądu w kod w dowolnym miejscu, w którym jest to potrzebne. Działa to następująco: IDE informuje leksera kompilatora o pozycji kursora (punkt, w którym wymagany jest wgląd w kod), a lekser zamienia to w specjalny token (nazywa się to tokenem Kibitza). Za każdym razem, gdy parser napotka ten token (który może znajdować się w dowolnym miejscu), wie, że jest to sygnał do odesłania wszystkich posiadanych informacji do redaktora. Robi to używając longjmp, ponieważ jest napisane w C; co robi, to powiadamia ostatecznego rozmówcę o rodzaju konstrukcji składniowej (tj. kontekście gramatycznym), w którym został znaleziony punkt Kibitza, jak również o wszystkich symbolicznych tabelach niezbędnych dla tego punktu. Na przykład jeśli kontekst jest w wyrażeniu, które jest argumentem metody, możemy sprawdzić przeciążenia metody, spojrzeć na typy argumentów i odfiltrować prawidłowe symbole tylko do tych, które mogą rozwiązać ten typ argumentu (to ogranicza wiele nieistotnych skorup w menu rozwijanym). Jeśli jest w kontekście zagnieżdżonego zakresu (np. Po „.”), Parser zwróci odniesienie do zakresu, a IDE może wyliczyć wszystkie symbole znalezione w tym zakresie.

Robione są także inne rzeczy; na przykład, treści metod są pomijane, jeśli token kibitza nie leży w ich zakresie - jest to robione optymistycznie i wycofywane, jeśli przeskakuje token. Odpowiedniki metod rozszerzających - pomocnicy klas w Delphi - mają rodzaj wersjonowanej pamięci podręcznej, więc ich wyszukiwanie jest dość szybkie. Jednak wnioskowanie o typie ogólnym w Delphi jest znacznie słabsze niż w języku C #.

A teraz do konkretnego pytania: wnioskowanie o typach zmiennych zadeklarowanych za pomocą varjest równoważne sposobowi, w jaki Pascal wnioskuje o typie stałych. Wynika z typu wyrażenia inicjalizacyjnego. Te typy są budowane od dołu do góry. Jeśli xjest typu Integeri yjest typu Double, to x + ybędzie typu Double, ponieważ takie są reguły języka; itd. Przestrzegasz tych zasad, dopóki nie masz typu pełnego wyrażenia po prawej stronie, a to jest typ, którego używasz dla symbolu po lewej stronie.

Barry Kelly
źródło
7

Jeśli nie chcesz pisać własnego parsera, aby zbudować abstrakcyjne drzewo składni, możesz przyjrzeć się użyciu parserów z SharpDevelop lub MonoDevelop , z których oba są open source.

Daniel grał
źródło
4

Systemy Intellisense zazwyczaj przedstawiają kod za pomocą abstrakcyjnego drzewa składni, które pozwala im na rozwiązanie typu zwracanego funkcji przypisanej do zmiennej „var” w mniej więcej taki sam sposób, jak zrobi to kompilator. Jeśli używasz VS Intellisense, możesz zauważyć, że nie poda on typu zmiennej, dopóki nie zakończysz wprowadzania prawidłowego (rozpoznawalnego) wyrażenia przypisania. Jeśli wyrażenie jest nadal niejednoznaczne (na przykład nie może w pełni wywnioskować ogólnych argumentów dla wyrażenia), typ var nie zostanie rozstrzygnięty. Może to być dość złożony proces, ponieważ może być konieczne wejście dość głęboko w drzewo, aby rozwiązać typ. Na przykład:

var items = myList.OfType<Foo>().Select(foo => foo.Bar);

Typ zwracany to IEnumerable<Bar>, ale rozwiązanie tego wymaga znajomości:

  1. myList jest typu, który implementuje IEnumerable.
  2. Istnieje metoda rozszerzenia OfType<T> która ma zastosowanie do IEnumerable.
  3. Wynikowa wartość to IEnumerable<Foo>i istnieje metoda rozszerzenia, Selectktóra ma do tego zastosowanie.
  4. Wyrażenie lambda foo => foo.Barma parametr foo typu Foo. Wynika to z użycia Select, który przyjmuje plikFunc<TIn,TOut> a ponieważ TIn jest znane (Foo), można wywnioskować typ foo.
  5. Typ Foo ma właściwość Bar, która jest typu Bar. Wiemy, że Select zwraca IEnumerable<TOut>i TOut można wywnioskować z wyniku wyrażenia lambda, więc wynikowy typ elementów musi być IEnumerable<Bar>.
Dan Bryant
źródło
Racja, może być dość głęboka. Nie przeszkadza mi rozwiązywanie wszystkich zależności. Myśląc o tym, pierwsza opcja, którą opisałem - kompiluj i wywołuj - jest absolutnie nie do przyjęcia, ponieważ wywołanie kodu może mieć skutki uboczne, takie jak aktualizacja bazy danych, a to nie jest coś, co redaktor powinien robić. Kompilacja jest w porządku, wywoływanie nie. Jeśli chodzi o budowanie AST, to chyba nie chcę tego robić. Naprawdę chcę odłożyć to zadanie do kompilatora, który już wie, jak to zrobić. Chcę móc poprosić kompilator, aby powiedział mi, co chcę wiedzieć. Chcę tylko prostej odpowiedzi.
Cheeso
Wyzwaniem związanym z inspekcją tego z kompilacji jest to, że zależności mogą być dowolnie głębokie, co oznacza, że ​​może być konieczne zbudowanie wszystkiego, aby kompilator mógł wygenerować kod. Jeśli to zrobisz, myślę, że możesz użyć symboli debugera z wygenerowanym IL i dopasować typ każdego lokalnego do tego symbolu.
Dan Bryant
1
@Cheeso: kompilator nie oferuje tego rodzaju analizy typu jako usługi. Mam nadzieję, że w przyszłości tak się stanie, ale żadnych obietnic.
Eric Lippert
tak, myślę, że to może być droga - rozwiąż wszystkie zależności, a następnie skompiluj i sprawdź IL. @Eric, dobrze wiedzieć. Na razie, jeśli nie mam aspiracji do wykonania pełnej analizy AST, więc muszę uciec się do brudnego hacka, aby stworzyć tę usługę przy użyciu istniejących narzędzi. Np. Skompiluj inteligentnie skonstruowany fragment kodu, a następnie użyj programu ILDASM (lub podobnego), aby uzyskać odpowiedź, której szukam.
Cheeso
4

Ponieważ celujesz w Emacsa, najlepiej będzie zacząć od pakietu CEDET. Wszystkie szczegóły, które Eric Lippert są już omówione w analizatorze kodu w narzędziu CEDET / Semantic dla C ++. Istnieje również parser C # (który prawdopodobnie wymaga trochę TLC), więc jedyne brakujące części dotyczą dostrajania niezbędnych części dla C #.

Podstawowe zachowania są zdefiniowane w podstawowych algorytmach, które zależą od funkcji podlegających przeciążeniu, które są zdefiniowane dla każdego języka. Sukces kompletnego silnika zależy od stopnia dostrojenia. Z C ++ jako przewodnikiem, uzyskanie wsparcia podobnego do C ++ nie powinno być takie złe.

Odpowiedź Daniela sugeruje użycie MonoDevelop do parsowania i analizy. Może to być alternatywny mechanizm zamiast istniejącego analizatora składni języka C # lub może zostać użyty do rozszerzenia istniejącego analizatora składni.

Eric
źródło
Dobrze, wiem o CEDET i używam obsługi języka C # w katalogu Contrib do semantycznej. Semantyczny dostarcza listę zmiennych lokalnych i ich typów. Silnik uzupełniania może skanować tę listę i oferować użytkownikowi właściwe wybory. Problem występuje, gdy zmienna jest var. Semantyczny poprawnie identyfikuje go jako zmienną, ale nie zapewnia wnioskowania o typie. Moje pytanie dotyczyło konkretnie tego, jak rozwiązać ten problem . Przyjrzałem się również podłączeniu do istniejącego zakończenia CEDET, ale nie mogłem dowiedzieć się, jak. Dokumentacja CEDET jest ... ach ... niekompletna.
Cheeso
Komentarz boczny - CEDET jest niezwykle ambitny, ale trudno mi go używać i rozszerzać. Obecnie parser traktuje „przestrzeń nazw” jako wskaźnik klasy w C #. Nie mogłem nawet wymyślić, jak dodać „przestrzeń nazw” jako odrębny element składniowy. To uniemożliwiło wykonanie wszystkich innych analiz składniowych i nie mogłem zrozumieć, dlaczego. Wcześniej wyjaśniłem trudności, jakie miałem z ramami ukończenia. Poza tymi problemami istnieją szwy i zachodzenie na siebie między kawałkami. Na przykład nawigacja jest częścią zarówno semantyczną, jak i senatora. CEDET wydaje się kuszący, ale ostatecznie ... jest zbyt nieporęczny, aby się do niego zobowiązać.
Cheeso
Serio, jeśli chcesz jak najlepiej wykorzystać mniej udokumentowane części CEDET-u, najlepiej jest wypróbować listę mailingową. Pytania łatwo zagłębić się w obszary, które nie zostały jeszcze dobrze opracowane, dlatego potrzeba kilku iteracji, aby wypracować dobre rozwiązania lub wyjaśnić już istniejące. W szczególności dla C #, ponieważ nic o tym nie wiem, nie będzie prostych, jednorazowych odpowiedzi.
Eric
2

Trudno jest zrobić dobrze. Zasadniczo musisz modelować specyfikację języka / kompilator przez większość leksykowania / analizowania / sprawdzania typów i zbudować wewnętrzny model kodu źródłowego, który możesz następnie zapytać. Eric opisuje to szczegółowo dla C #. Zawsze możesz pobrać kod źródłowy kompilatora F # (część F # CTP) i przyjrzeć sięservice.fsi się interfejsowi ujawnionemu z kompilatora F #, który jest używany przez usługę języka F # do zapewniania inteligencji, podpowiedzi dla typów wywnioskowanych itp. poczucie możliwego „interfejsu”, jeśli kompilator był już dostępny jako API do wywołania.

Inną drogą jest ponowne użycie kompilatorów w takiej postaci, w jakiej je opisujesz, a następnie użycie odbicia lub przyjrzenie się wygenerowanemu kodowi. Jest to problematyczne z punktu widzenia, że ​​potrzebujesz „pełnych programów”, aby uzyskać dane wyjściowe kompilacji z kompilatora, podczas gdy podczas edytowania kodu źródłowego w edytorze często masz tylko „programy częściowe”, które jeszcze się nie analizują, nie mają wdrożone wszystkie metody itp.

Krótko mówiąc, myślę, że wersja „niskobudżetowa” jest bardzo trudna do wykonania, a wersja „prawdziwa” jest bardzo, bardzo trudna do wykonania. (Gdzie „trudne” oznacza tutaj zarówno „wysiłek”, jak i „trudność techniczną”).

Brian
źródło
Tak, wersja „niskobudżetowa” ma pewne wyraźne ograniczenia. Próbuję zdecydować, co jest „wystarczająco dobre” i czy mogę spotkać ten bar. Z własnego doświadczenia wynika, że ​​testowanie tego, co mam do tej pory, sprawia, że ​​pisanie C # w emacsie jest o wiele przyjemniejsze.
Cheeso
0

W przypadku rozwiązania „1” masz nową funkcję w .NET 4, która umożliwia szybkie i łatwe wykonanie tego. Więc jeśli możesz przekonwertować swój program na .NET 4, byłby to najlepszy wybór.

Softlion
źródło