Red – Fullstack

O Taq postou um twitter sobre os custos da fragmentação das linguagens de programação. Apesar de comum em diversas atividades humanas, o cara tem uma certa razão. O desperdício de tempo de computador, energia elétrica, ideias, etc. é grande. Muitas vezes por nada. Talvez trocar seis por meia dúzia. Atualmente tenho visto uma movimentação em torno de Rust. Os caras estão desenvolvendo até um emacs em Rust. Como se não bastasse as “novas” linguagens disponíveis, temos o problema das plataformas. Linux, Windows, Mac, Android, iOS. Tudo fragmentado.  Aí o cara escolhe electron e baixa a metade do HD para desenvolvimento. O programa ocupa a outra metade. E o cara ainda tem coragem de usar uma camiseta escrita “Save the planet”. Pelos materiais utilizados, temos até coleta especial para lixo eletrônico.

Então eu releio o texto antigo do Carl Sanssenrath e penso…

To me this is all about Personal Computing, not Personal Enslaving. It is about being the masters of our own computers, not the reverse.

Para lutar contra a complexidade do desenvolvimento ele criou REBOL. É, uma linguagem muito avançada para a época. Tipo Smalltalk e seu ambiente gráfico com janelas, menus e mouse para a década de 80.  Acho que continua muito avançada para os dias de hoje (ou para os programadores de hoje). Sendo uma linguagem proprietária e como o desenvolvimento estava estagnado, Nenad Rakocevic, iniciou a criação de Red. Basicamente a mesma linguagem com algumas melhorias como abranger todo o espectro do desenvolvimento indo do baixo nível (C,…), passando por níveis mais elevados (Ruby, Python,…) e indo até a criação de DSLs (a própria linguagem possui diversas).

Eliminando a complexidade

  • Como abrange todos os níveis de programação e a sintaxe é a mesma, não é necessário que alguém programando em Ruby, Python, etc. tenha que aprender C para adicionar uma funcionalidade disponível apenas neste ambiente. Até o final do artigo veremos como.
  • Como permite, sem nada adicional, gerar executáveis para os diversos ambientes (Linux, Windows, Mac, etc..) e sem a necessidade de trocar de ambiente, evita que o programador tenha que ficar trocando de SO para ver se está tudo bem (basta executar o programa em outro SO para ver se está tudo bem).
  • Para a interface gráfica existe o dialeto VID que, com as mesmas linhas de programação gera GUI para os sistemas desejados sem a necessidade de baixar bibliotecas adicionais. Nada impede que se adicione a possibilidade de criar uma TUI usando o curses ou algo do tipo. A linguagem possui diversas DSLs escritas nela mesmo.
  • Tudo o que foi dito apenas baixando aproximadamente 1Mb e sem precisar instalar nada.

Mas vejamos alguns aspectos na prática.

Vamos começar escovando bits e trabalhar diretamente com o sistema operacional. Como uso Linux, deixarei os outros sistemas operacionais de fora pois nem tenho como testar. \mas nada impede de fazer o mesmo com os outros. Vejamos como ficaria para imprimir uma string usando uma chamada direta ao Linux.

Syscall

Red/System []

; definições globais
#define STDOUT     1
#define SYS-write  4
#define SYS-exit   1
#define null-byte  #"^@"

#syscall [
   write: SYS-write [
       fd      [integer!]
       buffer  [c-string!]
       count   [integer!]
       return: [integer!]
   ]
   quit: SYS-exit [
       status  [integer!]
   ]
]

; retorna o tamanho de uma string terminada por zero
length?: func [s [c-string!] return: [integer!] /local i][
    i: 0
    while [s/1 <> null-byte][
        s: s + 1
        i: i + 1
    ]
    return i
]

; imprime uma string
print: func[s [c-string!] return: [integer!]][
    write STDOUT s length? msg
]

; programa
msg: "Hello World^/"
result: print msg
if result > 0 [result: 0]
quit result   

Mesmo sem ter (poder utilizar) os cabeçalhos de C (.h), o programa possui apenas 40 LOC. Algumas definições globais, o protótipo das funções que serão utilizadas e o programa com a mesma sintaxe de Red. O nome da função para retornar o tamanho de uma string podia ser strlen ou size. Foi utilizado lenght? para seguir o que já existe na linguagem e não ter que aprender nada de novo. Para abstrair um pouco mais, foi criada a função print para ficar mais próximo do que já existe. Poderia colocar tudo que vem antes do programa em um arquivo separado e trocar por uma linha como #include syscall.h que nem daria para saber que não é um programa em Red. Procure por uma versão em C para ver como ficaria.

Ok. Agora vamos compilar, usando a chave –no-runtime para não incluir o runtime de red/system (na sequencia veremos mais sobre isso). O resultado é:

# guaracym @ guaracy-pc in ~/red/reds-examples
$ red -c --no-runtime syscall.reds

-=== Red Compiler 0.6.3 ===- 

Compiling /home/guaracym/red/reds-examples/syscall.reds ...

Target: Linux 

Compiling to native code...
...compilation time : 19 ms
...linking time     : 5 ms
...output file size : 624 bytes
...output file      : /home/guaracym/red/reds-examples/syscall 

Pois é. O resultado é um executável com abundantes 624 bytes. E que funciona perfeitamente.

# guaracym @ guaracy-pc in ~/red/reds-examples
$ ./syscall                       
Hello World

Apenas por curiosidade, vamos ver o código gerado.

# guaracym @ guaracy-pc in ~/red/reds-examples
$ objdump -d syscall

syscall:     formato de ficheiro elf32-i386


Desmontagem da secção .text:

0804809c <.text>:
804809c:       ff 35 38 91 04 08       pushl  0x8049138
80480a2:       e8 69 00 00 00          call   0x8048110
80480a7:       a3 4c 91 04 08          mov    %eax,0x804914c
80480ac:       a1 4c 91 04 08          mov    0x804914c,%eax
80480b1:       3d 00 00 00 00          cmp    $0x0,%eax
80480b6:       7e 0f                   jle    0x80480c7
80480b8:       b8 00 00 00 00          mov    $0x0,%eax
80480bd:       c7 05 4c 91 04 08 00    movl   $0x0,0x804914c
80480c4:       00 00 00 
80480c7:       ff 35 4c 91 04 08       pushl  0x804914c
80480cd:       5b                      pop    %ebx
80480ce:       b8 01 00 00 00          mov    $0x1,%eax
80480d3:       cd 80                   int    $0x80
80480d5:       55                      push   %ebp
80480d6:       89 e5                   mov    %esp,%ebp
80480d8:       6a 00                   push   $0x0
80480da:       6a 00                   push   $0x0
80480dc:       83 ec 08                sub    $0x8,%esp
80480df:       b8 00 00 00 00          mov    $0x0,%eax
80480e4:       c7 45 f0 00 00 00 00    movl   $0x0,-0x10(%ebp)
80480eb:       eb 0e                   jmp    0x80480fb
80480ed:       8b 45 08                mov    0x8(%ebp),%eax
80480f0:       40                      inc    %eax
80480f1:       89 45 08                mov    %eax,0x8(%ebp)
80480f4:       8b 45 f0                mov    -0x10(%ebp),%eax
80480f7:       40                      inc    %eax
80480f8:       89 45 f0                mov    %eax,-0x10(%ebp)
80480fb:       8b 75 08                mov    0x8(%ebp),%esi
80480fe:       8a 06                   mov    (%esi),%al
8048100:       3c 00                   cmp    $0x0,%al
8048102:       75 e9                   jne    0x80480ed
8048104:       8b 45 f0                mov    -0x10(%ebp),%eax
8048107:       e9 00 00 00 00          jmp    0x804810c
804810c:       c9                      leave  
804810d:       c2 04 00                ret    $0x4
8048110:       55                      push   %ebp
8048111:       89 e5                   mov    %esp,%ebp
8048113:       6a 00                   push   $0x0
8048115:       6a 00                   push   $0x0
8048117:       6a 01                   push   $0x1
8048119:       ff 75 08                pushl  0x8(%ebp)
804811c:       ff 35 38 91 04 08       pushl  0x8049138
8048122:       e8 ae ff ff ff          call   0x80480d5
8048127:       50                      push   %eax
8048128:       5a                      pop    %edx
8048129:       59                      pop    %ecx
804812a:       5b                      pop    %ebx
804812b:       b8 04 00 00 00          mov    $0x4,%eax
8048130:       cd 80                   int    $0x80
8048132:       c9                      leave  
8048133:       c2 04 00                ret    $0x4

Ok. Encerramos por aqui. Deverá ser utilizado apenas em aplicações específicas e por profissionais qualificados. Não é para o nosso bico. Mas existe a possibilidade.

O nosso nível seria (acho eu), algo em um nível um pouco mais elevado, com o mesmo potencial de C mas um pouco mais bacana. Vamos supor um programa para gerar números aleatórios chamando as funções de uma biblioteca do sistema (no caso do Linux a libc). Ficaria algo assim:

Red/System

Red/System []

#import [
    LIBC-file cdecl [
        rand: "rand" [
            return: [integer!]
        ]
        seed: "srand" [
            seed [integer!]
        ]
        clock: "clock" [
            return: [integer!]
        ]
    ]
]

#define MAX 20

loop 2 [
    seed 1
    print-line ["--> seed 1" ]
    loop 5 [
        print-line rand % MAX
    ]
    seed clock
    print-line ["--> clock" ]
    loop 5 [
        print-line rand % MAX
    ]
]

Para o Linux, LIBC-file é definido como  #define LIBC-file “glibc.so.6” (para o Windows é “msvcrt.dll”). Na definição dos protótipos, eu defini que seed no programa irá chamar srand da biblioteca. Assim é feita a compatibilidade entre os diversos SOs. O programa apenas demonstra que pode ser utilizado clock para inicializar a semente para os números aleatórios. Compilando as 34 LOC, temos:

guaracym @ guaracy-pc in ~/red/reds-examples
$ red -c reds.reds

-=== Red Compiler 0.6.3 ===- 

Compiling /home/guaracym/red/reds-examples/reds.reds ...

Target: Linux 

Compiling to native code...
...compilation time : 381 ms
...linking time     : 14 ms
...output file size : 7096 bytes
...output file      : /home/guaracym/red/reds-examples/reds 

O tamanho do executável ficou em 7096 bytes, o que pode ser considerado bem pequeno para os padrões atuais. O resultado da saída é:

# guaracym @ guaracy-pc in ~/red/reds-examples
$ ./reds          
--> seed 1
3
6
17
15
13
--> clock
18
4
2
3
8
--> seed 1
3
6
17
15
13
--> clock
13
19
12
5
11

Em penúltimo lugar mas não menos importante, temos a linguagem Red propriamente dita. Um programa para ler uma página da internet, extrair o título e imprimir ficaria assim:

Red

Red []

p: read https://example.com
parse p [thru <title> copy t to </title>]
print ["O título da página é: " t]

Como tudo que vem antes de Red [] é considerado comentário, poderíamos ter algo como #!/usr/env red, alterar o atributo do arquivo para executável (chmod +x) e chamar direto como um arquivo de script qualquer. Ou poderemos chamar com red <nome do arquivo>. Poderia passar despercebido mas temos outro detalhe interessante. Foi utilizado parse  que é uma DSL escrita em Red e substitui as  expressões regulares. Continuamos com a mesma sintaxe da linguagem (e mais umas facilidades). Digamos que a leitura da página também é interessante. Nem precisa de baterias.

O resultado é:

# guaracym @ guaracy-pc in ~/red/reds-examples [17:31:04] 
$ red red.red
O título da página é:  Example Domain

Para enviar para um computador que não possua Red instalado(?), podemos gerar um executável e enviar apenas ele para o destino.

# guaracym @ guaracy-pc in ~/red/reds-examples [17:31:14] 
$ red -c red.red

-=== Red Compiler 0.6.3 ===- 

Compiling /home/guaracym/red/reds-examples/red.red ...
...using libRedRT built on 25-Oct-2018/20:29:07
...compilation time : 34 ms

Target: Linux 

Compiling to native code...
...compilation time : 1487 ms
...linking time     : 91 ms
...output file size : 40760 bytes
...output file      : /home/guaracym/red/reds-examples/red 

Aqui vale algumas observações:

  1. O tamanho informado é para desenvolvimento (compilado com -c). Para enviar para outro computador deverá ser compilado com a chave -r (release) e o tamanho ficará em torno de 600Kb ou mais.
  2. Na primeira vez, o tempo para a compilação é maior para gerar uma biblioteca que será utilizada (quase o mesmo com a opção -r).
  3. É possível gerar com a chave -t Windows e enviar diretamente para ser executado em um computador com Windows.

O resultado é o mesmo do chamado pela linha de comando (e nem poderia ser diferente).

# guaracym @ guaracy-pc in ~/red/reds-examples
$ ./red
O título da página é:  Example Domain

Velocidade

Agora vamos juntar o que já foi visto até aqui. Supomos que desejamos um programa que calcule a sequência de Fibonacci de forma recursiva. Sabemos que não é a melhor forma mas suponhamos que e não existe outra. Utilizando uma linguagem que não seja compilada é impraticável para alguns valores mais elevados. Poderíamos utilizar uma biblioteca em uma linguagem compilada e mais rápida ou, podemos escrever em Red e compilar apenas a parte crítica. Ficaria assim:

Red []

;; routine indica a compilação Red/System
fib: routine [n [integer!] return: [integer!]][
    either n < 3
    [1]
    [(fib n - 1) + (fib n - 2)]
]

;; func rotina interpretada
fibt: func [n][
  either n < 3
     [1]
     [(fibt n - 1) + (fibt n - 2)]
]

print fib 40

Quando é encontrada a palavra routine, o interpretador sabe que necessita compilar aquela função. Então o programa não pode ser executado como script o que, na grande maioria dos casos, não deve ser problema nenhum. A maior diferença no programa está no fato de que na rotina compilada é necessário especifica o tipo dos parâmetros ao passo que na interpretada não é necessário. Mas nada impede de termos fibt: func [n [integer!]] onde o parâmetro também seria verificado na chamada e iria gerar um erro antes da execução se fosse chamado com uma string. É possível especificar mais de um tipo aceito pela rotina. Compilamos o programa e o resultado é:

# guaracym @ guaracy-pc in ~/red/reds-examples
$ time ./fib
102334155
./fib  0,83s user 0,00s system 99% cpu 0,834 total

Pareceu bom. Mas vamos testar com um programa em C para ver como fica.

#include<stdio.h>

int fib(int n);

int main() {
  printf("%d\n", fib(40));
}

int fib(int n) {
  if (n<3)
    return 1;
  else
    return ( fib(n-1) + fib(n-2) );
}
# guaracym @ guaracy-pc in ~/red/reds-examples
$ time ./fibc
102334155
./fibc  0,65s user 0,00s system 99% cpu 0,653 total

Ok. O programa em C é mais rápido. Não é desculpa mas Red ainda é uma linguagem alfa (nem RC é) e o código não é otimizado. Acho que o resultado pode ser considerado bom. Independente do estágio da linguagem, vamos comparar com Ruby (só para deixar claro, Red interpretado é mais lento que Ruby)

def fib(n):
    if n==1 or n==2:
        return 1
    return fib(n-1)+fib(n-2)

print (fib(40))
# guaracym @ guaracy-pc in ~/red/reds-examples
$ ruby -v
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux]

# guaracym @ guaracy-pc in ~/red/reds-examples
$ time ruby fib.rb
102334155
ruby fib.rb  18,45s user 0,01s system 99% cpu 18,460 total

A diferença no tempo de execução é significativa.

Então era isso. Apesar da abordagem muito superficial, acho que foi possível ter uma ideia da abrangência da linguagem e como tudo fica com uma forma parecida facilitando a vida do desenvolvedor. Não coloquei exemplo de VID pois a parte gráfica para o Linux ainda não está pronta. Basicamente um compilador multiplataforma, um interpretador e, tudo isso, em aproximadamente 1Mb. Nem é possível dizer que vem com as baterias. É mais uma usina.

Voltando ao início do artigo, é uma fragmentação que eu considero muito bem vinda e faço questão de compartilhar. Uma linguagem alfa também pode gerar alguns produtos alfa. Acho que o melhor exemplo é a carteira que está sendo desenvolvida, roda no Windows e Mac e tem só 300Kb sem dependências externas (quase surreal). Se você deseja alguns exemplos de scripts e programas feitos em Red, tem uma página com uma coleção de links. Você pode baixar Red para o seu SO. Só não baixe a versão estável (existe estável de alfa?). Vá até o final e baixe a última versão. Tem mais novidades do que erros.

 

Anúncios

Senhor dos anéis.

Introdução

O meu interesse mesmo para a minha próxima linguagem é Red mas, como o desenvolvimento é meio confuso e lento (é só uma constatação e não uma reclamação) e ainda faltam coisas que eu desejo, resolvi olhar uma outra linguagem que passou na minha frente. Ring. Achei que podia me divertir um pouco.

A linguagem

Em primeiro lugar, não faz nada a mais do que a sua ou minha linguagem favorita (com maior ou menor trabalho). O que eu achei interessante:

  • Pode gerar programas para CLI, GUI (usa o QT) e WEB com ambiente que facilita o desenvolvimento.
  • Gera executáveis. No caso mais simples, nada que um shebag mais chmod +x não resolva mas pode facilitar a distribuição em outros casos. Na realidade é apenas um empacotamento do script e não gera executável nativo.
  • Multiplataforma (Windows, Mac, Linux, Android).
  • Multiparadigma.
  • etc..

Sintaxe

É baseada em diversas linguagens (Lua, Python, Ruby, C, Basic, etc.) e é interpretada. Diversas construções aceitam mais de uma sintaxe, tipo:

for i=1 to 10
    put i
next

ou

for i=1 to 10 {
    put i
}

A definição de funções também é interessante. Se você não gosta de uma, utilize a outra. Uma função para somar e mostrar o resultado de dois número será chamada como:
soma(2, 4) e retorna 6. Pode ser definida de qualquer uma das seguintes formas:

// tipo 1 (endentada)
def soma a, b
    c = a + b
    put c

// tipo 2 (end no final)
def soma a, b
    c = a + b
    put c
end

// tipo 3 (entre chaves)
def soma a, b {
    c = a + b
    put c
}

// tipo 4 (inline?)
def soma a,b c =a + b put c

Obs.: Você poderá trocar def por func que tudo funcionará da mesma forma. Mas existem algumas regrinhas para a utilização.

Variáveis

Existem variáveis globais e locais e o conceito é extendido para os objetos (globais dentro do objeto e locais dentro dos métodos). Tipagem dinâmica e fraca.

As variáveis apontam para um valor e não referência, mesmo objetos e listas. Sendo assim, se a variável a contém uma lista e você usar a expressão b = a, b continuará com o valor inicial mesmo que você altere a.

Dados

Existem diversas funções para a verificação do tipo de dado (isstring(), isnumber(), isList(), islower(), etc.) que retornam verdadeiro (1) ou falso (0) e para a conversão de dados (number(), string(), hex(), etc.)

Bibliotecas e funções

Um bom número de funções estão disponíveis na linguagem e não necessittam de nenhum comando adicional. Funções para listas (find, sort, reverse, swap, etc.), strings (lower, upper, left, right, trim, lines, substr, etc.), data e hora (time, date, adddays, diffdays, etc.), matemáticas (sin, cos, log, ceil, power, sqrt, random, etc.), arquivos (read, write, fopen, fread, fexists, dir, etc.), sistema (system, currentdir, exefilename, version, etc.) entre outras.

Na forma mais simples, para uma biblioteca de funções basta escrever um programa em Ring e, em outro, utilizar Load para ter acesso a todas as funções. Como podem existir variáveis globais e ocorrer um conflito, pode-se usar Load package e todas as variáveis serão locais.

Existem também bibliotecas com interface para bibliotecas em C/C++ para finalidades diversas como banco de dados (odbclib, mysql, sqlite), internet e segurança (internet com md5, sha512, libcurl), arquivos compactados (ziplib), interface gráfica QT (guilib) entre outras.

Existe a biblioteca stdlib que possui diversas classes e funções para facilitar a programação. Alguns exemplos de funções:

times (3, func {put "Hello World!" + nl})
map (1:10, func x {return x*x})
filter (1:20, func x {if x<5 return true else return false ok})
split ("um,dois,três,quatro",",")
lineCount("teste.ring")
isPrime(7)
a = [[1,2,3],[4,5,6],[7,8,9]]
b = [[1,0,0],[0,1,1],[0,0,1]]
MatrixMulti(a,b)

Alguns exemplos de classes de stdlib são

String, List, Stack, Queue, HashTable, Tree, Math, System, etc.

Basicamente são criadas classes encapsulando as funções já existentes podendo adicionar algumas funções. Em String, por exemplo, existe o método toFile(arquivo) que irá gravar o conteúdo da string em um arquivo especificado. Outras como HashTable, apenas, facilitam o trabalho com estrutura que pode ser feitas com listas (veja o tópico lista abaixo). Já Queue e Tree, apesar de poderem ser feitas proceduralmente, facilitam trabalhando com objetos.

nota: Não sei se esqueceram, está em desenvolvimento ou não irão colocar mas algumas classes poderiam ter outros métodos adicionados. Entre outras coisinhas, acho que a classe List poderia ter Map e Filter. Vamos tentar resolver depois.

Operadores

Os operadores matemáticos normais de qualquer linguagem (adição, subtração, divisão e multiplicação) e mais (incremento ++ e decremento ), lógicos (and, or, xor, complemento, rotação de bits para direita e esquerda. Eles podem ser combinados (a+=2) para diminuir o trabalho de digitação. A exponenciação é feita por função pow(x,y).

Estruturas de controle

Para o controle condicional temos os normais sendo que alguns aceitam diversas sintaxes. Nos exemplos abaixo, se você deseja fechar o comando com a chave, deverá colocar a chave inicial. Você pode escolher uma das opções separadas pela barra.

if cond [ { ]
  comandos
but | elseif cond
  comandos
...
else
  comandos
end | ok | }
switch var [{]
on | case cond
  comandos
...
other | else
  comandos
off | end | }
while cond [{]
  comandos
end | }
for var=inicio to fim | range [step passo] [{]
  comandos
next | end | }
do
  comandos
again cond
try | [{]
  comandos
catch
  comandos
end | }

Para os diversos laços, tem o loop para voltar o início e o exit para sair do laço. Até aqui, sem novidades. Mas elas aceitam um número indicando o número de níveis que devem ser executadas em laços aninhados.

? "inicio"
for x in 1:10          // nível 2
    for y = 1 to 10    // nivel 1
        if x > 3 
            exit 2 
        ok
        if y = 4 loop 2 ok
        ? "(" + x + "," + y + ")"
    next
next
? "fim

resulta:

inicio
(1,1)
(1,2)
(1,3)
(2,1)
(2,2)
(2,3)
(3,1)
(3,2)
(3,3)
fim

Para finalizar, também funcionam dentro de funções. Por exemplo:

for x in 1:10
    ignore(x,5,7)
    see x + nl
next

def ignore x,y,z
    if (x < y) or (x > z)
        loop
    ok

irá mostrar apenas os números 5,6,7 (lembrei do setjump/longjump de C).

Listas

Este tópico entrou mais para que eu possa desabafar. Em Ring, são indexadas iniciando em 1. Li comentários de gente reclamando pois não iniciava em zero. Mais ou menos como discutir se é bolacha ou biscoito. Bom mesmo é o Pascal onde pode-se iniciar com qualquer valor. Se eu fosse trabalhar com dados entre os anos de 1980 e 2018, a melhor forma seria algo como eventos : array [1980..2018] of evento;. E não precisaria ficar fazendo continhas do tipo eventos[ano - 1980].

Mas já que toquei no assunto, listas podem armazenar valores variados e não estão restritas um um determinado tipo. Um caso especial das listas é a matriz tipo nx2 que pode ser utilizada como um hash. Se você definir uma lista como: aList = [["um",1], ["oito",8], ["quatro",4]]  poderá acessar os valores com aLista["um"] e alterá-los com aList["oito"]=9. Só lembrando que aList[1] retorna ["um",1]. Em Ring, o operador dois pontos pode ser utilizado  antes de um identificador para convertê-lo em literal então, ficaria mais fácil definir como aList = [:um = 1, :oito = 8, :quatro = 4] e acessar como aLista[:oito]=9.

Como listas podem ser passadas como argumentos para as funções, podemos utilizar o método acima para nomear os parâmetros sem se preocupar com a ordem dos mesmos. Por exemplo:

p = new point([:y = 1, :x = 23])
p.print()

Class Point 
  x y
  def init aList
    x = aList[:x]
    y = aList[:y]
  def print
    put "(" + x + "," + y + ")" 

Como resultado, o código acima mostrará (23,1).
Alterações nos elementos de uma lista podem ser feitos facilmente sem a necessidade de referenciar a lista e seu índice. Por exemplo:

lista = 1:6
for x in lista
  switch x
    on 2 x = "dois"
    on 3 x = "três"
    on 4 x = x * 2
    on 6 x = "seis"
  end
next
? lista

retorna

1
dois
três
8
5
seis

Objetos

A implementação de objetos em Ring até que ficou legal. Tem private, this, self, super entre outras coisinhas e, usando algumas coisas de metaprogramação e linguagem natural implementadas em Ring, ganham um charme especial. Por exemplo:

// início do programa
o1 = new Retangulo(8, 9)
o1.area()
o1.perimetro()
metodos(o1)

o2 = new Quadrado(7)
o2 {
    ? "{ início em o2"
    funcao()
    variavel
    area()
    perimetro()
    ? "} fim em o2"
}
metodos(o2)

sublinhado(nl+"Classes:")
? classes()

// funções do programa
def metodos o
    sublinhado(nl+"Métodos da classe "+classname(o))
    ?  methods(o1)

def sublinhado s
    ? s
    ? copy("-",len(s))

// classes do programa
Class Retangulo
    l1 l2
    def init l1, l2
        this.l1 = l1
        this.l2 = l2
        ? "Retangulo de lados: "+l1+" e "+l2
    def area
        a = l1 * l2
        ? "Área: " + a
    def perimetro()
        p = l1 * 2 + l2 * 2
        ? "Perímetro: " + p

Class Quadrado from Retangulo
    def init x
        l1 = x
        l2 = x
        ? "Retangulo de lado = "+x
    def perimetro
        ? "- Executando operações específicas antes."
        super.perimetro()
        ? "- Executando operações específicas depois."
    def braceStart
        ? "-------- 8< --------"
    def braceEnd
        ? "-------- >8 --------"
    def braceError
        ? cCatchError
    

No trecho acima, as funções methods e classes são da parte de metaprogramação da linguagem e retornam as classes definidas no programa e os métodos disponíveis em uma classe. As chaves estão na parte de propgramação orientada ao objeto indicando que tudo o que está entre elas faz referencia ao objeto logo antes.  As funções braceStart, braceEnd e braceError estão na parte de programação natural. As duas primeiras são chamadas no momento em que encontraram as respectivas chaves. A última é acionada quando é encontrado um erro. Pode ser interessante para tratamento de erros dentro da função, baseando-se no código de erro.

Retangulo de lados: 8 e 9
Área: 72
Perímetro: 34

Métodos da classe retangulo
-----------------------------
init
area
perimetro

Retangulo de lado = 7
-------- 8< --------   
{ início em o2   
Error (R3) : Calling Function without definition !: funcao   
Error (R24) : Using uninitialized variable : variavel   
Área: 49   
- Executando operações específicas antes.   
Perímetro: 28   
- Executando operações específicas depois.  
} fim em o2   
-------- >8 --------

Métodos da classe quadrado
----------------------------
init
area
perimetro

Classes:
---------
retangulo
quadrado

TDD

A linguagem não traz maiores facilidades para desenvolvimento de testes mas, por dois motivos, nem é necessário. Primeiro eu não erro então são supérfluos. Segundo, mesmo não sendo mais profissionais, são relativamente fáceis de acoplar ao desenvolvimento. Vejamos o problema da classe lista da biblioteca stdlib que eu gostaria que tivesse Map e Filter. Para não alterar a biblioteca do sistema e não conflitar com os metodos da classe, fui obrigado a chamá-los de @Map e @Filter (ficaria mais bonitinho se eu alterasse o nome das funções globais para $Map e o método poderia ficar como Map).

O primeiro passo seria olhar a classe List. Verifiquei que ela possui testes mas é algo mais visual. Apenas são mostrados os valores o que para mim não são testes. Apenas resultados. Utilizei assert para os testes ficarem mais rígidos. Também não tinha a comparação entre listas diretamente então foi criado o método Compare que irá retornar -1, 0 ou 1 se a primeira lista for menor, igual ou maior que a segunda (poderia retornar apenas true e false).  Criei o arquivo ListPlus.ring que ficou assim:

Load "stdlib.ring"

if IsMainSourceFile()
    listPlus_test()
ok

func listPlus_test()
    aList = new listPlus([1,2,3])
    assert(aList.Compare(aList.Value())=0)
    assert(aList.Compare([1,2,3]) = 0)
    assert(aList.Compare([1,2]) = 1)
    assert(aList.Compare([1,2,3,6]) = -1)
    assert(aList.Compare([1,2,1]) = 1)
    assert(aList.Compare([1,2,6]) = -1)
    assert(aList.String() = "[1, 2, 3]")
    assert(aList.@Map(func x { return x*x}).Compare([1,4,9]) =0)
    assert(aList.@Filter(func x {if x%2 = 0 return false ok return true}).Compare([1,3])=0)
    aList.@Permutation()
    aList.@Permutation()
    assert(aList.Compare([2,1,3])=0)
    ? "Passou nos testes"
end 

Class listPlus From List
    func @Filter aFunc
        return new listPlus (Filter(vValue, aFunc))
    func @Map aFunc
        return new listPlus (Map(vValue, aFunc))
    func @Permutation
        Permutation(vValue)
    func Compare aList
        v1 = len(vValue)
        v2 = len(aList)
        if v1 < v2
            return -1
        but v1 > v2
            return 1
        ok
        for i=1 to v1
            if vValue[i] = aList[i]
                loop
            ok
            if vValue[i] > aList[i]
                return 1
            else
                return -1
            ok
        next
        return 0
    func String
        s= "["
        for i=1 to len(vValue)
            s = s +vValue[i]+", "
        next
        return left(s,len(s)-2) + "]"

Se o arquivo for chamado com ring listPlus.ring, a função IsMainSourceFile retorna true e os testes serão executados. Se for via Load, serão ignorados. Bem, é para efeitos didáticos. Poderia ser melhor colocar os testes em poutro arquivo.

Ambiente de desenvolviemto

Pode ser utilizado qualquer editor de textos para  a criação dos programas. Existem extensões para o Notepad++, Geany, Atom, Sublime Text 2, Visual Studio e Emacs. Não tem para o Vim e para o Emacs é quase como se não tivesse. Existem alguns programa feitos em Ring como dois REPL (CLI e GUI) sendo os dois bem rudimentares. Porém existe o rnote que é escrito em Ring e é bem legal.

rnote

É um ambiente de desenvolvimento escrito em ring. Em vez de ficar escrevendo, melhor ir mostrando as funcionalidades.

rnote-eval

Na aba Source Code o usuário pode entrar com o código (novidade?). É bem básico e os maiores problemas que encontrei foram o de carregar apenas um arquivo por vez e não endentar automaticamente o código. Aqueles números de linhas grandes eu não gostei. Não sei se os problemas são inexperiência ou existem mesmo. Podemos executar o código e a saída será mostrada no painel da direita. No painel também é possível ver uma lista de funções e classes do programa e é possível clicar para posicionar o código no elemento desejado. Se como editor deixa a desejar, como REPL é bem bom.

Na aba Form Designer é possível montar a nossa janela com facilidade. Quem conhece Delphi/Lazarus e outros sabe que é bem prático. Após o termino do design, se salvarmos com o nome de teste, por exemplo, serão gerados os seguintes arquivos. O arquivo teste.rform, o testeView.ring e testeController.ring. Basta editar o testeController.ring para executar as tarefas desejadas.

A aba Web Browser parece mais para ver a documentação. Falta uma integração maior o com o código tipo pressionar F1 e abrir a documentação no item correspondente.

Exemplo besta

Basicamente o programa irá ler os arquivos de um determinado diretório, mostrar o nome e um cabeçalho (caracteres de controles e outros não imprimíveis serão mostrados como ponto) e deverá rodar no console, GUI e navegador.

Primeiramente vou definir um arquivo com funções comuns (acho que o ideal seria criar uma classe mas, para fins didáticos, ficamos com uma programação procedural mesmo).

CLI

GUI

WEB

Como foi desenvolvido em estágio precário de conhecimento, vou fazer algumas alterações nos fontes e depois eu disponibilizo (se alguém se interessar).

No mais …

Não testei em outros ambientes fora o Linux. Mas pareceu ser interessante e até útil. É relativamente nova e ainda precisa de mais testes. Tive um problema com Dir() que retorna uma lista com o nome do arquivo e o tipo (0=arquivo, 1=diretório). Quando chamado para um local diferente do diretório que o programa estava rodando, embaralhava o tipo do arquivo. Pode ser só no Linux, no meu Linux, ou em qualquer SO.

O resto funcionou como deveria funcionar. Quando tiver mais tempo, pretendo testar para o Android+QT. Fiquei mais tempo do que desejava testando a linguagem e agora terei que trabalhar no final de semana. LOL.

Referências

No próprio site da lnguagem http://ring-lang.net/.

Acho que, mais importante que este artigo, é ler o conteúdo da página inicial e a FAQ. Diversas informações interessantes sobre a linguagem, os paradigmas, etc..

 

Troquei o Emacs pelo Visual Studio no Linux

Ok, é temporário e é apenas para Red. Mas a história é a seguinte:

Atualmente existe a possibilidade de salientar a sintaxe e mais algumas coisinhas utilizando Red + Emacs. Porém, ainda existe uma certa dificuldade na integração entre os dois o que faz com que o emacs vire um simples (e bem simples) editor de textos como qualquer outro. Opções simples como enviar um bloco ou até mesmo todo o programa para ser interpretado não estão disponíveis.

A plataforma primária dos desenvolvedores de Red é o Windows e eles já fizeram um bom trabalho integrando Red com o Visual Studio, pensei que poderia usar em vez de perder tempo com editores que não uso. Seria possível ajustar o Kate ou algum editor que use o gtksourceview (não gosto do scintilla). Como resolvi testar o Manjaro Budgie para mudar um pouco o visual de tempos usando o KDE (posso dizer que estou gostando), resolvi instalar o Visual Studio.

Apesar de não estar bem familiarizado, é um editor bom, relativamente simples e possui uma boa integração com Red.

captura-de-tela-de-2017-01-18-23-54-29