Fenner
A linguagem

Tipos

Inteiros e reais que dizem a largura, a família monetária, as quatro cadeias, enumerados, conjuntos, vetores e Variant.

O nome diz a largura#

Em Fenner o nome do tipo carrega o tamanho. Os nomes herdados do Delphi (Integer, Cardinal, Extended…) não existem, e o compilador aponta o erro:

texto
Erro: tipo desconhecido ou não suportado: "Integer"
FamíliaTipos
Inteiros com sinalInt8 Int16 Int32 Int64 Int128 Int256 Int512 Int1024 Int2048 Int4096
Inteiros sem sinalUInt8 UInt16 UInt32 UInt64 UInt128 UInt256 UInt512 UInt1024 UInt2048 UInt4096
ReaisFloat16 Float32 Float64 Float128
MonetáriosCurrency8 Currency16 Currency32 Currency64 Currency128 Currency256 Currency512 Currency1024 Currency2048 Currency4096
LógicosBool8 Bool16 Bool32 Bool64
CaracteresAChar (1 byte), WChar (UTF-16, 2 bytes)
PonteirosPointer (o mesmo que ^void), ^T tipado
Inteiros do tamanho do ponteiroNativeInt, NativeUInt
types.fxp
program Types;

// Os nomes dos tipos dizem a largura: nada de Integer, Cardinal ou Extended.

const
  Million = 1_000_000;     // o "_" só separa dígitos
  Mask = 0b1010_1010;      // binário
  Permissions = 0o755;     // octal
  Color = 0xFF8800;        // hexadecimal ($FF8800, do Pascal, também vale)

var
  Small: Int8;
  Wide: UInt64;
  Huge: Int256;
  Price: Currency64;
  Ratio: Float64;
begin
  Small := 127;
  Wide := 18_446_744_073_709_551_615;
  WriteLn('Int8:   ', Small, ' (', SizeOf(Int8), ' byte)');
  WriteLn('UInt64: ', Wide, ' (', SizeOf(UInt64), ' bytes)');

  // inteiros largos: a aritmética é nativa, sem biblioteca de números grandes
  Huge := 1_000_000_000_000_000_000;
  Huge := Huge * Huge * Huge;
  WriteLn('10^54 = ', Huge, ' (', SizeOf(Int256), ' bytes)');
  WriteLn('10^54 div 10^18 = ', Huge div 1_000_000_000_000_000_000);

  // moeda: ponto fixo; o literal não passa por Float64
  Price := 19.99;
  WriteLn('19.99 * 3 = ', Price * 3);
  Ratio := 1 / 3;
  WriteLn('1 / 3 = ', Ratio);

  WriteLn(Million, ' ', Mask, ' ', Permissions, ' ', Color);
end.
Saída
Int8:   127 (1 byte)
UInt64: 18446744073709551615 (8 bytes)
10^54 = 1000000000000000000000000000000000000000000000000000000 (32 bytes)
10^54 div 10^18 = 1000000000000000000000000000000000000
19.99 * 3 = 59.9700
1 / 3 = 0.333333
1000000 170 493 16746496

Os inteiros largos#

De Int256 a Int4096 (e os UInt correspondentes) a aritmética é nativa: somar, multiplicar, dividir, comparar e converter saem como instrução do LLVM, não como chamada de rotina. Não há biblioteca de números grandes por trás: é o mesmo add/sdiv dos tipos pequenos, só que mais largo. A única peça escrita à mão é a impressão, porque printf não formata mais que 64 bits.

A promoção de tipos numa conta é aritmética, não uma escada de if: o resultado tem a maior das duas larguras, e um operando sem sinal dessa largura sobe um degrau, porque só assim todo valor dele continua representável. Por isso UInt64 + Int32 dá Int128, e UInt128 + Int32 dá Int256.

Float16 e Float32 guardam estreito e calculam em Float64.

A família monetária#

Ponto fixo com sinal. A largura está no nome; as casas decimais não seguem a largura. Os dois primeiros são os que já existiam, e de 256 para cima é bits/8:

TipoCasasTipoCasas
Currency81Currency25632
Currency162Currency51264
Currency323Currency1024128
Currency644Currency2048256
Currency12812Currency4096512

Soma e subtração são inteiras. Produto e divisão passam pela largura dobrada: o produto de dois valores escalados tem a escala ao quadrado, e tirar uma delas é dividir. O arredondamento é meio para longe do zero, em toda a família.

Larguras misturadas reescalam. Somar Currency32 (3 casas) com Currency256 (32) multiplica o estreito por 10²⁹; a conta acontece na largura maior, e só então o resultado estreita. Moeda de 128 bits ou mais ganha do Float64 numa conta; de 64 bits ou menos perde (a regra do Delphi).

As quatro cadeias de texto#

TipoCodificaçãoObservação
LStringbytes, sem codificação declaradasó converte por cast explícito
U8StringUTF-8conversão implícita entre as codificações conhecidas
UStringUTF-16conversão implícita entre as codificações conhecidas
WStringUTF-16 (um BSTR de verdade)para COM; alocada pelo OleAut32

Todas compartilham o cabeçalho [contagem de referências][tamanho], o mesmo do vetor dinâmico. A indexação é base 1 (S[1] é o primeiro caractere). Há ainda a cadeia curta SString[N], com o tamanho no byte 0.

strings.fxp
program Strings;

const
  Query =
    '''
    select * from clientes
    where nome like '%silva%'
    ''';

var
  U: UString;      // UTF-16
  U8: U8String;    // UTF-8
  L: LString;      // bytes, sem codificação declarada
begin
  U := 'Olá, mundo';
  U8 := U;         // conversão implícita entre codificações conhecidas
  WriteLn('UString:  ', Length(U), ' caracteres');
  WriteLn('U8String: ', Length(U8), ' bytes');

  L := 'abc';
  WriteLn(L + '-' + LString(U8));   // LString só converte com cast explícito

  WriteLn(Copy(U, 1, 3), ' | ', Pos('mundo', U));
  U := U + '!' + #13#10 + 'segunda linha';
  WriteLn(U);

  // cadeia de várias linhas: o recuo do fecho sai de cada linha
  WriteLn('[', Query, ']');
end.
Saída
UString:  10 caracteres
U8String: 11 bytes
abc-Olá, mundo
Olá | 6
Olá, mundo!
segunda linha
[select * from clientes
where nome like '%silva%']

Como são quatro cadeias, as conversões dizem qual você quer: IntToUStr, IntToLStr, IntToU8Str, UStrToInt32… Não existe IntToStr.

Delete, Insert, Pos e Copy são formas da linguagem; as dezenas de funções auxiliares moram na RTL. Uma constante tipada de cadeia (const S: UString = '…';) vale para LString, U8String e UString: o literal já sai como bloco estático, e a constante é só o endereço dele.

Enumerados, conjuntos e faixas#

Fenner
type
  TColor = (Red, Green, Blue);
  TColors = set of TColor;
  TByteRange = 0..255;          // vira o MENOR inteiro que contém a faixa (UInt8)
enums-sets.fxp
program EnumsSets;

type
  TColor = (Red, Green, Blue);
  TColors = set of TColor;

  TStatus = (
    Stopped = 0,
    Running = 5,
    Paused = 7
  );

var
  C: TColor;
  S: TColors;
  St: TStatus;
begin
  // o escopo do enum é obrigatório: TColor.Green
  C := TColor.Green;
  S := [TColor.Red, TColor.Green];

  WriteLn(C in S);
  WriteLn(TColor.Blue in S);
  WriteLn(TColor.Blue not in S);

  Include(S, TColor.Blue);
  Exclude(S, TColor.Red);
  WriteLn(TColor.Red in S);

  // no rótulo do case, o seletor já fixa o tipo: Green: vale sem o escopo
  case C of
    Red:   WriteLn('vermelho');
    Green: WriteLn('verde');
    Blue:  WriteLn('azul');
  end case;

  St := TStatus.Running;
  WriteLn(Ord(St));          // valores explícitos, em qualquer ordem
  WriteLn(Ord(TStatus.Paused));
end.
Saída
TRUE
FALSE
TRUE
FALSE
verde
5
7
  • O escopo do enumerado é obrigatório (TColor.Green), exceto no rótulo de um case cujo seletor já fixa o tipo, onde Green: vale.
  • Valores explícitos podem ir em qualquer ordem, e um membro pode ser negativo: isso faz o enumerado ser guardado com sinal, e a comparação acerta sozinha. A largura é o maior entre o que menor..maior exige e o {$config minenumsize N}, e é daí que vêm os 4 bytes que a API do Windows espera.
  • Um set of é um bitset de até 64 elementos; um conjunto de enumerado com membro negativo é recusado. in, not in, Include e Exclude valem.
  • Um tipo faixa não muda o binário: acrescenta verificação em compilação para valores constantes. A verificação em execução é opcional ({$config rangechecks on}).

Vetores#

arrays.fxp
program Arrays;

type
  TByteRange = 0..255;                    // vira UInt8: a faixa é checada em compilação
  TMatrix = array[1..2, 1..3] of Int32;

function Sum(const AValues: array of Int32): Int32;    // vetor aberto
var
  I: Int32;
begin
  Result := 0;
  for I := 0 to High(AValues) do
    Inc(Result, AValues[I]);
end function;

var
  Fixed: array[0..4] of Int32;            // estático
  Names: array of UString;                // dinâmico (contagem de referências)
  Matrix: TMatrix;
  Short: SString[10];                     // cadeia curta: o tamanho vai no byte 0
  R: TByteRange;
  I, J: Int32;
begin
  for I := 0 to 4 do
    Fixed[I] := I * I;
  WriteLn(Length(Fixed), ' elementos, de ', Low(Fixed), ' a ', High(Fixed));

  SetLength(Names, 2);
  Names[0] := 'um';
  Names[1] := 'dois';
  SetLength(Names, 3);                    // cresce preservando o conteúdo
  WriteLn(Length(Names), ' nomes; o segundo é ', Names[1]);

  for I := 1 to 2 do
    for J := 1 to 3 do
      Matrix[I, J] := I * 10 + J;
  WriteLn('Matrix[2, 3] = ', Matrix[2, 3]);

  Short := 'cadeia curta demais';
  WriteLn(Short, ' (', Length(Short), ')');

  R := 200;
  WriteLn(R, ' cabe em ', SizeOf(TByteRange), ' byte');

  WriteLn(Sum([1, 2, 3, 4]));             // literal de vetor passado a um vetor aberto
end.
Saída
5 elementos, de 0 a 4
3 nomes; o segundo é dois
Matrix[2, 3] = 23
cadeia cur (10)
200 cabe em 1 byte
10
FormaO que é
array[0..9] of Int32estático; aceita vários índices (array[1..2, 1..3])
array of Int32dinâmico, com contagem de referências atômica
array[0..?] of UInt8 = (1, 2, 3)o ? conta a lista de valores
const A: array of Int32vetor aberto, como parâmetro; aceita um literal [1, 2, 3]
array of constlista de valores de tipos variados (a base do Format)

downin percorre um vetor do fim para o começo (veja Comandos).

Variant#

Variant e OleVariant não são tipos embutidos no compilador: são records gerenciados da RTL. O que eles fazem sai de class operator: Initialize/Finalize/Assign cuidam do tempo de vida, Implicit converte nos dois sentidos, e Add…GreaterThan dão a aritmética. A diferença entre os dois é a de sempre: o OleVariant só carrega o que o Automation entende e guarda o texto como BSTR, para ir direto a uma API COM.

core-variants.fxp
program Variants;

var
  V: Variant;
begin
  V := 10;
  V := V * 3;                 // aritmética: o Variant é um record gerenciado da RTL
  WriteLn(UString(V));

  V := 'agora é texto';       // o mesmo Variant, outro conteúdo
  WriteLn(UString(V));
end.
Saída
30
agora é texto

Ponteiros#

^T é um ponteiro tipado, Pointer o sem tipo. @X é o endereço, P^ o conteúdo, New/Dispose alocam e liberam um tipo, GetMem/FreeMem blocos. Um ponteiro para um tipo declarado depois (PNode = ^TNode) é aceito. A aritmética de ponteiro existe, e a sobreposição absolute e as regiões {$code unsafe} são o caminho para o que o código seguro recusa.

pointers.fxp
program Pointers;

type
  PNode = ^TNode;               // ponteiro para um tipo declarado depois
  TNode = record
    Value: Int32;
    Next: PNode;
  end record;

var
  Head, Node: PNode;
  I, Sum: Int32;
  X: Int32;
  PX: ^Int32;
begin
  X := 41;
  PX := @X;
  PX^ := PX^ + 1;
  WriteLn('X via ponteiro: ', X);

  // lista ligada: insere na frente
  Head := nil;
  for I := 1 to 5 do
  begin
    New(Node);
    Node^.Value := I * 10;
    Node^.Next := Head;
    Head := Node;
  end for;

  Sum := 0;
  Node := Head;
  while Node <> nil do
  begin
    Sum := Sum + Node^.Value;
    Node := Node^.Next;
  end while;
  WriteLn('soma da lista: ', Sum);

  while Head <> nil do
  begin
    Node := Head^.Next;
    Dispose(Head);
    Head := Node;
  end while;
end.
Saída
X via ponteiro: 42
soma da lista: 150