Powrót do Delphi na przykładzie ulepszania mojego programu

Powrót do Delphi na przykładzie ulepszania mojego programu
Artur Protasewicz
Artur Protasewicz
  • Rejestracja:ponad 15 lat
  • Ostatnio:ponad 5 lat
  • Postów:233
0

Mój cel:

Wychodząc od zbiorów znaków, chcę dojść do składowych języka programowania np. identyfikatorów zmiennych i typów, dalej składni języka itd., w końcu interpretera. Ale tu na forum chodzi mi tylko o napisanie maksymalnie poprawnie programu, który widać, żeby mieć go na wzór. Potem wątek zakończę. Mam zamiar w Delphi napisać interpreter starego języka programowania na Atari, z czasów komputerów 8-bitowych, języka o nazwie Action! Ale przede wszyskim poćwiczyć Delphi i temu to głównie służy.

ACTION! Reference Manual

Moje doświadczenia w C#

Przykład prostego interpretera własnego języka – podstawy

Jak to się zaczęło:

Wątek został rozpoczęty na moim Mikroblogu. To dwa posty oznaczone hashtagiem #universum Wątek się rozrósł i dlatego przenoszę go na forum. Jak dotąd był to dialog z @furious programming za co jestem mu wdzięczny, za jego zaangażowanie w, że tak powiem "odnowę" mojej osoby, jako programisty. Na chwilę obecną jest to poniższy program, gdzie doszedłem do słówka:

inherited

i nie bardzo wiem, gdzie je tu "przypiąć", co nie znaczy, że nie wiem do czego służy np. do wywołania metody odziedziczonej.

Kopiuj
{
Zmiany:
Zakresy cyfr i liter ustalane przy pomocy zbiorów
Aplikacja konsolowa
Log zamiast ShowMessage
Teksty jako stałe np. 'cyfra' jako SDigit
Parametry String jako const
_className zamienione na FClassName (pole prywatne TAlphanum)
Nazwy typów z prefiksem T oraz dalej duża litera, pozostałe małe
Nazwy typów String, Char, Integer z dużej litery
Wprowadzone $REGION - y
Parametry z prefiksem A podobnie do nazw typów
nazwy zmiennych, wprawdzie z małej litery, ale pełnym wyrazem
zwalnianie zmiennych przez Free
Blok chroniony try..except dla zminnej niezainicjowanej 'alien'
}
program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Classes;

const
  SUnknown  = '<Alien>';
  SDigit    = '<Digit>';
  SAlpha    = '<Alpha>';
  SAlphanum = '<Alphanum>';

type
  TDigit = class;
  TAlpha = class;

  TAlphanum = class abstract
  private
    FClassName: String;
  public
    function WhatIs(AChar: Char; ALog: TStrings): String; overload; virtual;
    function WhatIs(ATDigit: TDigit; ALog: TStrings): String; overload; virtual;
    function WhatIs(ATAlpha: TAlpha; ALog: TStrings): String; overload; virtual;
    constructor Create(const AClassName: String);
  end;

  TDigit = class(TAlphanum)
  public
    function WhatIs(AChar: Char; ALog: TStrings): String; override;
    function WhatIs(ATDigit: TDigit; ALog: TStrings): String; override;
  end;

  TAlpha = class(TAlphanum)
  public
    function WhatIs(AChar: Char; ALog: TStrings): String; override;
    function WhatIs(ATAlpha: TAlpha; ALog: TStrings): String; override;
  end;

{$REGION 'TAlphanum'}

constructor TAlphanum.Create(const AClassName: String);
begin
  FClassName := AClassName
end;

function TAlphanum.WhatIs(AChar: Char; ALog: TStrings): String;
var
  digit: TDigit;
  alpha: TAlpha;
begin
  ALog.Add('Have been to #1');
  digit := TDigit.Create(SDigit);
  alpha := TAlpha.Create(SAlpha);
  if digit.WhatIs(AChar, ALog) <> SUnknown then
    Result := digit.WhatIs(AChar, ALog)
  else
  if alpha.WhatIs(AChar, ALog) <> SUnknown then
    Result := alpha.WhatIs(AChar, ALog)
  else
    Result := SUnknown;
  digit.Free;
  alpha.Free;
end;

function TAlphanum.WhatIs(ATDigit: TDigit; ALog: TStrings): String;
var
  digit: TDigit;
begin
  digit := TDigit.Create(SDigit);
  Result := digit.WhatIs(ATDigit, ALog);
  digit.Free;
end;

function TAlphanum.WhatIs(ATAlpha: TAlpha; ALog: TStrings): String;
var
  alpha: TAlpha;
begin
  alpha := TAlpha.Create(SAlpha);
  Result := alpha.WhatIs(ATAlpha, ALog);
  alpha.Free;
end;

{$ENDREGION}

{$REGION 'TDigit'}

function TDigit.WhatIs(AChar: Char; ALog: TStrings): String;
begin
  ALog.Add('Have been to #2');
  if AChar in ['0'..'9'] then
    Result := FClassName
  else
    Result := SUnknown;
end;

function TDigit.WhatIs(ATDigit: TDigit; ALog: TStrings): String;
begin
  ALog.Add('Have been to #3');
  Result := ATDigit.FClassName;
end;

{$ENDREGION}

{$REGION 'TAlpha'}

function TAlpha.WhatIs(AChar: Char; ALog: TStrings): String;
begin
  ALog.Add('Have been to #4');
  if AChar in ['A'..'Z'] then
    Result := FClassName
  else
    Result := SUnknown;
end;

function TAlpha.WhatIs(ATAlpha: TAlpha; ALog: TStrings): String;
begin
  ALog.Add('Have been to #5');
  Result := ATAlpha.FClassName;
end;

{$ENDREGION}

var
  log: TStringList;
  alphanum: TAlphanum;
  digit: TDigit;
  alpha: TAlpha;
  alien: Char;
  i: Integer;
begin
  log := TStringList.Create;
  alphanum := TAlphanum .Create(SAlphanum);
  digit := TDigit.Create(SDigit);
  alpha := TAlpha.Create(SAlpha);
  log.Add('Character ''7'' is of type ' + alphanum.WhatIs('7', log));
  log.Add('Character ''A'' is of type ' + alphanum.WhatIs('A', log));
  log.Add('Character ''$'' is of type ' + alphanum.WhatIs('$', log));
  log.Add('Variable ''digit'' is of type ' + alphanum.WhatIs(digit, log));
  log.Add('Variable ''alpha'' is of type ' + alphanum.WhatIs(alpha, log));
  try
    {[Pascal Warning] W1036 Variable 'alien' might not have been initialized}
    log.Add('Variable ''alien'' is of type ' + alphanum.WhatIs(alien, log));
  except
    log.Add('Error: Variable ''alien'' not initialized');
  end;
  for i := 0 to log.Count - 1 do
    WriteLn(log[i]);
  WriteLn;
  Write('Press key Enter');
  ReadLn;
  log.Free;
  alphanum.Free;
  digit.Free;
  alpha.Free;
end.

Co widać na konsoli:

Kopiuj
{
Have been to #1
Have been to #2
Have been to #2
Character '7' is of type <Digit>
Have been to #1
Have been to #2
Have been to #4
Have been to #4
Character 'A' is of type <Alpha>
Have been to #1
Have been to #2
Have been to #4
Character '$' is of type <Alien>
Have been to #3
Variable 'digit' is of type <Digit>
Have been to #5
Variable 'alpha' is of type <Alpha>
Have been to #1
Have been to #2
Have been to #4
Variable 'alien' is of type <Alien>

Press key Enter
}
edytowany 4x, ostatnio: Artur Protasewicz
flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:około godziny
  • Lokalizacja:Tuchów
  • Postów:12166
0

Dobrze by było, abyś najpierw napisał o tym co chcesz osiągnąć. Póki co wiadomo tyle, że masz kilka klas opisujących zbiory jakichś tokenów, ale nie wiadomo do czego one służą.

Nie wiem też dlaczego TAlfanum jest klasą bazową dla klas cyfr (TDigit) i liter (TAlpha). Powinno być na odwrót – na zbiór znaków alfanumerycznych składają się litery i cyfry. Tyle że Delphi nie wspiera wielodziedziczenia klas, wiec trzeba do tematu podejść w podobny sposób do bieżącego, tyle że lepiej ten kod zaplanować.


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
edytowany 2x, ostatnio: flowCRANE
Artur Protasewicz
Artur Protasewicz
  • Rejestracja:ponad 15 lat
  • Ostatnio:ponad 5 lat
  • Postów:233
0

Stosowane rozwiązanie to użycie dwóch pól ze wskazaniami:

Kopiuj
FAlpha: TAlpha^; 
FDigit: TDigit^;

Mi się to nie podoba, ale chyba tak zrobię.

flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:około godziny
  • Lokalizacja:Tuchów
  • Postów:12166
0

A koniecznie potrzebujesz do tego celu klas i obiektów? Jeśli nie, to spokojnie możesz sobie takie zestawy znaków trzymać w zbiorach – te można dowolnie wypełniać, przeglądać (w tym iterować po nich), a także wykonywać na nich operacje arytmetyczne (np. w celu ich łączenia czy pozyskiwania części wspólnych).

Przykład:

Kopiuj
const
  CHARSET_LETTERS = ['A' .. 'Z', 'a' .. 'z'];
  CHARSET_DIGITS  = ['0' .. '9'];
  CHARSET_SYMBOLS = [';', ':', '.', '(', ')' {..}];

const
  CHARSET_ALPHANUMERIC = CHARSET_LETTERS + CHARSET_NUMBERS;

Jeśli koniecznie chcesz mieć do tego klasy i skorzystać z dziedziczenia i z polimorfizmu, to najpierw określ jakie informacje powinna posiadać klasa bazowa, jakie składowe powinny być publiczne, a jakie chronone i ew. wirtualne. Bez tego trudno będzie stworzyć dobre, uniwersalne klasy.

Artur Protasewicz napisał(a):
Kopiuj
FAlpha: TAlpha^; 
FDigit: TDigit^;

Takich rzeczy nie rób, bo się zaplączesz. Referencja sama w sobie jest wskaźnikiem, więc tworzysz w ten sposób wskaźnik na wskaźnik, co potem może być trudne do rozszyfrowania (programiści C znają ten ból).

Co prawda w bibliotece standardowej (przynajmniej dla FPC) istnieją takie typy danych jak PPPointer (wskaźnik na wskaźnik na wskaźnik na dane), ale nie wiem czy ktoś tego używa, czy się po prostu jąkał przy pisaniu kodu.


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
edytowany 10x, ostatnio: flowCRANE
Artur Protasewicz
Artur Protasewicz
  • Rejestracja:ponad 15 lat
  • Ostatnio:ponad 5 lat
  • Postów:233
0
furious programming napisał(a):

Jeśli koniecznie chcesz mieć do tego klasy i skorzystać z dziedziczenia i z polimorfizmu, to najpierw określ jakie informacje powinna posiadać klasa bazowa, jakie składowe powinny być publiczne, a jakie chronone i ew. wirtualne. Bez tego trudno będzie stworzyć dobre, uniwersalne klasy.

Jednak obiektowo bym chciał i muszę rzeczywiście to przemyśleć. Może coś mi da to, co teraz jest wpisywane do loga (zmodyfikowałem trochę wpisy). Może warto się przyjrzeć duplikatom w poniższym logu:

Kopiuj
{
Have been to #1 [TAlphanum.WhatIs(Char)]
Have been to #2 [TDigit.WhatIs(Char)]
Have been to #2 [TDigit.WhatIs(Char)]
Character '7' is of type <Digit>
Have been to #1 [TAlphanum.WhatIs(Char)]
Have been to #2 [TDigit.WhatIs(Char)]
Have been to #4 [TAlpha.WhatIs(Char)]
Have been to #4 [TAlpha.WhatIs(Char)]
Character 'A' is of type <Alpha>
Have been to #1 [TAlphanum.WhatIs(Char)]
Have been to #2 [TDigit.WhatIs(Char)]
Have been to #4 [TAlpha.WhatIs(Char)]
Character '$' is of type <Alien>
Have been to #3 [TDigit.WhatIs(TDigit)]
Variable 'digit' is of type <Digit>
Have been to #5 [TAlpha.WhatIs(TAlpha)]
Variable 'alpha' is of type <Alpha>
Have been to #1 [TAlphanum.WhatIs(Char)]
Have been to #2 [TDigit.WhatIs(Char)]
Have been to #4 [TAlpha.WhatIs(Char)]
Variable 'alien' is of type <Alien>

Press key Enter
}
flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:około godziny
  • Lokalizacja:Tuchów
  • Postów:12166
0

W takim razie bottom-up – zacznij od określenia wymagań klasy bazowej, a następnie tych dziedziczących. Przy czym nie deklaruj własnego pola z nazwą klasy, bo takie już istnieje w klasie TObject – spróbuj z niego skorzystać.

I koniecznie wyrzuć istniejący kod, bo jest niezgodny nie tyle z SRP, co z całym SOLID.


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
edytowany 5x, ostatnio: flowCRANE
Artur Protasewicz
Artur Protasewicz
  • Rejestracja:ponad 15 lat
  • Ostatnio:ponad 5 lat
  • Postów:233
0

Sporo pozmieniałem. Przyjąłem założenie, że skoro TAlphanum pyta WhatIs to TAlphanum musi znać odpowiedź.
Ciekawy jest wydruk loga poniżej programu.

Kopiuj
program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Classes;

const
  SUnknown  = '<Alien>';
  SDigit    = '<Digit>';
  SAlpha    = '<Alpha>';
  SAlphanum = '<Alphanum>';

type
  TDigit = class;
  TAlpha = class;

  TAlphanum = class
  private
    FClassName: String;
  public
    function WhatIs(AChar: Char; ALog: TStrings): String; overload;
    function WhatIs(ATDigit: TDigit; ALog: TStrings): String; overload;
    function WhatIs(ATAlpha: TAlpha; ALog: TStrings): String; overload;
    constructor Create(const AClassName: String);
  end;

  TDigit = class(TAlphanum)
  public
    constructor Create(const AClassName: String);
  end;

  TAlpha = class(TAlphanum)
  public
    constructor Create(const AClassName: String);
  end;

{$REGION 'WhatIs'}

function TAlphanum.WhatIs(AChar: Char; ALog: TStrings): String;
begin
  ALog.Add('[TAlphanum.WhatIs(Char)]');
  if AChar in ['0'..'9'] then
    Result := SDigit
  else
  if AChar in ['A'..'Z'] then
    Result := SAlpha
  else
    Result := SUnknown;
end;

function TAlphanum.WhatIs(ATDigit: TDigit; ALog: TStrings): String;
begin
  ALog.Add('TAlphanum.WhatIs(TDigit)');
  Result := FClassName;
end;

function TAlphanum.WhatIs(ATAlpha: TAlpha; ALog: TStrings): String;
begin
  ALog.Add('[TAlphanum.WhatIs(TAlpha)]');
  Result := FClassName;
end;

{$ENDREGION}

{$REGION 'Create'}

constructor TAlphanum.Create(const AClassName: String);
begin
  FClassName := AClassName
end;

constructor TDigit.Create(const AClassName: string);
begin
  inherited Create(AClassName);
end;

constructor TAlpha.Create(const AClassName: string);
begin
  inherited Create(AClassName);
end;

{$ENDREGION}

var
  log: TStringList;
  alphanum: TAlphanum;
  digit: TDigit;
  alpha: TAlpha;
  alien: Char;
  i: Integer;
begin
  log := TStringList.Create;
  alphanum := TAlphanum .Create(SAlphanum);
  digit := TDigit.Create(SDigit);
  alpha := TAlpha.Create(SAlpha);
  log.Add('Character ''7'' is of type ' + alphanum.WhatIs('7', log));
  log.Add('Character ''A'' is of type ' + alphanum.WhatIs('A', log));
  log.Add('Character ''$'' is of type ' + alphanum.WhatIs('$', log));
  log.Add('Variable ''digit'' is of type ' + alphanum.WhatIs(digit, log));
  log.Add('Variable ''alpha'' is of type ' + alphanum.WhatIs(alpha, log));
  try
    {[Pascal Warning] W1036 Variable 'alien' might not have been initialized}
    log.Add('Variable ''alien'' is of type ' + alphanum.WhatIs(alien, log));
  except
    log.Add('Error: Variable ''alien'' not initialized');
  end;
  for i := 0 to log.Count - 1 do
    WriteLn(log[i]);
  WriteLn;
  Write('Press key Enter');
  ReadLn;
  log.Free;
  alphanum.Free;
  digit.Free;
  alpha.Free;
end.

Log:

Kopiuj
{
[TAlphanum.WhatIs(Char)]
Character '7' is of type <Digit>
[TAlphanum.WhatIs(Char)]
Character 'A' is of type <Alpha>
[TAlphanum.WhatIs(Char)]
Character '$' is of type <Alien>
TAlphanum.WhatIs(TDigit)
Variable 'digit' is of type <Alphanum>
[TAlphanum.WhatIs(TAlpha)]
Variable 'alpha' is of type <Alphanum>
[TAlphanum.WhatIs(Char)]
Variable 'alien' is of type <Alien>

Press key Enter
}
edytowany 1x, ostatnio: Artur Protasewicz
Artur Protasewicz
Artur Protasewicz
  • Rejestracja:ponad 15 lat
  • Ostatnio:ponad 5 lat
  • Postów:233
0
furious programming napisał(a):

W takim razie bottom-up – zacznij od określenia wymagań klasy bazowej, a następnie tych dziedziczących. Przy czym nie deklaruj własnego pola z nazwą klasy, bo takie już istnieje w klasie TObject – spróbuj z niego skorzystać.

I koniecznie wyrzuć istniejący kod, bo jest niezgodny nie tyle z SRP, co z całym SOLID.

Wiem że jest pole ClassName, ale chcę mieć możliwość nadawania nazw z ogonkami w języku polskim, choć odłożyłem to na dalszy plan.

Prawdę mówiąc mam w głębokim poważaniu te wszyskie angielskie skróty i metodologie, przy całym szacunku do ciebie.
Istnieje tylko top-down i bottom-up i akurat top-down uczyłem się ucząc się pascala.

edytowany 2x, ostatnio: Artur Protasewicz
flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:około godziny
  • Lokalizacja:Tuchów
  • Postów:12166
0

To że nie chcesz pisać kodu zgodnego z SOLID, KISS czy DRY również mam w poważaniu, jednak nie stosując się do tych zasad, automatycznie komplikujesz kod, przez co trudniej go zrozumieć (również mnie), a tym bardziej rozwijać. Dlatego sugeruję pisanie kodu w sposób powszechnie uznawany za poprawny.

Artur Protasewicz napisał(a):

Wiem że jest pole ClassName, ale chcę mieć możliwość nadawania nazw z ogonkami w języku polskim […]

Stanowczo odradzam. Mimo wszystko Delphi pozwala na używanie znaków unikodowych w identyfikatorach:

Fundamental Syntactic Elements: Identifiers

Identifiers denote constants, variables, fields, types, properties, procedures, functions, programs, units, libraries, and packages. An identifier can be of any length, but only the first 255 characters are significant. An identifier must begin with an alphabetic character, a Unicode character, or an underscore (_) and cannot contain spaces. Alphanumeric characters, Unicode characters, digits, and underscores are allowed after the first character.

Natomiast jeśli chodzi o klasę TAlphanum, to w ogóle nie powinna ona posiadać metod WhatIs, bo jest to klasa abstrakcyjna i nie powinna operować na instancjach klas z niej dziedziczących. Chyba że parametr będzie tego samego typu, czyli TAlphanum.


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
edytowany 4x, ostatnio: flowCRANE
abrakadaber
abrakadaber
  • Rejestracja:ponad 12 lat
  • Ostatnio:7 miesięcy
  • Postów:6610
0

może się podłączę :P

najpierw ponarzekam

Kopiuj
    function WhatIs(AChar: Char; ALog: TStrings): String; overload;
    function WhatIs(ATDigit: TDigit; ALog: TStrings): String; overload;
    function WhatIs(ATAlpha: TAlpha; ALog: TStrings): String; overload;

Nie podobają mi się nazwy pierwszych parametrów. Po pierwsze to T - T przyjęło się jako oznaczenie typu. Po drugie wg mnie dużo lepiej sprawdziła by się jedna (taka sama) nazwa w każdej metodzie.

następnie

Kopiuj
  digit := TDigit.Create(SDigit);
  alpha := TAlpha.Create(SAlpha);

to jest też brzydkie, wręcz bym powiedział, że paskudne :p. Po co przekazywać tam te stałe? A jak zrobię TDigit.Create(SAlpha) albo TAlpha.Create(SDigit) to co wtedy? Ba mogę zrobić nawet tak TAlpha.Create('DUPA) i zadziała. Bo kod się nie wykrzaczy ale wyniki będą cokolwiek dziwne a wymuszając takie tworzenie obiektów wystawiasz na zewnątrz "zwracaną wartość", która jednak powinna być dla zewnętrznych klas niedostępna do modyfikacji.

Generalnie zgadzam się z @furious programming, że te klasy są zbędne. Wg mnie zabierasz się trochę od tyłka strony. Rozumiem, że występują tam jakieś tokeny, zmienne, operatory itd. i to takie klasy powinieneś mieć. Chociaż nie do końca jestem przekonany czy to muszą być klasy czy nie wystarczyło by aby były to rekordy (w tym konkretnym przypadku reprezentowania elementu języka)


Chcesz pomocy - pokaż kod - abrakadabra źle działa z techniką.
flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:około godziny
  • Lokalizacja:Tuchów
  • Postów:12166
0

Klasy nawet mogłyby od biedy pozostać, ale na pewno nie w takiej formie. Metody WhatIs są zbędne – powinno się w konstruktorze klas dziedziczących inicjalizować wartość pola z nazwą klasy (to własne pole) i ewentualnie wystawić je publicznie jako właściwość tylko do odczytu. O ile w ogóle ma ona jakikolwiek sens, w co wątpię.

Problem w tym zalążku kodu polega na tym, że każdy znak jest obiektem, a to spora nadmiarowość. Co za tym idzie, każdy token analizowanego kodu źródłowego będzie musiał być listą obiektów, co znacznie skomplikuje implementację oraz rozciągnie w czasie cały proces parsowania/kompilacji kodu źródłowego.


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
edytowany 5x, ostatnio: flowCRANE
Artur Protasewicz
Artur Protasewicz
  • Rejestracja:ponad 15 lat
  • Ostatnio:ponad 5 lat
  • Postów:233
0

@furious programming: Przepraszam za wczoraj, odnośnie linku poniżej, który dziś znalazłem:

SRP – Single Responsibility Principle w praktyce

Na to wygląda, że cały program jest do wyrzucenia, ale to tak po pierwszym przeczytaniu.
Niemniej on działa i jest szybszy od wersji pierwotnej.
Jeszcze dochodzi korzystanie z TObject.ClassName. To wymaga pozmieniania konstruktorów na bezparametrowe.

Uwzględnię jeszcze to, co pisze @abrakadaber i ujednolicę nazwę zmiennej-parametru metod WhatIs.

Artur Protasewicz
Artur Protasewicz
  • Rejestracja:ponad 15 lat
  • Ostatnio:ponad 5 lat
  • Postów:233
0

Teraz tak wygląda program.
Wykorzystałem pole TObject.ClassName.
Nadałem polu FClassName typ ShortString, bo TObject.ClassName jest tego typu
i to wynika z fragmentu o nazwach klas po angielsku.
Użyłem jednakowej nazwy parametru funkcji WhatIs tj. AToken
chociaż nie kojarzę tej nazwy z obiektami Delphi a z obiektami w rozumieniu
gramatyki języka np. polskiego, języka np. programu w Action! etc.
Utworzyłem klasę TAlien, aby w ogóle wyeliminować nazewnictwo
własne w stringach.
Wydruk z konsoli jest na końcu.

Kopiuj
program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Classes;
{
const
  SUnknown  = '<Alien>';
}
type
  TAlien = class; // odpowiednik stałej Unknown
  TDigit = class;
  TAlpha = class;

  TAlphanum = class
  private
    FClassName: ShortString;
  public
    function WhatIs(AToken: Char; ALog: TStrings): ShortString; overload;
    function WhatIs(AToken: TDigit; ALog: TStrings): ShortString; overload;
    function WhatIs(AToken: TAlpha; ALog: TStrings): ShortString; overload;
    constructor Create;
  end;

  TDigit = class(TAlphanum)
  public
    constructor Create;
  end;

  TAlpha = class(TAlphanum)
  public
    constructor Create;
  end;

  TAlien = class
  private
    FClassName: ShortString;
  public
    constructor Create;
  end;

{$REGION 'Create'}

constructor TAlphanum.Create;
begin
  FClassName := TAlphanum.ClassName;
end;

constructor TDigit.Create;
begin
  FClassName := TDigit.ClassName;
end;

constructor TAlpha.Create;
begin
  FClassName := TAlpha.ClassName;
end;

constructor TAlien.Create;
begin
  FClassName := TAlien.ClassName;
end;

{$ENDREGION}

{$REGION 'WhatIs'}

function TAlphanum.WhatIs(AToken: Char; ALog: TStrings): ShortString;
begin
  ALog.Add('[TAlphanum.WhatIs(Char)]');
  if AToken in ['0'..'9'] then
    Result := TDigit.ClassName
  else
  if AToken in ['A'..'Z'] then
    Result := TAlpha.ClassName
  else
    Result := TAlien.ClassName;
end;

function TAlphanum.WhatIs(AToken: TDigit; ALog: TStrings): ShortString;
begin
  ALog.Add('TAlphanum.WhatIs(TDigit)');
  Result := AToken.ClassName;
end;

function TAlphanum.WhatIs(AToken: TAlpha; ALog: TStrings): ShortString;
begin
  ALog.Add('[TAlphanum.WhatIs(TAlpha)]');
  Result := AToken.ClassName;
end;

{$ENDREGION}

var
  log: TStringList;
  alphanum: TAlphanum;
  digit: TDigit;
  alpha: TAlpha;
  alien: Char;
  i: Integer;
begin
  log := TStringList.Create;
  alphanum := TAlphanum .Create;
  digit := TDigit.Create;
  alpha := TAlpha.Create;
  log.Add('Character ''7'' is of type ' + alphanum.WhatIs('7', log));
  log.Add('Character ''A'' is of type ' + alphanum.WhatIs('A', log));
  log.Add('Character ''$'' is of type ' + alphanum.WhatIs('$', log));
  log.Add('Variable ''digit'' is of type ' + alphanum.WhatIs(digit, log));
  log.Add('Variable ''alpha'' is of type ' + alphanum.WhatIs(alpha, log));
  try
    {[Pascal Warning] W1036 Variable 'alien' might not have been initialized}
    log.Add('Variable ''alien'' is of type ' + alphanum.WhatIs(alien, log));
  except
    log.Add('Error: Variable ''alien'' not initialized');
  end;
  for i := 0 to log.Count - 1 do
    WriteLn(log[i]);
  WriteLn;
  Write('Press key Enter');
  ReadLn;
  log.Free;
  alphanum.Free;
  digit.Free;
  alpha.Free;
end.

{
[TAlphanum.WhatIs(Char)]
Character '7' is of type TDigit
[TAlphanum.WhatIs(Char)]
Character 'A' is of type TAlpha
[TAlphanum.WhatIs(Char)]
Character '$' is of type TAlien
TAlphanum.WhatIs(TDigit)
Variable 'digit' is of type TDigit
[TAlphanum.WhatIs(TAlpha)]
Variable 'alpha' is of type TAlpha
[TAlphanum.WhatIs(Char)]
Variable 'alien' is of type TAlien

Press key Enter
}
edytowany 1x, ostatnio: Artur Protasewicz
Artur Protasewicz
Artur Protasewicz
  • Rejestracja:ponad 15 lat
  • Ostatnio:ponad 5 lat
  • Postów:233
0

1. Single Responsibility Principle

Pierwszy rzut oka na wszystkie klasy w przypadku pytania (metody) WhatIs sugeruje, że cała odpowiedzialność spoczywa na klasie TAlphanum,

[ 1 ]
niemniej jeśli się wejrzy w spopsób udzielania odpowiedzi to mamy w ciele WhatIs odpowiedzi przez np. TDigit.ClassName, więc odpowiedzialność ostatecznie realizuje tu: TDigit, co wydaje się zgodne z SRP.

Być może źle to rozumiemiem

[ 2 ]
i chodzi o to, żeby metody np. WhatIs(AObject: TDigit) były w klasie TDigit już na pierwszy rzut oka,

ale to tylko komplikuje program, a efekt będzie ten sam,
więc jestem w kropce.

edytowany 4x, ostatnio: Artur Protasewicz
Artur Protasewicz
Artur Protasewicz
  • Rejestracja:ponad 15 lat
  • Ostatnio:ponad 5 lat
  • Postów:233
0

#project-yet-completed @furious programming To jest finalna, jak dotąd wersja. Jeśli będziesz miał chwilę i akceptujesz, przy czym ja biorę sobie do serca to co w tym wątku napisałeś i @abrakadaber , no więc jeśli jesteś skłonny to zaakceptować, to postaw haczyk albo punkt przy tym konkretnym poście.

Właściwie to już chyba wszystko, co tu trzeba zrobić. Zwracam uwagę na zmniejszenie ilości metod WhatIs i użycie w jednej z nich parametru TObject.

Kopiuj
program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Classes;

type
  TAlien = class;
  TDigit = class;
  TAlpha = class;

  TAlphanum = class
  public
    function WhatIs(AToken: Char; ALog: TStrings): ShortString; overload;
    function WhatIs(AToken: TObject; ALog: TStrings): ShortString; overload;
    constructor Create;
  end;

  TDigit = class(TAlphanum)
  public
    constructor Create;
  end;

  TAlpha = class(TAlphanum)
  public
    constructor Create;
  end;

  TAlien = class
  public
    constructor Create;
  end;

{$REGION 'Create'}

constructor TAlphanum.Create;
begin
  inherited Create;
end;

constructor TDigit.Create;
begin
  inherited Create;
end;

constructor TAlpha.Create;
begin
  inherited Create;
end;

constructor TAlien.Create;
begin
  inherited Create;
end;

{$ENDREGION}

{$REGION 'WhatIs'}

function TAlphanum.WhatIs(AToken: Char; ALog: TStrings): ShortString;
begin
  ALog.Add('[TAlphanum.WhatIs(Char)]');
  if AToken in ['0'..'9'] then
    Result := TDigit.ClassName
  else
  if AToken in ['A'..'Z'] then
    Result := TAlpha.ClassName
  else
    Result := TAlien.ClassName;
end;

function TAlphanum.WhatIs(AToken: TObject; ALog: TStrings): ShortString;
begin
  ALog.Add('[TAlphanum.WhatIs(TObject)]');
  Result := AToken.ClassName;
end;

{$ENDREGION}

var
  log: TStringList;
  alphanum: TAlphanum;
  digit: TDigit;
  alpha: TAlpha;
  alien: Char;
  i: Integer;
begin
  log := TStringList.Create;
  alphanum := TAlphanum .Create;
  digit := TDigit.Create;
  alpha := TAlpha.Create;
  log.Add('Character ''7'' is of type ' + alphanum.WhatIs('7', log));
  log.Add('Character ''A'' is of type ' + alphanum.WhatIs('A', log));
  log.Add('Character ''$'' is of type ' + alphanum.WhatIs('$', log));
  log.Add('Variable ''digit'' is of type ' + alphanum.WhatIs(digit, log));
  log.Add('Variable ''alpha'' is of type ' + alphanum.WhatIs(alpha, log));
  log.Add('Variable ''alphanum'' is of type ' + alphanum.WhatIs(alphanum, log));
  try
    {[Pascal Warning] W1036 Variable 'alien' might not have been initialized}
    log.Add('Variable ''alien'' is of type ' + alphanum.WhatIs(alien, log));
  except
    log.Add('Error: Variable ''alien'' not initialized');
  end;
  for i := 0 to log.Count - 1 do
    WriteLn(log[i]);
  WriteLn;
  Write('Press key Enter');
  ReadLn;
  log.Free;
  alphanum.Free;
  digit.Free;
  alpha.Free;
end.

{
[TAlphanum.WhatIs(Char)]
Character '7' is of type TDigit
[TAlphanum.WhatIs(Char)]
Character 'A' is of type TAlpha
[TAlphanum.WhatIs(Char)]
Character '$' is of type TAlien
[TAlphanum.WhatIs(TObject)]
Variable 'digit' is of type TDigit
[TAlphanum.WhatIs(TObject)]
Variable 'alpha' is of type TAlpha
[TAlphanum.WhatIs(TObject)]
Variable 'alphanum' is of type TAlphanum
[TAlphanum.WhatIs(Char)]
Variable 'alien' is of type TAlien

Press key Enter
}
edytowany 2x, ostatnio: Artur Protasewicz
Artur Protasewicz
Artur Protasewicz
  • Rejestracja:ponad 15 lat
  • Ostatnio:ponad 5 lat
  • Postów:233
0
furious programming napisał(a):

Nie wiem też dlaczego TAlfanum jest klasą bazową dla klas cyfr (TDigit) i liter (TAlpha). Powinno być na odwrót – na zbiór znaków alfanumerycznych składają się litery i cyfry. Tyle że Delphi nie wspiera wielodziedziczenia klas, wiec trzeba do tematu podejść w podobny sposób do bieżącego, tyle że lepiej ten kod zaplanować.

Ww. zależność to jest top-down i rzuty, czyli pochodne TAlphanum w kierunkach cyfr i liter. Tak to interpretuję. Zaznaczam, że wolałbym mieć dzidziczenie wielobazowe.

edytowany 2x, ostatnio: Artur Protasewicz
Artur Protasewicz
Artur Protasewicz
Szykuję się na dłuższy spacer. W końcu to mój urlop. Doskonalenie programu zwykle nie jest możliwe w codziennych okolicznościach w pracy.
flowCRANE
Później Ci odpiszę, bo teraz nie mam za bardzo czasu, a i sporo postów napisałeś, więc będzie co komentować.
Artur Protasewicz
Artur Protasewicz
  • Rejestracja:ponad 15 lat
  • Ostatnio:ponad 5 lat
  • Postów:233
0

Rozmawiam trochę sam ze sobą, ale bardzo liczę na to, że ktoś znacznie lepszy ode mnie wrzuci jeszcze swoje uwagi. Moja reputacja na dziś #75/5027 zniżkująca.

Zrobiłem test użycia przykładowych polskich znaków diakrytycznych w nazwach typów, a dokładnie w jednej - TAlien zamieniłem na TAłień. Na konsoli to trochę kiepsko wychodzi, bo się krzaki wyświetlają zamiast polskich znaków. Oczywiście można powalczyć ze stroną kodową 1250 - Windows, 852 - Polska, itd. ale lepiej liczyć na podsystem I/O, który zrobi wszystko za nas. Zapisałem zamiast na ekranie konsoli w pliku tekstowym i odczytałem w Notatniku (można od razu w edytorze Delphi). Poniżej program z polskimi znakami w nazwie klasy, a na końcu treść pliku. Nadmienię, że jest to zrobione w Turbo Delphi Explorer 2006 (bardzo sympatyczna wersja, jeśli przyjąć że na TFrame robi się komponenty, bo takowych dołączać nie można). Jeśli ktoś koniecznie chce mieć wyświetlone na konsoli to przyda się MODE /?

Kopiuj
program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Classes;

type
  TAłień = class;
  TDigit = class;
  TAlpha = class;

  TAlphanum = class
  public
    function WhatIs(AToken: Char; ALog: TStrings): ShortString; overload;
    function WhatIs(AToken: TObject; ALog: TStrings): ShortString; overload;
    constructor Create;
  end;

  TDigit = class(TAlphanum)
  public
    constructor Create;
  end;

  TAlpha = class(TAlphanum)
  public
    constructor Create;
  end;

  TAłień = class
  public
    constructor Create;
  end;

{$REGION 'Create'}

constructor TAlphanum.Create;
begin
  inherited Create;
end;

constructor TDigit.Create;
begin
  inherited Create;
end;

constructor TAlpha.Create;
begin
  inherited Create;
end;

constructor TAłień.Create;
begin
  inherited Create;
end;

{$ENDREGION}

{$REGION 'WhatIs'}

function TAlphanum.WhatIs(AToken: Char; ALog: TStrings): ShortString;
begin
  ALog.Add('[TAlphanum.WhatIs(Char)]');
  if AToken in ['0'..'9'] then
    Result := TDigit.ClassName
  else
  if AToken in ['A'..'Z'] then
    Result := TAlpha.ClassName
  else
    Result := TAłień.ClassName;
end;

function TAlphanum.WhatIs(AToken: TObject; ALog: TStrings): ShortString;
begin
  ALog.Add('[TAlphanum.WhatIs(TObject)]');
  Result := AToken.ClassName;
end;

{$ENDREGION}

var
  log: TStringList;
  alphanum: TAlphanum;
  digit: TDigit;
  alpha: TAlpha;
  alien: Char;
  i: Integer;
  f: Text;
begin
  log := TStringList.Create;
  alphanum := TAlphanum .Create;
  digit := TDigit.Create;
  alpha := TAlpha.Create;
  log.Add('Character ''7'' is of type ' + alphanum.WhatIs('7', log));
  log.Add('Character ''A'' is of type ' + alphanum.WhatIs('A', log));
  log.Add('Character ''$'' is of type ' + alphanum.WhatIs('$', log));
  log.Add('Variable ''digit'' is of type ' + alphanum.WhatIs(digit, log));
  log.Add('Variable ''alpha'' is of type ' + alphanum.WhatIs(alpha, log));
  log.Add('Variable ''alphanum'' is of type ' + alphanum.WhatIs(alphanum, log));
  try
    {[Pascal Warning] W1036 Variable 'alien' might not have been initialized}
    log.Add('Variable ''alien'' is of type ' + alphanum.WhatIs(alien, log));
  except
    log.Add('Error: Variable ''alien'' not initialized');
  end;
  AssignFile(f, 'c:\log.txt');
  ReWrite(f);
  for i := 0 to log.Count - 1 do
    WriteLn(f, log[i]);
  WriteLn(f);
  CloseFile(f);
  Write('Press key Enter');
  ReadLn;
  log.Free;
  alphanum.Free;
  digit.Free;
  alpha.Free;
end.

{
[TAlphanum.WhatIs(Char)]
Character '7' is of type TDigit
[TAlphanum.WhatIs(Char)]
Character 'A' is of type TAlpha
[TAlphanum.WhatIs(Char)]
Character '$' is of type TAłień
[TAlphanum.WhatIs(TObject)]
Variable 'digit' is of type TDigit
[TAlphanum.WhatIs(TObject)]
Variable 'alpha' is of type TAlpha
[TAlphanum.WhatIs(TObject)]
Variable 'alphanum' is of type TAlphanum
[TAlphanum.WhatIs(Char)]
Variable 'alien' is of type TAłień

}
edytowany 3x, ostatnio: Artur Protasewicz
abrakadaber
abrakadaber
  • Rejestracja:ponad 12 lat
  • Ostatnio:7 miesięcy
  • Postów:6610
0

nie mam zielonego pojęcia co próbujesz osiągnąć zmieniając nazwę typu przez dodanie do niej znaków narodowych


Chcesz pomocy - pokaż kod - abrakadabra źle działa z techniką.
Artur Protasewicz
Artur Protasewicz
  • Rejestracja:ponad 15 lat
  • Ostatnio:ponad 5 lat
  • Postów:233
0
abrakadaber napisał(a):

nie mam zielonego pojęcia co próbujesz osiągnąć zmieniając nazwę typu przez dodanie do niej znaków narodowych

Końcowa wersja programu to ta, której post zaczyna się od #project-yet-complted. Dodanie polskich znaków diakrytycznych (narodowych) to tylko test.
Ponieważ usunąłem pole prywatne:

Kopiuj
FClassName: string;

wcześniej zwane:

Kopiuj
_className: string;

w którym pierwotnie umiszczałem słowa "cyfra", "litera", a więc słowa po polsku, zrobiłem test polskich znaków.
Niemniej to już jest pewne uzupełnienie, a nie wersja finalna.
Tutaj tego nie ma, ale w pewnym momencie myślałem o klasie TCoś, może to mnie skłoniło do testu,
ale generalnie jestem przeciwnikiem pisania programów z polskimi identyfikatorami.

Artur Protasewicz
Artur Protasewicz
  • Rejestracja:ponad 15 lat
  • Ostatnio:ponad 5 lat
  • Postów:233
0

Kończę swój udział w wątku i wracam do programowania.
Program pierwotnie, jeszcze na Miroblogu po hashtagiem #universum, został oceniony, jako nienajgorszy.
W tej chwili, w mojej opinii jest dużo lepszy, choć to określenie nieścisłe.

Pozostaje mi praca do zrobienia:
Zapoznanie się z tematyką SRP, SOLID, KISS, DRY, czego nie mogę zrobić na poczekaniu.

Dziękuję za pomoc i udział: @furious programming oraz @abrakadaber

Poniżej wersja końcowa:
(przepraszam za powtórzenie z istniejącego już postu)

Kopiuj
program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Classes;

type
  TAlien = class;
  TDigit = class;
  TAlpha = class;

  TAlphanum = class
  public
    function WhatIs(AToken: Char; ALog: TStrings): ShortString; overload;
    function WhatIs(AToken: TObject; ALog: TStrings): ShortString; overload;
    constructor Create;
  end;

  TDigit = class(TAlphanum)
  public
    constructor Create;
  end;

  TAlpha = class(TAlphanum)
  public
    constructor Create;
  end;

  TAlien = class
  public
    constructor Create;
  end;

{$REGION 'Create'}

constructor TAlphanum.Create;
begin
  inherited Create;
end;

constructor TDigit.Create;
begin
  inherited Create;
end;

constructor TAlpha.Create;
begin
  inherited Create;
end;

constructor TAlien.Create;
begin
  inherited Create;
end;

{$ENDREGION}

{$REGION 'WhatIs'}

function TAlphanum.WhatIs(AToken: Char; ALog: TStrings): ShortString;
begin
  ALog.Add('[TAlphanum.WhatIs(Char)]');
  if AToken in ['0'..'9'] then
    Result := TDigit.ClassName
  else
  if AToken in ['A'..'Z'] then
    Result := TAlpha.ClassName
  else
    Result := TAlien.ClassName;
end;

function TAlphanum.WhatIs(AToken: TObject; ALog: TStrings): ShortString;
begin
  ALog.Add('[TAlphanum.WhatIs(TObject)]');
  Result := AToken.ClassName;
end;

{$ENDREGION}

var
  log: TStringList;
  alphanum: TAlphanum;
  digit: TDigit;
  alpha: TAlpha;
  alien: Char;
  i: Integer;
begin
  log := TStringList.Create;
  alphanum := TAlphanum .Create;
  digit := TDigit.Create;
  alpha := TAlpha.Create;
  log.Add('Character ''7'' is of type ' + alphanum.WhatIs('7', log));
  log.Add('Character ''A'' is of type ' + alphanum.WhatIs('A', log));
  log.Add('Character ''$'' is of type ' + alphanum.WhatIs('$', log));
  log.Add('Variable ''digit'' is of type ' + alphanum.WhatIs(digit, log));
  log.Add('Variable ''alpha'' is of type ' + alphanum.WhatIs(alpha, log));
  log.Add('Variable ''alphanum'' is of type ' + alphanum.WhatIs(alphanum, log));
  try
    {[Pascal Warning] W1036 Variable 'alien' might not have been initialized}
    log.Add('Variable ''alien'' is of type ' + alphanum.WhatIs(alien, log));
  except
    log.Add('Error: Variable ''alien'' not initialized');
  end;
  for i := 0 to log.Count - 1 do
    WriteLn(log[i]);
  WriteLn;
  Write('Press key Enter');
  ReadLn;
  log.Free;
  alphanum.Free;
  digit.Free;
  alpha.Free;
end.

{
[TAlphanum.WhatIs(Char)]
Character '7' is of type TDigit
[TAlphanum.WhatIs(Char)]
Character 'A' is of type TAlpha
[TAlphanum.WhatIs(Char)]
Character '$' is of type TAlien
[TAlphanum.WhatIs(TObject)]
Variable 'digit' is of type TDigit
[TAlphanum.WhatIs(TObject)]
Variable 'alpha' is of type TAlpha
[TAlphanum.WhatIs(TObject)]
Variable 'alphanum' is of type TAlphanum
[TAlphanum.WhatIs(Char)]
Variable 'alien' is of type TAlien

Press key Enter
}
abrakadaber
abrakadaber
  • Rejestracja:ponad 12 lat
  • Ostatnio:7 miesięcy
  • Postów:6610
0

a wytłumacz mi jeszcze po co są Ci te puste konstruktory?

Kopiuj
constructor TAlien.Create;
begin
  inherited Create;
end;

tak naprawdę aby to miało ręce i nogi to powinieneś mieć interfejs (bo w delphi nie ma de facto klas abstrakcyjnych ani możliwości na zablokowanie utworzenia instancji klasy), po którym będą dziedziczyły te klasy TAlien, TDigit i TAlpha. Wtedy w metodzie function WhatIs(AToken: TObject; ALog: TStrings): ShortString; overload; jako parametr nie przekazujesz TObject tylko ten interfejs. Zapobiegnie to zrobienia np. czegoś takiego

Kopiuj
  alpha: TStringList;
begin
  alpha := TStringList.Create;
  log.Add('Variable ''digit'' is of type ' + alphanum.WhatIs(alpha, log));

BTW ta linijka (i analogiczne) log.Add('Variable ''digit'' is of type ' + alphanum.WhatIs(alpha, log)); jest co najmniej dziwna - najpierw masz log.Add a potem przekazujesz log do metody WhatIs. Ja rozumiem, że to do logowania ale jednak


Chcesz pomocy - pokaż kod - abrakadabra źle działa z techniką.
Artur Protasewicz
Artur Protasewicz
Dzięki tym konstruktorom tokeny stają się obiektami. Co do logu, to ja dbałem o to, żeby nie powstała sytuacja, jaką pokazujesz. Zasadnicza część to jest alphanum.WhatIs(alpha); Jeśli zrobimy alpha := TDigit.Create; to w odpowiedzi dostaniesz, że alpha jest TDigit. Log pilnowany przeze mnie co do doboru słów i zmiennych, tylko mi pomagał jednocześnie sprawdzić wszysko, w jednym uruchomieniu programu. Log jako taki można wyrzucić, co nie zmieni działania kodu obiektowego.
Artur Protasewicz
Artur Protasewicz
Jesteśmy na granicy zbioru pojęć gramatyki np. języka polskiego i tu jest token, a świta technicznego rozwiązania, które realizuje tę gramatykę w Delphi obiektowo i tu są TObject, TDigit, TAlpha... Programista stoi tu pomiędzy tymi dwoma zbiorami.
Artur Protasewicz
Artur Protasewicz
Cofnąłem haczyk w poprzednim poście. Wyrzucę jeszcze log z kodu.
Artur Protasewicz
Artur Protasewicz
  • Rejestracja:ponad 15 lat
  • Ostatnio:ponad 5 lat
  • Postów:233
0

Kolejna wersja. Dla mnie w pełni akceptowalna:

Kopiuj
program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Classes;

type
  TAlien = class;
  TDigit = class;
  TAlpha = class;

  TAlphanum = class
  public
    function WhatIs(AToken: Char): ShortString; overload;
    function WhatIs(AToken: TObject): ShortString; overload;
    constructor Create;
  end;

  TDigit = class(TAlphanum)
  public
    constructor Create;
  end;

  TAlpha = class(TAlphanum)
  public
    constructor Create;
  end;

  TAlien = class
  public
    constructor Create;
  end;

{$REGION 'Create'}

constructor TAlphanum.Create;
begin
  inherited Create;
end;

constructor TDigit.Create;
begin
  inherited Create;
end;

constructor TAlpha.Create;
begin
  inherited Create;
end;

constructor TAlien.Create;
begin
  inherited Create;
end;

{$ENDREGION}

{$REGION 'WhatIs'}

function TAlphanum.WhatIs(AToken: Char): ShortString;
begin
  if AToken in ['0'..'9'] then
    Result := TDigit.ClassName
  else
  if AToken in ['A'..'Z'] then
    Result := TAlpha.ClassName
  else
    Result := TAlien.ClassName;
end;

function TAlphanum.WhatIs(AToken: TObject): ShortString;
begin
  Result := AToken.ClassName;
end;

{$ENDREGION}

var
  alphanum: TAlphanum;
  digit: TDigit;
  alpha: TAlpha;
  alien: Char;
begin
  alphanum := TAlphanum .Create;
  digit := TDigit.Create;
  alpha := TAlpha.Create;
  WriteLn(alphanum.WhatIs('7'));
  WriteLn(alphanum.WhatIs('A'));
  WriteLn(alphanum.WhatIs('$'));
  WriteLn(alphanum.WhatIs(digit));
  WriteLn(alphanum.WhatIs(alpha));
  WriteLn(alphanum.WhatIs(alphanum));
  try
    {[Pascal Warning] W1036 Variable 'alien' might not have been initialized}
    WriteLn(alphanum.WhatIs(alien));
  except
    WriteLn('Error: Variable not initialized');
  end;
  Write('Press key Enter');
  ReadLn;
  alphanum.Free;
  digit.Free;
  alpha.Free;
end.

{
TDigit
TAlpha
TAlien
TDigit
TAlpha
TAlphanum
TAlien
Press key Enter
}
abrakadaber
abrakadaber
  • Rejestracja:ponad 12 lat
  • Ostatnio:7 miesięcy
  • Postów:6610
2

skopiuj, skompiluj i zobacz, że nie ma żadnej różnicy :)

Kopiuj
program Project1;
 
{$APPTYPE CONSOLE}
 
uses
  SysUtils,
  Classes;
 
type
   TAlphanum = class
  public
    function WhatIs(AToken: Char): ShortString; overload;
    function WhatIs(AToken: TObject): ShortString; overload;
  end;
 
  TDigit = class(TAlphanum)
  end;
 
  TAlpha = class(TAlphanum)
  end;
 
  TAlien = class  //BTW dlaczego nie dziedziczy po TAlphanum?
  end;

 {$REGION 'WhatIs'}
 
function TAlphanum.WhatIs(AToken: Char): ShortString;
begin
  if AToken in ['0'..'9'] then
    Result := TDigit.ClassName
  else
  if AToken in ['A'..'Z'] then
    Result := TAlpha.ClassName
  else
    Result := TAlien.ClassName;
end;
 
function TAlphanum.WhatIs(AToken: TObject): ShortString;
begin
  Result := AToken.ClassName;
end;
 
{$ENDREGION}
 
var
  alphanum: TAlphanum;
  digit: TDigit;
  alpha: TAlpha;
  alien: Char;
begin
  alphanum := TAlphanum .Create;
  digit := TDigit.Create;
  alpha := TAlpha.Create;
  WriteLn(alphanum.WhatIs('7'));
  WriteLn(alphanum.WhatIs('A'));
  WriteLn(alphanum.WhatIs('$'));
  WriteLn(alphanum.WhatIs(digit));
  WriteLn(alphanum.WhatIs(alpha));
  WriteLn(alphanum.WhatIs(alphanum));
  try
    {[Pascal Warning] W1036 Variable 'alien' might not have been initialized}
    WriteLn(alphanum.WhatIs(alien));
  except
    WriteLn('Error: Variable not initialized');
  end;
  Write('Press key Enter');
  ReadLn;
  alphanum.Free;
  digit.Free;
  alpha.Free;
end.

Chcesz pomocy - pokaż kod - abrakadabra źle działa z techniką.
Zobacz pozostały 1 komentarz
Artur Protasewicz
Artur Protasewicz
No właściwie to niedobry przykład z tym orzeczeniem i podmiotem. Zapomniałem, że zmierzam do ACTION! Apologize.
flowCRANE
@Artur Protasewicz: wciąż zastanawiam się nad tym, jak będzie wyglądało wykorzystanie tych klas w docelowym projekcie. Nie rozumiem ich zastosowania.
Artur Protasewicz
Artur Protasewicz
@furious programming: Mam zamiar opisać w ten sposób na początek liczbę hex i identyfikator. Pisanie o konkretach powinno to rozjaśnić. Mam jeszcze jedną wersję programu. Nie wiem czy ją tu wrzucić, bo wątek jest już jakby zamknięty - jest haczyk i kółko.
vpiotr
Ładnie i krótko, ale w realnym rozwiązaniu byłaby tablica o indeksie Char i wartościach TAlphanum (klasyfikacja w czasie O(1)).
Artur Protasewicz
Artur Protasewicz
Możecie dyskutować, mnie dziś czka 6 godzin podróży więc i tak nie mogę brać udziału. Wylączam się.
Artur Protasewicz
Artur Protasewicz
  • Rejestracja:ponad 15 lat
  • Ostatnio:ponad 5 lat
  • Postów:233
0

POST FACTUM

Zaczynam rozdział po udoskonaleniu programu Delphi do poziomu, który nazwę wzorcowym dla moich dalszych poczynań programistycznych.
Został postawiony haczyk, jest w kółku.
Ostateczną postać programowi (na tym etapie) nadał @abrakadaber
Obrałem sobie za następne rzeczy do zrobienia: liczbę heksadecymalną i identyfikator.
Zgadzam się z @vpiotr że trzeba mówić raczej o ciągach znaków, czyli o typie String (ew. ShortString) i ten etap teraz się zaczyna.
Myślę, że rozwijanie wątku ma sens, niezależnie od istnienia rozwiązania oznaczonego haczykiem.
Jak sądzicie, kontynuować ten wątek, czy zacząć nowy? @furious programming @abrakadaber @vpiotr

flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:około godziny
  • Lokalizacja:Tuchów
  • Postów:12166
0
Artur Protasewicz napisał(a):

Obrałem sobie za następne rzeczy do zrobienia: liczbę heksadecymalną i identyfikator.
Zgadzam się z @vpiotr że trzeba mówić raczej o ciągach znaków, czyli o typie String (ew. ShortString) i ten etap teraz się zaczyna.

O to właśnie pytałem od samego początku – po co są te klasy. Na początku przypuszczałem, że są one typem Char na sterydach, które do reprezentacji dowolnego ciągu znaków będą wymagać ręcznej alikacji pamięci (konstruktory klas) oraz przechowywania ich w liście (której pamięcią też trzeba będzie samemu zarządzać).

Od początku sądziłem, że to spory overcoding, niewarty czasu, a i solidna komplikacja dość prostych zadań. Nie mam tutaj na myśli analizy kodu źródłowego, a takich czynności jak ekstrakcja fragmentów tekstu czy porównywanie ciągów. Biblioteka standardowa posiada mnóstwo gotowych funkcji i typów danych, a Ty całe to dobrodziejstwo chcesz wyrzucić do kąta i samemu pisać wszystko od podstaw. Bez jakiegokolwiek zysku – ani krócej nie będzie trwało pisanie kodu, ani nie będzie łatwiejsze, ani sam kod nie będzie działał szybciej.

Dlatego nie rozumiem po co to robisz i tracisz czas na rzeczy, których nie potrzebujesz.

Myślę, że rozwijanie wątku ma sens, niezależnie od istnienia rozwiązania oznaczonego haczykiem.
Jak sądzicie, kontynuować ten wątek, czy zacząć nowy?

Przyjęło się na forum, że jeden wątek dotyczy jednego problemu. Rób jak uważasz, byle uniknąć bałaganu.


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
Artur Protasewicz
Artur Protasewicz
  • Rejestracja:ponad 15 lat
  • Ostatnio:ponad 5 lat
  • Postów:233
0
furious programming napisał(a):

Przyjęło się na forum, że jeden wątek dotyczy jednego problemu. Rób jak uważasz, byle uniknąć bałaganu.

Trochę powalczyłem z liczbą hex i widzę, że rzeczywiście zrobi się bałagan, bo już nie widzę rozwiązania bez iteracji na typie String i funkcji Pos(SubStr, Str): Integer i nie wiem jak to "zjeść" modelem, który dotąd omawialiśmy. Tworzy się jakaś dwoistość modelu i to jest bałagan.

Problem był. Został rozwikłany. I na tym koniec. Dzięki za pomoc.

flowCRANE
Bałagan, o którym wspomniałem, dotyczy wątku, nie Twojego projektu. ;)
flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:około godziny
  • Lokalizacja:Tuchów
  • Postów:12166
0

Klasami powinieneś opisywać konkretne tokeny i gromadzić je w jakichś kontenerach (generycznych listach czy drzewach – w zależności od wymagań). Natomiast kod powineneś trzymać jako standardowy ciąg znaków i używać funkcji z biblioteki standardowej do jego przetwarzania.

Ok, skoro nie chcesz dalej dyskutować to na tym zakończmy. W razie problemów pytaj śmiało.


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
edytowany 3x, ostatnio: flowCRANE
Artur Protasewicz
Artur Protasewicz
Ok.
somedev
  • Rejestracja:ponad 6 lat
  • Ostatnio:prawie 5 lat
  • Postów:666
0

Jeden z kolegów z tego forum popełnił kompilator języka w Pascalu - @Patryk27. Duża część, jak struktura głów,a parsowanie, optymalizacja etc. będzie podobna, między językami, więc sugeruje najpierw zobaczyć, co i jak się piszę, a nie tworzyć takich "wywijasów" ;) Na uczelni jak ktoś pisał sam niepotrzebne biblioteki, zamiast zrozumieć algorytm i po prostu używać standardowej z danego języka, to jeden wykładowca sugerował, że do liczenia całek też powinniśmy sami dojść, a nie z książek. Świat bez korzystania z dziedzictwa stal by w miejscu, a tak to idziemy do przodu i obecnie nie trzeba np. pisać obsługi listy czy mamy kolekcje bezpieczne pod względem wielu wątków. Polecam zacząć od lektury https://github.com/Patryk27/SScript-Compiler. Zresztą, to też nie jest tak, że w taki sposób ręczny powinno się parsery robić - od tego są narzędzia jak Bison, Yacc, Lex - co prawda można w tym opisać gramatykę i generować parsery w C, ale widzę, że ktoś i dla Delphi napisał geneator na bazie tych tooli - https://github.com/RomanYankovsky/ndyacclex Nader wszystko proponuję zacząć, od teorii https://pl.wikipedia.org/wiki/Generator_parser%C3%B3w No i życzę serdecznie powodzenia i przyjemności podczas zabawy z Delphi na urlopie ;)

edytowany 1x, ostatnio: somedev
Patryk27
Odnośnie generatorów parserów: wydaje mi się, że na przykład taki Rust od Mozilli ma ręcznie napisany parser i śmiga całkiem nieźle ;-) Tym niemniej oczywiście lepiej jest wykorzystywać gotowce, niż tracić czas na wynajdywanie koła od nowa;
somedev
No to zależy, czy ktoś chce rozwiązać jakiś problem w najkrótszym czasie w najlepszy sposób, czy po prostu jego celem jest pisanie parsera samo w sobie ;) Jedni jadą na ryby, inni rozwiązują wykreślanki, a jak widać można i parsery pisać. Może faktycznie dawno nie pisałem niczego dla samego faktu pisania i odr azu szukam sensu biznesowego ;)
Artur Protasewicz
Artur Protasewicz
  • Rejestracja:ponad 15 lat
  • Ostatnio:ponad 5 lat
  • Postów:233
0

@somedev: Powtórzę teksty, które są tylko na moim Mikroblogu, a od których ten wątek się zaczyna:

HashTag: #universum

Mikroblog. Post 1.

Czasami popadam w stwarzanie świata od nowa i myślę nad czymś, co już dawno wymyślono. Myślę sobie: diabli nadali tę informatykę, a z drugiej strony - przecież to kochasz i to twój najlepszy azyl. Ale też pojawia się dość ważne pytanie w kontekście korzystania z rzeczy gotowych, już stworzonych, doskonalonych i utrwalonych przez lata - czy ktoś będzie umiał to zrobić od nowa tak samo, za pięć, dziesięć, dwadzieścia lat? Niby po co i dlaczego miałaby istnieć taka potrzeba? Trudno powiedzieć, czy nastąpi jakaś zagłada, którą chętnie posiłkują się media i różne popularno-naukowe filmy. A może świat będzie posługiwał się zupełnie nowymi środkami komunikacji. Za bardzo w to nie wierzę, choć to na pewno subiektywne odczucie - ani w zagładę nie wierzę, ani w nowe środki komunikacji.

Mikroblog. Post 2.

Metodą prób i błędów, nieco popartą wyczuciem wynikającym z doświadczenia, wchodząc w interakcję z kompilatorem Delphi, pociągnąłem to, co zacząłem postem wcześniejszym z tym samym hashtagiem. Przypominam sobie Delphi, wybierając losowo tematy, które są w stanie mnie trochę wciągnąć. Zacznę od tego, co zobaczyłem na końcu swojej ni to pracy, ni to zabawy. Odnotowuję chwilę radości programisty, nawet, jeśli to wszystko jest bez sensu.

somedev
  • Rejestracja:ponad 6 lat
  • Ostatnio:prawie 5 lat
  • Postów:666
0

No to życzę przyjemności w tym kręceniu korbką, bo chyba do tego się to sprowadza ;)

Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)