As extensões de linguagem desaparecidas do compilador MetaWare High C (2023)
(duriansoftware.com)- O High C Compiler para FM TOWNS, de 1989, ia além do suporte ao ambiente DOS e trazia vários recursos de linguagem voltados ao usuário que eram raros em compiladores C da época
- Combinado ao DOS extender da Phar Lap, ele se tornou o compilador C 1st-party do FM TOWNS em um fluxo de desenvolvimento que aproveitava o 80386 de 32 bits no ambiente MS-DOS de 16 bits
- Underscores em literais numéricos, argumentos nomeados, intervalos em
case, funções aninhadas e geradores são recursos que só entraram nos padrões C/C++ muito mais tarde, ou que ainda hoje não existem no padrão - Funções aninhadas ofereciam “full function value” na forma de closures não escapáveis, passando juntas a ponteiro de função e ponteiro de contexto, o que era mais expressivo que ponteiros de função C comuns
- Geradores eram implementados como açúcar sintático sobre funções aninhadas: o corpo do loop
fordo chamador era transformado em uma função aninhada e passado como argumento deyield, numa estrutura simples
O lugar do FM TOWNS e do High C
- Um manual de compilador C dos anos 1980, encontrado em uma pilha de livros sobre FM TOWNS, continha extensões de linguagem mais ricas do que se esperava
- Para usar C e linguagens da sua família em ambientes reais, por muito tempo foram necessárias extensões de fornecedores
- Hoje, em ambientes centrados em GCC, Clang e MSVC, as extensões tendem a se concentrar em tratamento específico de plataforma ou controle de detalhes de baixo nível
- Nos anos 1980, empresas menores e mais numerosas competiam por adoção, e as extensões também eram mais variadas
- A Phar Lap criou um dos primeiros DOS extenders, que permitia usar o processador 80386 de 32 bits em ambientes MS-DOS de 16 bits
- A MetaWare portou o High C Compiler para o SDK do DOS extender da Phar Lap a pedido da Phar Lap
- A Fujitsu integrou o DOS extender da Phar Lap ao sistema operacional da plataforma FM TOWNS, baseada no 80386, e o High C se tornou o compilador C 1st-party dessa plataforma
- O FM TOWNS foi lançado em 1989, pouco antes da ratificação do C89, o primeiro padrão ANSI C
Pequenas conveniências que vieram antes do padrão
-
Separadores underscore em literais numéricos
- Era possível inserir separadores underscore dentro de números para tornar literais numéricos longos mais legíveis
- C++ introduziu separadores com apóstrofo, como
1'000'000, no C++14 - C só introduziu um recurso semelhante no C23
-
Argumentos nomeados
- Era possível nomear argumentos em funções com muitos parâmetros ou que usam muitos tipos cujo significado não fica claro no ponto de chamada, como
bool - Os argumentos nomeados do High C funcionam de modo parecido com o popular recurso do Python
- O rótulo do argumento é opcional
- Quando há rótulo, a sintaxe
argumentName => valuepermite especificar argumentos em ordem arbitrária - Argumentos sem rótulo e com rótulo podem ser misturados, mas todos os parâmetros da função precisam ter um argumento correspondente
- C e C++ padrão ainda não têm esse recurso
- Era possível nomear argumentos em funções com muitos parâmetros ou que usam muitos tipos cujo significado não fica claro no ponto de chamada, como
-
Intervalos em
case- Oferecia um recurso para fazer match de um intervalo de valores de uma vez, como
case low..highem Pascal - C e C++ padrão não adotaram esse recurso
- Oferecia um recurso para fazer match de um intervalo de valores de uma vez, como
Funções aninhadas e full function value
- High C permite declarar funções aninhadas dentro de funções, como em Pascal
- A forma de implementação se aproxima de uma versão mais completa do que as funções aninhadas do Pascal padrão ou da extensão de funções aninhadas do GCC
- High C permite declarar não só funções aninhadas, mas também tipos full function value
- Diferentemente dos ponteiros de função C tradicionais, eles guardam junto um ponteiro de função e um ponteiro de contexto
- Isso permite reencontrar o contexto capturado por uma função aninhada
- São closures não escapáveis, cuja vida útil não continua depois que a função externa retorna
- A extensão de funções aninhadas do GCC escrevia código executável na pilha de chamadas para fazer thunk do ponteiro de contexto, tentando permitir que funções aninhadas fossem referenciadas como ponteiros de função comuns
- Essa abordagem trouxe riscos sérios de segurança e levou muitas plataformas a desativarem completamente o recurso
- Referências a funções locais no High C podem ser usadas como valores de primeira classe, mas sua vida útil não se estende para depois do retorno da função externa
- Funções aninhadas também podem fazer
gotopara a função pai- Como blocos do Smalltalk, isso permite saídas não locais para fora da função aninhada
- Com isso, é possível criar funções que se comportam como fluxo de controle
- Objective-C ganhou blocks que podem ser usados como escaping closures em 2009, e C++ introduziu lambdas em 2011
- Nenhum dos dois recursos tinha capacidade de saída não local
- C padrão ainda não tem um recurso oficial de funções aninhadas
Corrotinas geradoras
- A MetaWare destacava o recurso de geradores a ponto de dedicar um capítulo inteiro a ele
- Em 1989, High C oferecia corrotinas geradoras ao estilo Python em plain C
- Uma função geradora é declarada com a sintaxe
void foo(Arg arguments) -> (Yield yields)- Dentro da função, pode-se chamar várias vezes a função mágica
yield(values...)para produzir uma sequência de valores - O chamador percorre os valores gerados em sequência com uma nova sintaxe de loop
for, na formafor variable... <- foo(arguments...) do { ... }
- Dentro da função, pode-se chamar várias vezes a função mágica
- Essa implementação pode ser combinada de maneiras complexas com funções aninhadas
- Uma função aninhada dentro de um gerador pode capturar a operação de
yielddo gerador externo - A função aninhada pode se chamar recursivamente para percorrer uma árvore ou estrutura de dados recursiva, dando
yieldem cada etapa
- Uma função aninhada dentro de um gerador pode capturar a operação de
- Esse formato parece difícil de implementar em Python ou em muitas linguagens mainstream com corrotinas geradoras
Como os geradores são implementados e a diferença em relação às linguagens padrão
- Os geradores do High C funcionam como açúcar sintático sobre funções aninhadas, sem um runtime avançado
- Uma declaração de gerador da forma
void foo(Arg arguments) -> (Yield yields)equivale à declaração de função comumvoid foo(void yield(Yield yields)!, Arg arguments)yieldé um parâmetro implícito do tipo “full function value”- Uma chamada
yield(values)dentro do corpo do gerador é uma chamada de função comum a esse parâmetro implícito de função
- O corpo do loop
forno lado do chamador é transformado em uma função aninhada- Essa função aninhada é passada como argumento
yielddo gerador - A estrutura é simples, mas efetiva
- Essa função aninhada é passada como argumento
- Como funções aninhadas dão suporte a saídas não locais,
break,continueegotoque saem do corpo do loopfortambém funcionam por meio degotopara a posição adequada fora do loop - É improvável que C padrão tente integrar recursos desse tipo
- C++20 oferece um recurso de corrotinas muito flexível e complexo, baseado em transformação de corrotinas em tempo de compilação
- Parece possível usá-lo para implementar geradores
- No entanto, o resultado provavelmente não se combinaria com funções locais de forma tão intuitiva
1 comentários
Comentários do Hacker News
Por sorte, tenho um exemplar da versão em inglês do High C/C++ Language Reference
http://jdebp.uk./FGA/metaware-iterator-driven-for.html
http://jdebp.uk./Proposals/metaware-iterator-driven-for.html
breakoureturneram compilados. Será que isso era transformado de modo que a funçãoyieldretornasse um código de estado e ele fosse verificado no ponto da chamada?case, argumentos nomeados, funções aninhadas, funções aninhadas estáticas e um recurso parecido com geradoresPor exemplo, dá para escrever
int a = 1_234_567;,case 5 .. case 6:,test(b:3, a:4);Funções aninhadas estáticas não podem acessar variáveis do frame da função externa, então aparece um erro como
Error: static function test.foo.plus cannot access variable i in frame of function test.fooO recurso parecido com geradores está em https://dlang.org/spec/statement.html#foreach_over_struct_an...
Por exemplo, se você fizer um serviço de cache em memória, talvez seja melhor que os próprios itens do cache não sejam rastreados pelo coletor de lixo. Como ele muitas vezes não conhece o padrão real de acesso, pode acabar atrapalhando. Mas, para a maior parte dos outros componentes desse serviço, ter um coletor de lixo combina mais
E por que os argumentos nomeados são na forma
test(a:4, b:3)e nãotest(.a=4, b.=3);?Também fico curioso sobre como seria possível lidar com tipos de primeira classe em C
lcc-winadicionou sobrecarga de operadores, argumentos padrão de função e sobrecarga de função. A documentação diz para ver “generic functions” [1]O compilador C do Plan 9 também introduziu várias extensões de linguagem, e algumas delas, como structs/unions anônimas, depois entraram no padrão C. Hoje o GCC aceita a flag
-fplan9-extensions[2], que pode habilitar recursos bem úteis, como a conversão automática de ponteiros para struct em campos anônimos em chamadas de função e atribuições[1] https://lcc-win32.services.net/C-Tutorial.pdf
[2] https://gcc.gnu.org/onlinedocs/gcc/Unnamed-Fields.html
É uma pena que isso não tenha se espalhado mais pelo mundo e influenciado os padrões da linguagem. Impressiona que esses recursos já existissem há tanto tempo
Isso já apareceu antes no Hacker News: https://news.ycombinator.com/item?id=38938402
Será que existe uma cópia em PDF em algum lugar?
yield[0]. A linguagem Icon, de época parecida, também tinha um recurso de geradores semelhante [1], e usavasuspendem vez deyield. Pelo que sei, Ada (1983) também tinha algo assimEsses recursos de linguagem não eram completamente desconhecidos
[0] https://publications.csail.mit.edu/lcs/pubs/pdf/MIT-LCS-TR-2...
[1] https://dl.acm.org/doi/pdf/10.1145/800055.802034
Ele explica underscores em números, intervalos em
case, parâmetros nomeados, funções aninhadas e até variáveis de função completashttps://bitsavers.org/pdf/metaware/…
Basta olhar o Apêndice A, umas 50 páginas antes do fim do arquivo
Na época em que eu estava aprendendo e escrevendo código, acabei conhecendo isso por meio de sites meio suspeitos
Também vai descobrir a rica história das linguagens de programação de sistemas. E perceber como o design de C e de Go se parece no quanto ignorou o que estava acontecendo em outros ecossistemas e as experiências do passado
O link para o manual do compilador está em https://winworldpc.com/product/metaware-high-c-cpp/33x
O PDF do manual de C traz uma indicação de copyright de 2007
https://f.duriansoftware.com/@joe/113195961485703110
¥nem vez de\n, esses exemplos de código parecem ter sido escritos em Shift-JIS. Em Shift-JIS,¥ocupa a posição do\em ASCII[0] https://en.wikipedia.org/wiki/JIS_X_0201
EUC-JP é melhor para esse uso, porque não tem esse problema. Em Pascal, se você usa comentários
(* *)e não comentários{ }, também não há esse problema ao usar Shift-JISEm vez disso, é bem provável que estivesse sendo usado o JIS X 0201, que serviu de base para o Shift-JIS (https://en.m.wikipedia.org.org/wiki/JIS_X_0201)
C:¥em vez deC:\Call (Param_A => 1, Param_B => "Foo");, sublinhados em números de bases arbitrárias (X : Integer := 1_000;), subprogramas aninhados e verificação baseada em intervalosParece que a gente frequentemente esquece o quanto C era incrivelmente primitivo na época em comparação com várias outras linguagens
Não conheço o suficiente sobre convenções de escrita japonesa ou regras de kerning, mas parece que pegaram uma fonte proporcional com kanji e caracteres latinos e a forçaram a caber em células de largura fixa
De todo modo, é bom que os exemplos de código não estejam em fonte de 8pt como em muitos livros que tenho
try_fold()(https://scribe.rip/@veedrac/rust-is-slow-and-i-am-the-cure-3...)Mas, justamente por isso, parece que essas extensões ficaram relativamente desconhecidas e precisaram ser redescobertas e reinventadas décadas depois em C/C++ moderno