Fenner
A linguagem

Declarações e comandos

const, type e var; variáveis que nascem no meio do bloco; if, case, laços, rótulos, with e os operadores.

Declarações#

Fenner
const
  Limit = 3;                                              // constante
  Greeting: UString = 'olá';                              // constante tipada
  Names: array[0..2] of UString = ('um', 'dois', 'três'); // de vetor também

var
  Total: Int32 = 100;       // variável global com valor inicial
  Fresh: default Int32;     // "default": a variável nasce zerada

const aceita constantes simples, tipadas, de vetor e de record (inclusive com parte variante). var aceita valor inicial. threadvar declara uma variável por thread.

declarations.fxp
program Declarations;

const
  Limit = 3;                                              // constante
  Greeting: UString = 'olá';                              // constante tipada
  Names: array[0..2] of UString = ('um', 'dois', 'três'); // constante tipada de vetor

type
  TPoint = record
    X, Y: Int32;
  end record;

  TItem = class(TObject)
  public
    Value: Int32;
  end class;

var
  Total: Int32 = 100;          // global com valor inicial
  P: TPoint;

function NewItem(const AValue: Int32): TItem;
begin
  Result := TItem.Create;
  Result.Value := AValue;
end function;

procedure Demo;
var
  Fresh: default Int32;        // "default" faz a variável nascer zerada
begin
  WriteLn('Fresh = ', Fresh);
end procedure;

begin
  WriteLn(Greeting, ' ', Names[Limit - 1], ' ', Total);
  Demo;

  // with sobre um valor, com apelido: o objeto vive enquanto o bloco o usa
  with NewItem(7) as It do
    WriteLn('Item.Value = ', It.Value);

  P.X := 1;
  P.Y := 2;
  WriteLn('P = (', P.X, ', ', P.Y, ')');
end.
Saída
olá três 100
Fresh = 0
Item.Value = 7
P = (1, 2)

Variáveis que nascem prontas#

Duas formas deixam de lado o "declare tudo no alto":

A lista entre parênteses depois do tipo constrói o objeto. C: TCounter(10) é a chamada de TCounter.Create(10), com a mesma escolha de sobrecarga. O comando entra no começo do corpo, o único ponto em que "já construído ao entrar" vale por todos os caminhos. Só em variável local, e uma por linha.

var no meio de um bloco. Um begin…end é um escopo: o var escrito no meio dele vale dali até o end, e o mesmo nome pode voltar num bloco irmão com outro tipo.

FormaO que faz
var X: T := V;tipo escrito, valor inicial
var X: T(args);tipo escrito, objeto construído
var X := V;o tipo vem do valor (o valor é obrigatório)
for var I := 1 to N doa variável do laço vive só no laço
inline-vars.fxp
program InlineVars;

type
  TCounter = class(TObject)
  public
    Start: Int32;
    constructor Create(const AStart: Int32);
    destructor Destroy; override;
  end class;

constructor TCounter.Create(const AStart: Int32);
begin
  Start := AStart;
  WriteLn('  Create(', AStart, ')');
end constructor;

destructor TCounter.Destroy;
begin
  WriteLn('  Destroy(', Start, ')');
  inherited Destroy;
end destructor;

procedure Demo;
var
  C: TCounter(10);              // declarada E construída: TCounter.Create(10)
begin
  WriteLn('corpo: ', C.Start);

  if C.Start > 5 then
  begin
    var Total := C.Start * 2;   // o tipo vem do valor
    var Msg: UString := 'bloco';
    WriteLn(Msg, ' ', Total);
  end if;                          // Total e Msg morrem aqui

  for var I := 1 to 3 do
    Write(I, ' ');
  WriteLn;
end procedure;

begin
  Demo;
  WriteLn('fim');
end.
Saída
  Create(10)
corpo: 10
bloco 20
1 2 3 
  Destroy(10)
fim

O que for gerenciado (objetos, cadeias, interfaces) morre no end, num try…finally que o compilador monta, de modo que um Exit no meio do bloco também solta o que o bloco criou. A destruição acontece na ordem inversa da declaração.

Condicionais#

Fenner
if N < 2 then
  Result := N
else
  Result := Fib(N - 1) + Fib(N - 2);

if também é uma expressão (o else passa a ser obrigatório), na grafia por extenso ou na com ?:

Fenner
WriteLn(if I > 5 then 'grande' else 'pequeno');
WriteLn(I > 5 ? 'grande' : 'pequeno');

Só o ramo escolhido é avaliado. Os dois ramos têm de terminar no mesmo tipo: número com número dá o mais largo, dois objetos dão o ancestral comum, e nil aceita o tipo do outro lado.

case#

Fenner
case I of
  0..4:    WriteLn('baixo');
  5, 6, 7: WriteLn('médio');
  8..99:   WriteLn('alto');
else
  WriteLn('fora da faixa');
end case;

O seletor pode ser inteiro, enumerado ou caractere. Os rótulos aceitam faixas, listas e expressões constantes (VT_VECTOR or VT_UI1:), desde que o seletor seja inteiro.

Para cadeias há scase (sensível a maiúsculas) e icase (insensível); uscase e uicase são as versões para UTF:

case-strings.fxp
program CaseStrings;

var
  Command: UString;
begin
  Command := 'Stop';

  // scase: o case de cadeia, sensível a maiúsculas
  scase Command of
    'start': WriteLn('iniciar');
    'Stop':  WriteLn('parar');
  else
    WriteLn('comando desconhecido');
  end scase;

  // icase: o mesmo, sem diferenciar maiúsculas de minúsculas
  icase Command of
    'START': WriteLn('iniciar');
    'STOP':  WriteLn('parar (sem diferenciar a caixa)');
  else
    WriteLn('comando desconhecido');
  end icase;
end.
Saída
parar
parar (sem diferenciar a caixa)

Laços#

FormaObservação
for I := A to B do / downtofor var I := … deixa a variável viver só no laço
for var I in A..B dosobre uma faixa; a direção vem dos valores (0..1000 sobe, 10000..0 desce)
while Cond dotesta antes
repeat … until Condtesta depois
for X in Colecao dosobre vetores, sobre um campo ChainRoot e sobre chain(obj)
for X downin Colecao dopercorre ao contrário (também vale para vetores)

Break sai do laço, Continue passa à próxima volta. Para sair de fora de um laço aninhado, sem bandeira nem goto, o laço recebe um rótulo entre colchetes e Break o cita pelo nome (ou pelo índice, contado de dentro para fora; Break pelado é Break(0)):

Fenner
for [outer] I := 1 to 3 do
  for [inner] J := 1 to 3 do
  begin
    if J = 2 then
      Break(outer);       // sai dos dois laços
  end for;

O nome é melhor que o índice, porque não muda de significado quando alguém insere um laço no meio. Um salto fora de alcance é erro de compilação.

control.fxp
program Control;

var
  I, J, N: Int32;
  Q, R: Int32;
  Names: array[0..2] of UString;
  Name: UString;
begin
  Names[0] := 'Ana'; Names[1] := 'Bia'; Names[2] := 'Caio';

  // for, com e sem passo invertido
  for I := 1 to 3 do
    Write(I, ' ');
  for I := 3 downto 1 do
    Write(I, ' ');
  WriteLn;

  // for sobre faixa: a direção vem dos valores
  for var K: Int32 in 0..3 do
    Write(K, ' ');
  WriteLn;

  // for-in sobre vetor; downin percorre do fim para o começo
  for Name in Names do
    Write(Name, ' ');
  WriteLn;
  for Name downin Names do
    Write(Name, ' ');
  WriteLn;

  // laço com rótulo: Break(nome) sai do laço de fora, sem bandeira nem goto
  N := 0;
  for [outer] I := 1 to 3 do
    for [inner] J := 1 to 3 do
    begin
      Inc(N);
      if J = 2 then
        Break(outer);
    end for;
  WriteLn('passos: ', N);

  // repeat..until e while com Continue
  I := 0;
  repeat
    Inc(I);
    if Odd(I) then
      Continue;
    Write(I, ' ');
  until I >= 8;
  WriteLn;

  // quociente e resto numa conta só
  Q, R := 17 divmod 5;
  WriteLn('17 = 5 * ', Q, ' + ', R);

  // if como valor, nas duas grafias
  WriteLn(if I > 5 then 'grande' else 'pequeno');
  WriteLn(I > 5 ? 'grande' : 'pequeno');

  // case aceita faixas e listas
  case I of
    0..4: WriteLn('baixo');
    5, 6, 7: WriteLn('médio');
    8..99: WriteLn('alto');
  end case;
end.
Saída
1 2 3 3 2 1 
0 1 2 3 
Ana Bia Caio 
Caio Bia Ana 
passos: 2
2 4 6 8 
17 = 5 * 3 + 2
grande
grande
alto

Exit, Result e Halt#

Result é a variável de retorno de uma função. Exit sai da rotina atual; Exit(valor) sai devolvendo um valor (e é a forma mais usada no lugar de atribuir a Result e sair). Halt encerra o programa. Dentro de um try…finally, o finally roda de qualquer jeito.

with#

Fenner
with NewItem(7) as It do
  WriteLn(It.Value);

O with aceita um apelido (as It): a referência vai para uma local escondida e o objeto continua vivo enquanto o bloco o usa. Com o apelido, os nomes usados dentro do bloco ficam explícitos, em vez de se misturarem com os de fora.

Quociente e resto numa conta só#

Fenner
Q, R := 17 divmod 5;      // Q = 3, R = 2

Operadores#

GrupoOperadores
Aritméticos+ - * / div mod
Bitsand or xor not shl shr (o shr é lógico sobre a largura do tipo)
Comparação= <> < > <= >=
Conjuntos e tiposin not in is as
Ponteiros@ ^
Anuláveis?T ?X ?? ?. ! — veja Nulos e exceções
ValorC ? A : B e if C then A else B

and e or avaliam só o necessário (curto-circuito). A atribuição é :=. Os operadores de um record (Add, Equal, Assign, Increment…) são declarados com class operator; veja Classes.

Rótulos e goto#

label e goto existem, para os poucos casos em que o fluxo estruturado não serve. O rótulo é declarado na rotina onde é usado, prefixa um comando e o desvio fica dentro da rotina; atravessar um try/finally/except não vale. Para sair de laços, prefira o laço com rótulo e Break(nome).

Seções protegidas: sync#

sync(Semaforo[, Prazo]) do … else … é uma região protegida por semáforo, com o else do prazo esgotado. Quando entra por um Exit no meio, o semáforo é solto. Quem diz o que é um semáforo é a RTL.

Prefira o end nomeado#

Todo bloco que se fecha com end pode dizer o que está fechando, repetindo a palavra que o abriu: end class, end record, end interface, end procedure, end function, end constructor, end destructor, end operator, end case, end try, end if, end for, end while, end with e end region. O end sozinho continua valendo, mas o nome é o estilo recomendado, e é assim que o código da RTL, da WCL, dos exemplos e dos demos está escrito.

Duas vantagens. Quem lê um end lá no fim de uma rotina comprida sabe na hora o que ele fecha, sem subir a tela procurando o begin. E o compilador confere: um end case onde está aberto um if é erro, com a mensagem dizendo o que realmente está aberto. Assim, um end esquecido ou sobrando é apontado perto de onde está, e não no fim do arquivo.

end-nomeado.fxp
program EndNomeado;

// O "end" pode dizer o que está fechando. O compilador confere: "end case" num "if" é erro.

type
  TPoint = record
    X: Int32;
    Y: Int32;
  end record;

  TCounter = class
  private
    FValue: Int32;
  public
    procedure Add(const AAmount: Int32);
    function Value: Int32;
  end class;

procedure TCounter.Add(const AAmount: Int32);
begin
  FValue := FValue + AAmount;
end procedure;

function TCounter.Value: Int32;
begin
  Result := FValue;
end function;

procedure Describe(const AKind: Int32);
begin
  case AKind of
    1: WriteLn('um');
    2: WriteLn('dois');
  else
    WriteLn('outro');
  end case;
end procedure;

var
  LCounter: TCounter;
  LPoint: TPoint;
  LIndex: Int32;
begin
  LCounter := TCounter.Create;
  try
    for LIndex := 1 to 3 do
    begin
      LCounter.Add(LIndex);
      if LIndex = 2 then
      begin
        WriteLn('dois!');
      end if;
    end for;
    WriteLn('soma = ', LCounter.Value);
  finally
    LCounter.Free;
  end try;

  LPoint.X := 1;
  LPoint.Y := 2;
  Describe(LPoint.X);
  Describe(LPoint.Y);
  Describe(9);
end.
Saída
dois!
soma = 6
um
dois
outro

Cada bloco fecha com o seu nome