Looplex Knowledge Base
Codificação com Lawtex
Codificação com Lawtex
Meu primeiro programa: "Hello Judge"
Abaixo, para quebrar um pouco o gelo, vamos codificar o nosso primeiro programa em Lawtex.
template[TEMP_HelloJudge] { metainfo { language = "pt_BR" } body { operations { print "Olá juíz!" } }}
O código acima não faz nada de especial. Toda a estrutura do código, a forma de escrever, as restrições de chaves e nomes dos comandos definem um vocabulário específico. Esse vocabulário que compõe a linguagem Lawtex é regido por regras sintáticas para a perfeita compreensão do algoritmo pela máquina. Não apresenta nenhuma ação ao usuário, isto é, não gera nenhum card, e ainda por cima imprime "Olá juiz!".
No caso, todo o template em Lawtex deve ter, no mínimo essa estrutura, ou seja, a diretiva "template" com um identificador iniciado por letra maiúscula entre colchetes, e um bloco de propriedades delimitados por chaves. Cada propriedade por sua vez, segue suas particularidades sintáticas, as quais serão melhor exploradas nos itens abaixo.
Definições de Sintaxe
Sintaxe é um aspecto necessário, porém menos importante na engenharia jurídica. A sintaxe corresponde ao formalismo da linguagem em que se escreve os códigos interpretados pela máquina. Cada linguagem de programação tem sua própria sintaxe: suas regras gramaticais, palavras reservadas, formatos de expressar declarações e operações, entre outras.
A importância da sintaxe é operacional, ou seja, você não consegue programar sem sintaxe, contudo a lógica independe da sintaxe. Na verdade, se este material for bem-sucedido, você aprenderá lógica de programação, aprenderá a formalizar e estruturar a lógica jurídica em declarações, condições, dependências, iterações, etc. Tais estruturas são clássicas em programação estruturada. Dominando esse raciocínio você pode fazê-lo em qualquer linguagem de programação existente, respeitadas as particularidades de cada uma.
Dentre outros critérios, a linguagem Lawtex foi escolhida como linguagem de codificação deste material, particularmente pelas facilidades em que se definem os templates e componentes de um template, visto que foi uma linguagem desenvolvida especialmente para profissionais do meio jurídico. Além disso, o feedback do código produzido é imediato, isto é, existe uma plataforma que responsivamente interpreta o código dinamicamente.
Talvez nem toda linguagem dispõe de propriedades como "help" ou "request", mas se você souber, por exemplo, o que é iterar um vetor de objetos, você poderá consultar a sintaxe necessária para fazê-lo em Lawtex nesse material ou na internet para qualquer outra linguagem. Depois de aprender a iterar sobre vetores de objetos com o foreach em Lawtex procure, por exemplo, "How to iterate in Python" no Google e compare a sintaxe com o Python. Você verá que a lógica transcende a implementação.
A tecnologia muda muito rápido, mas seus fundamentos não. Fazendo uma analogia com as línguas, praticamente todas as ideias que podem ser expressas em francês podem ser expressas em inglês, respeitadas algumas poucas particularidades. Tenha isso em mente.
Aprenda a sintaxe, mas sempre pressupondo que seu domínio vem com a prática e que a consulta é sempre possível. Mantenha seu foco na lógica que aprendeu no começo do material. Apesar da relatividade da importância da sintaxe, ela é imprescindível para programar. Lembre-se: a sintaxe é a "fórmula", o formato pelo qual pedimos para o computador executar uma tarefa. Quando redigimos no código a linha
print <dataDeAssinatura>,
estamos pedindo ao sistema que escreva no documento a data de assinatura preenchida ao usuário. Considere o trecho de código abaixo:
<valorTotal>.ask(),<numeroDeParcelas>.ask(),if (<numeroDeParcelas>.isNotEmpty() AND <valorTotal>.isNotEmpty()) { <valorDaParcela> = <valorTotal> / <numeroDeParcelas>, print "\p O pagamento será realizado em " & <numeroDeParcelas>, print "parcelas de R$" & <valorDaParcela> & ".\n\b"}
A tradução desse código para português seria:
"Pergunte ao usuário o valor total do produto","Pergunte o número de parcelas","Caso o usuário responder essas informações pedidas, ou seja, não forem mais vazias" "Calcule o valor da parcela, dividindo o valor total pelo número de parcelas", "Imprima um parágrafo com essa informação: primeiro discrimine o número de parcelas" "Descreva também os valores delas e por fim, feche o parágrafo e pule uma linha"
Agora releia as linhas de código e compare-as com a versão em português: você verá que elas parecem bem menos intimidadoras.
Dessa forma, a sintaxe é a língua falada pelo computador. O computador é menos sagaz com contexto e erros de digitação que seres humanos. Se o código não for absolutamente perfeito ele não será compreendido e o computador retornará um erro. Esses "erros de digitação" são chamados de erros de sintaxe. Você sempre cometerá erros de sintaxe. É comum na vida de todo programador e, portanto, de toda engenheira ou engenheiro jurídico. Por isso é interessante validar o código conforme você o redige para não ter que resolver todos os erros de uma só vez. Não se preocupe tanto com a depuração (busca minuciosa pelo erro) do código antes de subi-lo no sistema. O sistema informa quando há um erro (mas não informa erros de lógica jurídica).
Sugerimos uma ferramenta de desenvolvimento para codificação: o Sublime Text 3. É uma ferramenta leve e muito boa para edição de código. Basta fazer o download no site https://www.sublimetext.com para obter uma versão gratuita. Um bom recurso do Sublime é o uso de snippets. Eles são pequenos modelos que podem ser acionados no editor de texto que usamos para programar. Eles serão a fonte de consulta "abstrata" da sintaxe. Justamente porque existem os snippets optamos, por questões didáticas, procuramos usar exemplos concretos nesse material. Basta escrever "stru" que o editor vai sugerir "struct". Aperte enter e você terá automaticamente um exemplo da sintaxe com lacunas, só sendo necessário preencher o que realmente interessa.
Outra coisa importante é a indentação ou recuo. Nos exemplos de sintaxe você vai reparar que há sempre uma serie de tabulações, recuos inseridos antes de determinados trechos de código. Isso é para identificar em que contexto se inserem. Se estou declarando uma variável e abro chaves para declarar suas propriedades devo dar um "tab" antes de inserir cada uma delas. Isso porque as propriedades de uma variável estão em nível hierárquico ou de profundidade diferente das demais variáveis. Fica muito confuso deixar informações diferentes no mesmo recuo. Além disso, com o recuo correto você pode minimizar trechos internos de código para ter uma visão geral do código. Isso ficará mais claro com o tempo.
Os erros de sintaxe não são os únicos erros. Há erros mais complexos. Imagine que, por acidente, no lugar de dividir o valor total pelo número de parcelas você divida o valor total pela parte devedora. Parte devedora é uma variável que não é um número. Ela é um sujeito, com nome, CPF, endereço etc. O sistema só consegue fazer contas com números. Não faz sentido fazer contas com objetos complexos com diversas informações. Da mesma forma não é possível fazer uma conta com textos. Diante desse erro o sistema acusará uma incompatibilidade na operação, ou seja, você tentou realizar uma operação que não é adequada ao tipo de informação fornecida. Esses erros podem ser evitados, mas isso exige atenção e compreensão do que se está fazendo.
Outro erro comum nesse sentido é pedir uma informação não indexada a um vetor (ver Classes de operandos) O que isso quer dizer? Imagine que você tem um conjunto de partes garantidoras. Essa sequência de garantidores é expresso em Lawtex por um vetor. Como veremos a sintaxe dele é |garantidores|
. Imagine agora que eu escreva a linha
print |garantidores.estadoCivil|,
com o objetivo de imprimir o estado civil de cada garantidor numa cláusula de representações e garantias. Estado civil não é uma característica da sequência de garantidores, mas sim de cada um dos seus elementos. Cada garantidor tem seu próprio estado civil. Logo é preciso indexar, ou seja, identificar o estado civil de qual dos garantidores se quer. O sistema acusará um erro sem indexação. Nesse sentido se eu inserir a linha
print |garantidores{0}.estadoCivil|,
aí sim o sistema executará a ordem. No caso, ele vai buscar o primeiro garantidor do conjunto de garantidores e vai imprimir seu estado civil. Isso porque indexamos com a posição zero do vetor, que é a primeira posição em Lawtex.
Todos esses erros, desde os mais simples aos mais complexos nos levam ao que destacamos acima: A parte mais importante é a capacidade de fazer um raciocínio lógico, organizado e coerente.
A seguir vamos falar da topologia dos templates. Falamos em topologia pois o código em Lawtex é estruturado. Há um espaço certo para a inserção de cada tipo de código. Inicialmente isso pode parecer um formalismo excessivo. Por que razão eu sou obrigado a fazer declarações no início do código e só operá-las depois? Bom, há vários motivos para isso, mas o melhor deles é que quando terceiros forem ler seu código ficará mais fácil identificar quais informações são utilizadas e o que é feito com elas. Lembre-se que para efeitos de programação você mesmo no futuro é um terceiro. Depois de programar diversos templates dificilmente você vai se lembrar com exatidão de cada linha redigida. Então é preciso organização as informações para facilitar sua própria vida no futuro. Isso é verdade especialmente porque o código é um trabalho em constante evolução. Quase nunca você programa um template e nunca mais o edita. Novas teses podem surgir, erros podem aparecer, os usuários podem pedir novas funcionalidades. Nunca se sabe. Esteja, portanto, preparado:
- Faça o código da maneira mais organizada que você conseguir.
- Tente modularizar as lógicas (ex. criar um branch para cada argumento ou cláusula).
- Não misture assuntos diferentes (não calcule num ponto do código um valor que você só vai utilizar adiante).
- Dê nomes intuitivos para as variáveis (no lugar de chamar o número do processo de
<ndp>
use o alias<numeroDoProcesso>
). - Nunca abandone linhas de código antigas e desnecessárias no template.
- Não mantenha uma lógica mais complexa e confusa só porque você já começou ela assim.
Adotando algumas dessas boas práticas de programação, um futuro de sucesso o aguarda engenheiro! Subjacente a todas essas recomendações está a seguinte: coragem! Não deixe o trabalho mal feito por preguiça de refazê-lo. É mais fácil corrigir e abandonar uma lógica no começo do que quando ela já está nas mãos do usuário. Isso é extremamente difícil. Os autores desse material já se deixaram (e ainda se deixam) seduzir pela preguiça diversas vezes e a conta sempre tem de ser paga meses depois.
Provavelmente esses conselhos não fizeram sentido algum para você se essa é a primeira vez que você leu o material, mas acredite, você vai lembrar delas ao longo dos próximos itens. Caso não lembre, volte e releia essa introdução depois que terminar de estudar todas as funcionalidades do sistema.
Topologia e Templates
Todo template em Lawtex tem 5 blocos importantes: Metainfo, Head, Body, Foot e Extra.
template[TEMP_TemplateAlias] { metainfo { language = "pt_BR" } head { title = "Contrato de prestação de serviços médicos" declarations { +<numeroDoContrato> : String { name = "Número do contrato" request = "Insira o número do contrato" }, +<dataDoContrato> : Date { name = "Data" request = "Insira a data de celebração do contrato" } } operations { print bold("Número:") & " " & <numeroDoContrato>, print bold("Data:") & " " & <dataDoContrato> } } body { operations { print "Corpo do contrato..." } } foot { declarations { +<advogadoQueAssina> : String, +<oabDoAdvogado> : String, +<dataAssinatura> : Date } operations { print "Termos em que pede deferimento,\b\b", print <advogadoQueAssina> & "\b", print "OAB/SP no " & <oabDoAdvogado> } } extra { operations { print "Template concluído." } }}
Se atente na estruturação básica dos blocos do template. Na continuação segue uma descrição desses blocos.
Metainfo
O metainfo é o espaço em que você vai inserir as informações mais básicas e gerais do template, que valerão para todo o documento. Observe atentamente o código abaixo.
metainfo { language = "pt_BR" name = "Inicial de ação indenizatória contra empresa aérea" statement { audience = "Profissionais do direito especialistas em Direito do Consumidor" components = "O template usa os componentes globais da Looplex" functionalities = "O template avalia ilícitos cometidos pela empresa aérea." inputs = "Para preencher o template o usuário deve ter os dados do voo." overview = "O template redige uma ação indenizatória contra empresas aéreas" warnings { "Esse template não lida com acidentes aéreos", "Se houve morte ou feridos esse template não se aplica" } } description = "Esse template recebe dados sobre falhas na prestação de serviços" style = "escritoriofamoso.lawsty" tags { "Consumidor", "Overbooking", "Indenização", "Aérea", "Voo", "Dano", "Moral", "Hipossuficiência" } declarations { +<parteAutora> : struct[ParteAutora] { name = "Dados da parte autora" request = "Insira os dados abaixo" fields { +[nome] : String { name = "Nome" request = "Qual o nome da parte autora?" }, +[rg] : String { name = "RG" request = "Qual o RG da parte autora?" }, +[cpf] : String { name = "CPF" request = "Qual o CPF da parte autora?" } } } }}
Se existe alguma informação que você vai usar em diversas cláusulas ou diversos argumentos, declare essa informação no metainfo. Em geral a maior parte das informações será declarada no metainfo. Esse espaço não tem, como você poderá observar, parte operativa, pois as operações são feitas ao longo do texto. Esse é um espaço eminentemente declarativo e tudo que é declarado aqui é visível para o que está no resto do template.
Vamos passar ponto a ponto e entender o que cada trecho de código quer dizer. Em primeiro lugar abrimos o metainfo com chaves. Tudo que há dentro dele está delimitado por elas.
O language é o espaço para escolher a língua. Esse espaço existe, pois o sistema permite tradução de documentos ou sua redação em outras línguas, então ele poderia ser preenchido, por exemplo, por "en_US".
Em seguida, o name é o espaço para o nome do documento gerado. Esse nome é que aparecerá na lista de documentos disponíveis no sistema.
Em seguida temos o statement, como ele tem diversos elementos ele também os delimita com chaves. Esses elementos serão textos mostrados ao usuário que está pensando em usar o template. Na interface ele verá isso como um resumo que o informa se esse é o documento que ele quer gerar ou não. Em primeiro lugar inserimos o público alvo, estabelecendo quais são os conhecimentos necessários para preencher o questionário. Em seguida identificamos os componentes, ou seja, trechos de código já programados por outras pessoas e só utilizados neste template. Em funcionalidades descrevemos quais lógicas jurídicas o sistema será capaz de fazer nesse caso e em inputs descrevemos quais informações são necessárias para preencher o questionário, orientando o usuário na coleta do material que ele usará para responder as questões. Em overview descrevemos brevemente o que é o template e em warnings fazemos ressalvas ou alertas sobre o uso do template. Encerrados os elementos que estão no statement, seguimos com os elementos gerais.
No description fazemos a descrição, que se assemelha ao statement, mas tem uma destinação interna, de orientação de quem lê o código. Descreve brevemente o que faz e como funciona o código.
O style indica o estilo de letra, tamanho de fonte, utilização ou não de parágrafos recursivos etc que serão utilizados no template. Normalmente cada escritório ou empresa possui estilo diferente para cada template produzido.
As tags servem para orientar a busca no sistema. Quando alguém usar o mecanismo de busca com qualquer dessas palavras chave o sistema recomendará esse template. Ele também pode orientar a produção de dados agregados pelo sistema (ex. gerar um relatório da porcentagem de documentos gerados que tratam de Direito do Consumidor).
No declarations fazemos as declarações, como já adiantamos, definimos nossa "caixa de ferramentas", as informações que vamos usar nas lógicas e impressões. No exemplo declaramos um tipo complexo de informação que são os dados de um caso envolvendo empresa aérea. Logo em baixo declaramos que esse template terá uma variável desse tipo declarado. Nesse tipo declaramos em abstrato informações que todo caso envolvendo empresas aéreas deve ter.
No exemplo colocamos alguns campos demonstrativos. Primeiro perguntamos o nome da parte. Em seguida pedimos o RG. Ao final, pedimos o CPF da parte autora. É claro que em um template completo faríamos mais perguntas. Perguntaríamos dados do caso, como por exemplo, se a parte autora perdeu o voo, se o voo mudou de portão, se ela estava atrasada, se havia fila etc. O objetivo é sempre afunilar as possibilidades e tentar inferir o caso por respostas objetivas do usuário.
Veremos a lógica de perguntas e operações com mais atenção em outros tópicos. Por enquanto, a lição que deve ser tomada é que esse é o espaço para declaração de informações que serão utilizadas ao longo de todos os argumentos.
Head
O head é um espaço de declaração de cabeçalhos. Números de processos, títulos, logotipos de escritórios, endereçamentos e outras informações desse tipo, quando se restringem apenas ao cabeçalho, devem ser declaradas e operadas no head. Além disso aqui definimos um título do documento, que aparecerá em destaque no topo da primeira página:
head { title = "Instrumento particular de contrato de prestação de serviços médicos" declarations { +<numeroDoContrato> : String { name = "Número do contrato" request = "Insira o número do contrato" }, +<dataDoContrato> : Date { name = "Data" request = "Insira a data de celebração do contrato" } } operations { print bold("Número:") & " " & <numeroDoContrato>, print bold("Data:") & " " & <dataDoContrato> }}
Neste exemplo, além do título e o bloco de declarações, se observa a presença do bloco de operações (operations), onde as informações que foram declaradas são usadas nas lógicas e impressões. No exemplo estamos imprimindo o número e a data do contrato. Neste exemplo, duas variáveis foram declaradas, uma do tipo texto (String) e outra do tipo data (ver Classes de operandos).
Body
O body tem a exata mesma estrutura que o head, como é possível ver no snippet do template. Ele é o espaço dedicado à impressão do corpo do documento, o texto em si, com argumentos, cláusulas, tópicos, parágrafos etc. Também é nele que fazemos avaliações e lógicas necessárias para a realização dessa impressão. Exemplo:
body { declarations { +<devedor> : *Devedor, +<dataAtraso> : Date { name = "Início do atraso" }, +<valorDevido> : Currency { name = "Valor devido" } } operations { if (<devedor.genero> == "Masculino") { print "Prezado " } else { print "Prezada " }, print <devedor.nome> & ",\b\b", print "Verificamos em nosso sistema que você tem parcelas em atraso desde " & <dataAtraso> & ". ", print "Atualmente o valor total devido, acrescido de juros e multa, é de R$ " & <valorDevido> & ". Entre em contato conosco o mais rápido possível com a nossa equipe para apresentar os seus comprovantes de pagamento ou quitar as parcelas devidas." }}
Foot
O foot, mais uma vez, tem a mesma estrutura que o body e o head.
foot { declarations { +<advogado> : *Advogado } operations { print "Termos em que pede deferimento,\b\b", print sign(<advogado.nome>) & "\b", print "OAB/" & <advogado.oab.uf> & " no " & <advogado.oab.numero> }}
Todas as considerações feitas acima valem para ele. Em geral ele é o encerramento do documento:
Extra
O extra é um espaço exclusivamente operativo que se destina a realizar operações depois que o documento é utilizado. Sua principal função é ser o espaço de execução das funções de smart documents. Na data de redação deste material, ele ainda era pouco utilizado, mas é o espaço privilegiado para dar comandos que mandem e-mails, registrem andamento de processos no sistema de gestão do escritório etc. Não abordaremos nessa oportunidade.
Classes de operandos
Conforme observado nos exemplos de código anteriores, dentro das declarações constam unidades de armazenamento do valores. Tais unidades são os operandos.
Operandos são containers de informações, repositórios onde se acessa dados variáveis, sejam obtidos a partir de respostas do usuário, sejam obtidas por meio de cálculos e operações dentro do próprio algoritmo. Existem informações atômicas que são armazenadas em variáveis, assim como existem informações que não tem nenhum acesso externo ao sistema, ou seja, são constantes dentro do código. Existem operandos que representam coleções de dados, sejam elas homogêneas, como no caso de vetores ou variáveis do tipo lista, ou sejam elas heterogêneas, como os casos de objetos do tipo estrutura.
Variáveis e objetos
Utilizamos a variável para armazenar informações inseridas pelo usuário. Declaramos uma variável quando pretendemos imprimi-la ou utilizá-la em alguma lógica ou avaliação ao longo do código. Sempre que declaramos a variável precisamos informar ao sistema seu tipo, além de suas propriedades, já descritas acima. Nos itens acima vimos diversos exemplos de variáveis com suas propriedades. Aqui vamos falar um pouco mais de seus tipos.
Uma variável pode ser: (1) String, (2) Text, (3) Integer, (4) Real, (5) Currency, (6) List, (7) Boolean, (8) Date e (9) Time. Além desses tipos, chamados primitivos, a variável pode ser do tipo (10) Struct. Vamos passar por cada tipo com mais detalhes em tópicos seguintes, mas por enquanto saiba que (1) e (2) são campos livres para o usuário digitar o que quiser, (3), (4) e (5) são números, (6) é uma lista de Strings, como já descrevemos, (7) é qualquer pergunta cuja resposta é "sim" ou "não", (8) é uma data e (9) um horário. Como também já adiantamos (10) é um objeto, ou seja, um tipo customizado pelo usuário. Pessoa, endereço, processo judicial são exemplos de tipos, com várias informações a eles vinculadas. Uma variável do tipo pessoa gerará um card que pergunta CPF, nome, endereço, telefone, e-mail etc.
Quando uma variável possui o tipo Struct, suas propriedades serão aquelas definidas na Struct para todas as variáveis daquele tipo. Em particular, uma variável que possui um tipo Struct é chamada de Objeto. Note também que é frequente termos mais de uma variável com o mesmo tipo Struct. Podemos, por exemplo, ter uma variável chamada <locador>
e outra chamada <locatario>
ambas do tipo pessoa. Por fim, é preciso atenção na redação do código para usarmos a sintaxe correta.
As variáveis são delimitadas pelos sinais <
e >
. Muito parecidos com as variáveis são os campos. Como já sugerimos os campos são, de certa forma, as variáveis associadas a um objeto. As variáveis e os objetos são informações avulsas e os campos, delimitados pelos colchetes [
e ]
são informações declaradas dentro de uma Struct, como veremos adiante. Da mesma forma, é preciso atenção para não confundirmos os delimitadores de variáveis com os delimitadores de vetores, que são cercados pela barra |
. Seguem dois exemplos de variáveis de diferentes tipos:
declarations { +<locador> : *Sujeito, +<temGarantia> : Boolean}
Vector
Já sugerimos o que é o vetor ao descrevermos suas propriedades. O vetor é melhor entendido como um conjunto de variáveis de mesmo tipo. Quando usamos uma variável para pedir ao usuário o autor de uma ação ele só poderá inserir os dados do autor uma vez. Quando usamos um vetor permitimos que o usuário repita o procedimento de inserção dos dados de um autor quantas vezes julgar necessário, para o número de autores que desejar.
É frequente o uso de vetores em documentos jurídicos. Podemos ter um número indefinido de partes, de vendedores, de autores, de réus, de intervenientes anuentes etc. Daí a necessidade de representá-los como vetor. Como já indicamos, o vetor deve ser um conjunto de elementos do mesmo tipo, logo devemos definir o tipo do vetor. Vejamos os exemplos a seguir:
declarations { +|autores| : Vector[*Autor], &|telefones| : Vector[String]}
Lembre-se que é possível criar vetores tanto de maneira avulsa quanto como um campo de um objeto. Podemos declarar um vetor de autores e cada um desses autores, dentre os campos que os definem, ter um vetor de telefones. Isso muitas vezes provoca confusão, mas é um aspecto muito utilizado do código. Assim como cada um dos autores de um conjunto de autor tem o próprio nome e o próprio CPF ou CNPJ, cada um deles pode ter seu próprio conjunto de endereços, de telefones, de e-mails etc.
Como já adiantamos na introdução da parte sobre sintaxe, o uso de vetores deve ser indexado ou iterado. Se eu desejo, por exemplo, imprimir o nome de cada um dos autores eu tenho que proceder da seguinte forma:
operations { foreach(<autor> IN |autores|) where (separator = "%f1, %s2, %p2, bem como %l2.") { print { [version = <autor.nome> & ", portador do documento (RG) no " & <autor.rg>], [version = <autor.nome> & ", RG no " & <autor.rg>] } }}
Basicamente, essas linhas de código falam para o sistema tomar a sequência de autores e, para cada um deles, imprimir seus respectivos nomes, separados por vírgulas entre o primeiro e o penúltimo elemento, por um "e" entre o penúltimo e o último, finalizando com um ponto final. Explicando o que significa o argumento separator dentro da cláusula where, lê-se %f
como first, %s
como second, %p
como penult e %l
como last. O número ao lado dessas letras diz qual versão do print deve-se imprimir. Assim sendo, a primeira vez irá imprimir ..., portador do documento (RG) no ...
, enquanto da segunda vez em diante imprimirá somente ..., RG no ...
.
No caso de vetor ainda se admite acesso a um elemento específico (acesso indexado). É possível fazer o seguinte:
operations { print |autor{0}.nome|}
Ou seja, pedir que o computador imprima o nome do primeiro autor do conjunto de autores, ou seja, a posição zero do vetor. Note que as posições dos vetores sempre se iniciam em 0 (zero).
Faremos mais três comentários adicionais sobre o vetor. Em primeiro lugar, podemos saber quantas posições tem o vetor usando o comando size. Esse recurso é muito útil quando queremos saber, por exemplo, se há só uma parte ré ou mais de uma delas. Para tanto basta fazermos |reus|.size()
. Podemos usar esse valor para fazer avaliações ou até imprimi-lo.
Outro recurso interessante é que podemos pedir para que o usuário escolha um dos objetos complexos que ele inseriu anteriormente. Ao invés do usuário se deparar com todos os dados de todos os objetos ele vai se deparar com uma lista resumida, em que pode escolher qual deles quer. Veremos mais detalhes dessa funcionalidade, chamada select quando discutirmos estruturas. Por enquanto, imagine que você tem um vetor de advogados num template e quer que o usuário escolha quais deles receberão as intimações:
operations { |advogados|.select(|advogadosIntimados|, "Quais advogados foram intimados?", 0, 1)}
Note que dentro dos parênteses, além do vetor que recebe os elementos selecionados dos primeiros e da pergunta feita, há mais outros dois parâmetros ao final, separados por virgulas. Trata-se dos limites, o lower (no mínimo) e o upper (no máximo). O primeiro indica quantos elementos no mínimo o usuário deve escolher. O segundo indica quantos elementos no máximo o usuário deve escolher.
Por fim, é possível filtrar o vetor, acessando um subconjunto dele que satisfaz determinadas características. É o caso quando queremos acessar apenas as partes casadas, para depois fazer uma cláusula de representações:
operations { |partesCasadas| = |partes|.filter ( [this.tipoDeSujeito] == "Pessoa natural" AND [this.estadoCivil] == "Casado" )}
Veja que o vetor de pessoas casadas recebe uma versão filtrada do vetor de partes. O critério do filtro é pegar qualquer elemento (indicado genericamente pelo this
) que seja pessoa natural e que seja casado.
Constant
A constante se assemelha à variável em vários aspectos. Ela também tem um tipo, também pode ser operada e impressa como a variável, mas é utilizada em geral para informações que não são preenchidas pelo usuário e se mantém constantes ao longo do código. Ela é cercada pela cerquilha #
, conforme mostraremos a seguir. A constante também tem como propriedades: tipo, nome e ajuda. Vejamos a sintaxe da constante:
declarations { -#tipoDeContrato# : String { name = "Tipo de contrato" value = "Contrato de Locação" }}
Observe que a definição da constante apresenta uma propriedade value recebendo um valor (pelo símbolo de igual), dentro de chaves colocadas imediatamente após o tipo String. Esse bloco delimitado por chaves é a área de definição de propriedades do operando. Independentemente do operando, em Lawtex eles têm suas propriedades particulares.
Uma diferença importante entre a constante e os demais operandos, é que no lugar do default ela tem uma outra propriedade chamada value. Na prática seu funcionamento é o mesmo. Os dois são definições prévias da informação pelo engenheiro ou pela engenheira. A diferença é que o default é um padrão, uma predefinição do valor para agilizar o preenchimento pelo usuário que usa frequentemente o padrão. O value, embora também seja uma informação predefinida, não se destina a ser substituído pelo usuário. Ele é efetivamente o valor que o engenheiro quer que essa constante tenha. Um uso possível da constante ocorre no uso de componentes globais já programados.
Suponha que você esteja programando um contrato e que esse contrato usará a funcionalidade de cláusulas de garantia. Os tipos de garantias acessíveis ao usuário dependem do tipo de contrato. Alguns contratos, como o de locação, não admitem certos tipos de garantia (art. 37 da Lei de Locações). Dessa forma, é interessante que todo contrato tenha uma constante que armazene o tipo de contrato. Assim, o componente de garantias, quando utilizado, saberá o tipo do contrato e ajustará automaticamente as garantias possíveis. Isso também pode ser feito com uma variável oculta, mas o código fica mais claro se isso for feito por constante.
Propriedades dos operandos
Nesse ponto do texto nosso foco será quais são essas propriedades, sua utilidade e como expressá-las. Ao final deste item descreveremos onde e como essas propriedades aparecem em cada um dos tipos de operandos.
Mandatory
Iniciaremos com a propriedade mandatory. Essa propriedade tem uma "responsabilidade" dupla no código Lawtex. Ela governa: (a) a obrigatoriedade de preenchimento de um campo ou informação pela usuário e (b) a visibilidade do campo ou informação pelo usuário. Essa propriedade comporta três opções:
- visível e obrigatório, representada pela sintaxe
+
; - visível, mas opcional, representada pela sintaxe
&
e, por fim; - invisível, representada pela sintaxe
−
.
Vejamos exemplos das três opções. Suponha que queiramos perguntar ao usuário, por meio de uma variável, o número do processo para fazer o endereçamento de uma contestação. Essa informação é indispensável nesse contexto, de forma que os jovens engenheiros jurídicos devem obrigar o usuário a preenchê-la. Logo, essa variável seria declarada da seguinte forma:
declarations { +<numeroDoProcesso> : String}
Há duas observações necessárias aqui. Em primeiro lugar, as propriedades são definidas apenas na declaração. Na operação, elas serão dadas, ou seja, o sistema já saberá que aquele operando tem as propriedades declaradas, de forma que não é necessário repeti-las. É possível, contudo, alterá-las por meio de operações, como demonstraremos nos tópicos seguintes. A segunda é que a propriedade mandatory fica sempre a esquerda do alias do operando declarado (o +
do exemplo acima), sendo o lado direito reservado ao tipo do operando, o que também discutiremos adiante.
Seguiremos com outro exemplo. Suponha, agora, que vamos pedir o endereço de alguém. Alguns endereços têm complemento (ex. conjunto A, sala 2, 5o andar, apartamento 12 etc.), mas outros não. Assim, se exigirmos que o usuário insira a informação vamos gerar um problema para quem mora em uma casa fora de um condomínio! A informação deve, portanto, ser opcional:
declarations { &<complemento> : String}
Nesse caso o usuário poderá ou não inserir o dado, ficando a seu critério salvar sem resposta o campo opcional.
Por fim, suponha que queiramos calcular o valor total de um contrato. Esse contrato tem parcelas e todas parcelas têm o mesmo valor:
declarations { +<numeroDeParcelas> : Integer { name = "Número de parcelas" }, +<valorDaParcela> : Currency { name = "Valor da parcela (em R$)" }}
É fácil ver que seria redundante perguntar ao usuário qual é o valor total do contrato. Se sabemos que as parcelas têm sempre o mesmo valor e se conhecemos o valor e o número de parcelas, basta multiplicar as variáveis para obter o total. Logo, ainda que precisemos da informação "valor total", nós calcularemos nós mesmos, sem que seja necessário perguntar ao usuário. Logo, essa informação será oculta e permanecerá oculta até que o próprio sistema faça a conta e a responda, sem nenhuma interferência do usuário:
declarations { +<numeroDeParcelas> : Integer, +<valorDaParcela> : Currency, -<valorTotalDoContrato> : Currency}
Como veremos em tópicos futuros, podemos realizar operações matemáticas e inserir seus resultados dentro de variáveis, ocultas ou não, por meio de operações como a seguinte:
operations { <valorTotalDoContrato> = <numeroDeParcelas> * <valorDaParcela>}
Não se preocupe com as operações. Dedicaremos uma parte desse texto apenas para elas. Por enquanto, queremos apenas que você entenda qual a situação que demanda um operando visível e obrigatório, visível e opcional e invisível.
Name
A propriedade name também se aplica a todos os operandos. Ela se destina, sobretudo, a orientar o usuário sobre qual é a informação ou pergunta presente em um campo ou variável a ser preenchida. Assim, sempre que o sistema for se referir a uma pergunta específica, ele vai usar o nome da pergunta, e não a pergunta na íntegra. Isso ficará mais claro com exemplos.
Na interface há um sumário com uma lista das informações que serão preenchidas. Imagine um template que pede ao usuário o valor da multa por atraso no pagamento. A pergunta desse campo é "Insira, em porcentagem, o valor da multa pelo atraso no pagamento". Como vimos, a pergunta tem que ser o mais clara o possível para prevenir erros do usuário. Ocorre que, justamente por isso, ela é longa demais para que seja referência no sumário. Logo, no sumário, a variável que receberá essa informação será identificada pelo nome "Multa por atraso". Assim, o usuário identifica do que trata aquela variável e visualiza a pergunta completa após selecioná-la. O nome da variável também é utilizado na revisão.
O card de revisão, como já adiantamos, é um resumo de todas as informações que foram preenchidas. Assim como no sumário, não faz sentido colocar a pergunta completa numa lista sucinta das informações que já foram preenchidas. Logo, na tabela do card de revisão o usuário verá, na coluna da esquerda, o nome dos campos e variáveis que preencheu e, no lado direito, a informação inserida. Seguiremos agora com a sintaxe necessária para definir a propriedade name.
Note que no exemplo a seguir inclui o tipo da variável, a propriedade estudada no item anterior e a próxima propriedade que veremos:
declarations { +<numeroDeParcelas> : Integer { name = "Número de parcelas" request = "Insira o número de parcelas do financiamento" }}
Assim, ao executar esse código, o sistema gerará uma variável para ser preenchida pelo usuário. No sumário e no card de revisão ela será identificada pelo nome "Número de parcelas". Ao clicar em "Número de parcelas" o usuário irá, em seguida, para a tela de preenchimento da variável, que pede que o usuário "Insira o número de parcelas do financiamento" e oferece o espaço para que ele o faça. Como veremos adiante, ao declararmos a variável com essa propriedade ela se manterá com esse nome independentemente de como usarmos a informação armazenada pela variável ao longo do documento. Lembre-se, contudo, como também já adiantamos, que é possível alterar essa propriedade ao longo da parte operativa. Descreveremos a sintaxe dessa modificação nos tópicos a seguir. Por ora seguiremos discutindo em mais detalhes o request a seguir.
Um último apontamento necessário é que o name é diferente do alias. Para compreender a diferença basta fazer uma analogia com seus documentos no computador: name é o título deles quando você os abre com um editor de texto, ao passo que alias é a extensão do arquivo, ou seja, seu nome do ponto de vista do sistema. O nome é sempre utilizado na interface e com o usuário. Já o alias é o nome que aparecerá no sistema.
Request
Como já indicamos, o request é o texto mostrado ao usuário logo acima do campo em que ele insere uma informação. Seu papel é descrever da melhor forma possível para o usuário o que queremos dele, ou seja, que informação ele deve inserir. Ele deve ser curto e de fácil entendimento. Retomando o que descrevemos nas reflexões teóricas sobre engenharia jurídica, a interpretação que o usuário faz do request é diferente da interpretação que os juristas costumam fazer da lei. Na interpretação do request o usuário deve, de fato, entender a intenção do engenheiro ou da engenheira que programou o template. A melhor forma de garantir que essa seja a interpretação do usuário é pedir uma informação fática e simples.
Devemos justamente evitar perguntas jurídicas e abstratas, pois o exercício da engenharia é deixar a abstração e a complexidade do Direito para o sistema, restando para o usuário atividades simples. No lugar de perguntarmos, por exemplo, "a multa é abusiva?" podemos perguntar diretamente "qual é o valor da multa" e contrastarmos esse valor com o total da obrigação ou com algum parâmetro que nós temos para que o sistema determine sozinho se a multa é ou não abusiva. O usuário não deve nem saber que o sistema está fazendo essa avaliação quando ele insere a informação objetiva pedida, qual seja, o valor da multa. Um exemplo da sintaxe do request foi dado no item anterior.
Atomic
Para explicar essa propriedade vamos retomar o exemplo do estado civil. Isso porque atomic é uma propriedade exclusivamente aplicável às listas. Dedicaremos um item apenas às variáveis e campos do tipo lista, mas faremos uma pequena introdução aqui para facilitar a compreensão da propriedade atomic. Quando definimos uma variável como tipo lista também definimos diversos textos como suas opções. Assim, limitamos a ação do usuário à escolha de um desses textos. Há dois tipos de listas: as atômicas e as não-atômicas.
A propriedade aqui discutida irá definir o tipo da lista. A lista atômica é aquela em que a escolha de uma das alternativas exclui a escolha das demais. Quando pedimos ao usuário gênero para fazermos as concordâncias ele não pode escolher masculino e feminino ao mesmo tempo. Configuramos a lista de gêneros para ser atômica para que ele escolha uma só opção. Por outro lado, quando perguntamos ao usuário o que foi pedido numa petição inicial e oferecemos uma lista dos pedidos possíveis o usuário poderá escolher, simultaneamente, quantos quiser. Vejamos exemplos da sintaxe da propriedade:
declarations { +<statusDivida> : List ("Em atraso", "Em dia", "Quitada") { name = "Status da dívida" request = "Selecione o status do pagamento das parcelas da dívida" atomic = true }, +<pedidos> : List ("Remoção do cadastro de inadimplentes", "Anulação do contrato", "Outro") { name = "Pedidos" request = "Pedidos da parte autora" atomic = false }}
Veja que o pagamento da dívida não pode estar atrasado e em dia ao mesmo tempo. Da mesma forma, uma dívida ou está integralmente quitada ou o pagamento das parcelas até o momento está em dia. Assim, não podemos permitir que o usuário escolha mais de uma opção, então a propriedade atomic é igual a true, ou seja, ele deve informar se o pagamento está em atraso, se ele está em dia, mas ainda faltam pagamentos a ser realizados ou se toda a dívida já foi quitada. Já nos pedidos o mesmo não ocorre, a lista não tem de ser atômica, pois é possível que na inicial a parte autora peça uma das três opções, duas delas ou todas. Lembre-se que a parte pode pedir anulação do contrato e, subsidiariamente, a revisão do valor das parcelas.
Outro ponto interessante é que em diversas iniciais há pedidos contraditórios. Logo, como veremos adiante, devemos permitir que usuário consiga informar ao sistema as contradições e fraquezas do argumento da outra parte. O usuário não precisa entender essas fraquezas. O próprio sistema estará programado para reconhece-las a partir das informações do usuário.
Upper
Para explicar a propriedade upper, assim como fizemos para outras propriedades, precisamos apresentar o contexto em que ela surge. O principal uso do upper é com vetores. Vetores, como já sugerimos, são conjuntos. Eles podem ser conjuntos de qualquer coisa. Podemos ter um vetor de textos, em que o usuário digita cada atividade do objeto social de uma empresa e até um vetor de objetos, em que o usuário insere os endereços de suas filiais.
Essa propriedade coloca um limite máximo de elementos que usuário pode inserir no conjunto. Isso porque, em geral, quando declaramos um vetor, o usuário pode inserir quantos elementos ele quiser. Um contrato pode, a princípio, ter quantas partes o usuário desejar. Ocorre que em alguns contextos podemos querer que usuário possa inserir um número de elementos que quiser, respeitado determinado limite. Imagine que queremos que o usuário insira os sócios de uma sociedade limitada, mas queremos impedir que ele insira mais de dez sócios. A sintaxe seria a seguinte:
declarations { +|socios| : Vector[*Socio] { name = "Sócios" request = "Insira os dados de cada sócio" upper = 10 }}
Não se incomode com os elementos que você ainda não conhece dessa sintaxe. Eles são os elementos de um vetor de objetos. No caso, o usuário poderá inserir diversos sócios, sendo os campos definidos a parte numa struct de sócio. Veremos isso com mais detalhes adiante. Aqui o que importa é que quando o usuário visualizar esse vetor ele não verá nenhum elemento no vetor. Apenas um botão com o ícone ‘+’. Selecionando esse ícone ele acrescentará um elemento. Ele poderá fazer isso até o máximo de dez elementos.
A propriedade também pode ser utilizada com listas não-atômicas para limitar o número de opções da lista que o usuário pode escolher simultaneamente. Assim, a lista pode ter, por exemplo, cinco opções, mas o usuário pode ser constrangido a escolher no máximo três delas.
Lower
O lower tem a mesma função do upper mas para impor um limite mínimo de elementos do vetor. Esse limite costuma ser mais utilizado que o upper. É frequente obrigarmos o usuário a colocar pelo menos uma parte autora, no mínimo duas testemunhas, pelo menos uma garantia etc. Ele também pode impor que usuário escolha, por exemplo, pelo menos duas opções de uma lista de cinco opções. Vamos usar o exemplo das testemunhas:
declarations { +|testemunhas| : Vector[*Testemunha] { name = "Testemunhas" request = "Insira os dados das testemunhas que assinarão o contrato" lower = 2 }}
Default
O default é uma propriedade muito utilizada e importante. Fundamentalmente o default se destina a predefinir uma resposta padrão para tornar o preenchimento mais rápido para a maior parte dos usuários. Um exemplo é quando pedimos o endereço da parte em algum contrato ou petição. Como o sistema é brasileiro e se destina primordialmente a automatizar documentos conforme o Direito Brasileiro, é de se esperar que a maioria das partes inseridas no sistema sejam brasileiras, residentes e domiciliadas no território nacional. Assim, como a grande maioria dos usuários irá escolher "Brasil" como país no momento de preencher o endereço, é razoável deixar o Brasil como resposta padrão predefinida, poupando a maior parte dos usuários de preencher esse campo, sem impedir que os poucos casos de pessoas sediadas ou domiciliadas em outros países sejam cobertos.
Para além disso, como discutiremos com mais detalhes no item de objetos ou estruturas, o default tem um papel importante para evitar erros de renderização. Esse ponto será crucial quando tratarmos de polimorfismo. Para os fins desse tópico é suficiente ter em mente que muitas vezes não podemos deixar um campo vazio. Como vimos, as estruturas ou objetos têm campos. Esses campos podem surgir dinamicamente. O sistema escolhe pedir ao usuário o CPF ou o CNPJ de uma parte dependendo de uma resposta anterior do usuário no sentido de que essa parte é pessoa física ou não. Como já dissemos, o sistema tem uma ordem de execução das ordens que damos a ele. Se no momento que ordenamos, por exemplo, que o sistema imprima o CPF da pessoa e o sistema ainda não sabe se a pessoa é física ou não, ele vai acusar um erro. É preciso garantir ao sistema que a informação utilizada efetivamente existe. Um dos instrumentos para garantir a existência de uma informação no início da execução, ou seja, antes do usuário responder, é colocar um default na pergunta condicionante. Isso porque enquanto a condicionante for vazia o sistema não sabe avaliar qual das opções ele deve escolher. Vejamos um exemplo:
declarations { +<sujeito> : Struct { fields { +[tipoDeSujeito] : List ("Pessoa natural", "Pessoa jurídica", "Ente não personificado") { name = "Tipo de sujeito" request = "A parte autora é..." atomic = true default = "Pessoa jurídica" }, if ([tipoDeSujeito] == "Pessoa natural") { +[numeroReceita] : String { name = "CPF" request = "Insira o número de CPF da parte autora" } } else { +[numeroReceita] : String { name = "CNPJ" request = "Insira o número de CNPJ da parte autora" } } } }}
Como podemos ver o default garante que pelo menos um dos tipos de número perante a Receita Federal existe. Do contrário, no início da execução, antes do usuário preencher o documento, nenhum dos dois campos existiria e a ordem de impressão resultaria num erro. Logo que fosse iniciado, o sistema acusaria um erro.
Além disso, o default também facilita, pois o usuário não tem que mudar nada se a parte for pessoa jurídica. Por fim, adiantando um pouco do que será discutido sobre polimorfismo, como existem dois campos com o mesmo nome, eles devem estar em condições excludentes. Não é possível existir dois campos com o mesmo nome. Se você declarar, você deve garantir que quando um existir o outro não existirá.
Help
O help complementa o request. Lembre-se que quando tratamos do request dissemos que ele deve ser curto, pois de fato as perguntas devem ser objetivas e, para que a tela não fique poluída, há pouco espaço para elas. Não é desejável que tenham mais de 150 caracteres. Se faltar uma descrição mais específica ou uma explicação melhor da dinâmica de resposta do campo, é possível acrescentar uma ajuda. A ajuda pode ter um texto maior e ser bem detalhada. Ela é representada por uma pequena interrogação e pode ser aplicável a um card inteiro ou a um campo de uma estrutura. Assim como as outras propriedades, o help é definido nas declarações, mas pode ser alterado com operações ou com polimorfismos para se adequar ao contexto em que o usuário se insere. Vamos usar um exemplo muito frequente de contencioso:
declarations { +<parteAutora> : Struct { fields { +[temProcuracao] : Boolean { name = "Procuração ad judicia " request = "A parte autora juntou procuração?" help = "Verifique, nos autos, se há procuração da parte autora" }, if ([temProcuracao] == true) { +[procuracaoEhAdequada] : Boolean { name = "Adequação da procuração" request = "A procuração é adequada? Veja o help para mais detalhes" help = "Nesse campo você deve responder ‘não’ se a procuração não é adequada. Considere adequada a procuração se a) ela foi assinada pela parte autora e b) se o advogado que assinou a peça é o que recebeu os poderes ou se houve substabelecimento daquele para o que assina" } } } }}
Veja que nesse exemplo o sistema só pergunta se a procuração é adequada se houver procuração e que com a pergunta há uma explicação de como identificar se a procuração é adequada.
Máscaras
As máscaras são restrições, limites impostos ao usuário no exercício da liberdade de preencher algum campo aberto. É comum utilizá-las quando a informação que você deseja tem um formato específico. Um exemplo claro é o número CNJ para processos. Embora o usuário possa digitar o número, sabemos de antemão o número de dígitos e quantos pontos e traços deve ter o número que ele vai digitar. O mesmo vale para CPF e CNPJ. Assim, diminuímos, por exemplo, a chance de o usuário esquecer um dos caracteres e aumentamos a padronização, dado que o sistema mesmo vai inserir os caracteres fixos necessários. Vejamos um exemplo:
declarations { +<numeroDoProcesso> : String where ("\d\d\d\d\d\d\d-\d\d.\d\d\d\d.\d.\d\d.\d\d\d\d") { name = "Número do processo" request = "Insira o número do processo" }}
A indicação de máscara vem após a diretiva where
. Com essa sintaxe o sistema só permitirá que o usuário insira dígitos (representados pelo \d
). Além disso ele colocará um traço após 7 dígitos, um ponto após os próximos dois dígitos e assim por diante. As máscaras só se aplicam às Strings, então mesmo que chamemos o identificador do CNJ de "número", do ponto de vista ele é, na verdade, um texto.
Os símbolos de máscara são:
\d
Representa somente os dígitos: [ 0-9 ]
\a
Representa somente as letras minúsculas: [ a-z ]
\A
Representa somente as letras maiúsculas: [ A-Z ]
\@
Representa os alfa-numéricos, isto é, dígitos e letras maiúsculas e minúsculas: [ 0-9a-zA-Z ]
\_
Representa espaço em branco, tabulação ou quebra de linha: [ \s ]
\.
Representa o universo de todos caracteres: [ · ]
\c
Representa qualquer caractere, exceto letras maiúsculas: [ ^A-Z ]
\C
Representa qualquer caractere, exceto letras minúsculas: [ ^a-z ]
\r
Indica que a ocorrência do símbolo ou caractere anterior é recursiva, podendo haver nenhuma ou mais ocorrências.
\o
Indica que a ocorrência do símbolo ou caractere anterior é opcional.
Declarações e Tipos
Todo operando está associado a um tipo específico, que restringe o que o usuário deve responder. Por exemplo, se um operando for numérico, o mesmo não pode admitir caracteres alfa numéricos. Ou ainda, se o operando for do tipo data, o usuário deve ter em mãos uma componente específica para seleção de datas. Deste modo, explicaremos cada um dos tipos existentes em Lawtex.
String
Como já adiantamos String é um tipo primitivo. Campos, variáveis e vetores desse tipo são textos livres, que o usuário pode preencher como quiser. Há algumas funcionalidades voltadas para textos, como veremos adiante. Podemos colocar textos em letra maiúscula, em letra minúscula, em negrito, itálico etc. Também denominamos Strings os blocos de texto fora de qualquer operando. Você já os viu entre aspas nos exemplos dados acima. O tratamento é o mesmo para ambas. Podemos concatená-las, pular linhas, abrir e fechar parágrafos. A seguir daremos um exemplo de declaração, outro de operação e outro de texto impresso com Strings. A declaração poderia ser assim:
+<tipoDeContrato> : String { name = "Tipo de contrato" request = "Insira o tipo de contrato" default = "Contrato de compra e venda"}
A operação poderia ser assim:
print "\p A assinatura do " & uppercase(<tipoDeContrato>) & " está condicionada à entrega da documentação descrita no anexo II.\n\b"
Após o preenchimento, o sistema poderia imprimir o seguinte:
A assinatura do CONTRATO DE COMPRA E VENDA está condicionada à entrega da documentação descrita no anexo II.
A linha da operação ordena, basicamente, que o sistema comece um parágrafo imprimindo uma String concatenada com outra digitada pelo usuário na variável de tipo de contrato, fazendo todas as letras maiúsculas, e concatenando, por fim, com o resto do texto, antes de encerrar o parágrafo e pular uma linha. Note que toda vez que um parágrafo é aberto com \p
ele precisa ser fechado com \n
. Ao abrirmos um parágrafo e não fecharmos o próximo será considerado um subitem do anterior. Se o primeiro parágrafo, não fechado, for o item “1.” o próximo será “1.1”. Se escrevermos o próximo também sem fechá-lo o seguinte será “1.1.1” e assim por diante. É preciso ter isso em mente pois isso pode gerar muita confusão. Se, ao trabalhar com Strings, você esquecer de fechar um dos parágrafos os demais vão ficar errados. Identificar um parágrafo errado em um template grande, com diversas Strings, pode ser um desafio desagradável. Por fim, o \b
é o comando para pular para a próxima linha sem necessariamente delimitar o parágrafo.
O uppercase()
é um dos chamados tubes de texto, os quais veremos com mais detalhes depois. Por enquanto você deve saber declarar operandos do tipo String, imprimir e concatenar Strings, bem como organizar os parágrafos em que são impressas.
Text
Text é um tipo primitivo com propriedades e comportamento idênticos aos de Strings. Assim, tudo que foi dito acima também é aplicável aqui. A única diferença é a visualização na interface. Uma variável do tipo String apresenta ao usuário um espaço pequeno, de uma linha, para que o usuário digite, já o text permite que o usuário digite e visualize um bloco inteiro de texto.
Em geral usamos String para pequenos textos com nomes, identificação de bens, complementos de endereços etc. Utilizamos text para ressalvas, cláusulas, argumentos ou quaisquer blocos inteiros de texto que o usuário queira inserir. É preciso ter em mente que ao permitirmos que o usuário insira grandes trechos de texto não temos como garantir a qualidade e a coerência do que ele insere. Podemos, por exemplo, garantir que um contrato que programamos sempre faça a concordância adequada ao número de partes (ex. as credoras ou a credora). Já o usuário que digita uma cláusula pode incorrer em alguma inconsistência ou erro. Por isso, evitamos usar muitos campos abertos, sobretudo do tipo text, e quando usamos construímos requests e helps extremamente precisos e informativos.
Boolean
O tipo Boolean é um tipo primitivo que se usa quando queremos guardar valores de “sim” e “não”. Um operador Booleano que admite como valores true e false, isto é, verdadeiro e falso, respectivamente. Normalmente, variáveis e campos desse tipo são usados em expressões condicionais, conforme veremos na Seção Operações e Comandos de operações básicas. A declaração poderia ser assim:
declarations { +<querQuitarImovel> : Boolean { name = "Deseja quitar o imóvel?" request = "Deseja quitar o imóvel?" default = false }}
É conveniente utilizar identificar variáveis e campos do tipo Boolean com palavras iniciadas com verbos. Uma operação poderia ser assim:
operations { <querQuitarImovel> = <estaPagandoPeloImovel> AND <querPagarTodasAsParcelasDoImovel>}
Assim como visto na álgebra Booleana, é possível realizar operações lógicas e armazená-las em variáveis ou campos do tipo Boolean. Os conectivos são escritos em caixa alta: AND (conjunção), OR (disjunção), XOR (ou exclusivo) e NOT (negação).
Date e Time
O tipo Date e o tipo Time são tipos primitivos que servem para expressar datas e horários. Sendo assim, podem ser declaradas por variáveis, campos e vetores. É importante não tratar esses tipos como se fossem Strings simplesmente pois é muito comum se realizar operações com variáveis e campos destes tipos.
Há algumas funcionalidades específicas para datas e horários tais como: tomar o dia de hoje, saber se uma data (ou horário) ocorre antes ou depois de outra data (ou horário), escrever datas por extenso, extrair numericamente os anos, meses, dias, horas, minutos e segundos, etc. A seguir daremos um exemplo de declaração de data e horário, e outro de operação básicas envolvendo ambos os operandos declarados. A declaração poderia ser assim:
declarations { +<dataDeAssinaturaDoContrato> : Date { name = "Data de assinatura do contrato" request = "Qual a data de assinatura do presente instrumento?" }, +<horaDaAssinaturaDoContrato> : Date { name = "Horário de assinatura do contrato" request = "Qual o horário em que o presente instrumento foi firmado?" }}
Uma operação comum em Lawtex é descrita a seguir:
operations { <dataDeAssinaturaDoContrato> = today(), <horaDaAssinaturaDoContrato> = now()}
Já adiantando um pouco cenas do próximo capítulo, o código antecedente realiza uma atribuição usando duas funções específicas: today() que retorna o dia de hoje; e now() que retorna o horário local. A atribuição e as funções serão explicadas na Seção Operações e Comandos.
Integer
Integer é um o tipo primitivo correspondente aos números inteiros. É comum usarmos esse tipo para variáveis discretas, ou seja, com informações quantitativas sobre “coisas indivisíveis”. É o caso do número de filhos, número de sócios, número de garantias etc. Ninguém diria que um contrato tem meia garantia ou que uma empresa tem dois terços de sócios. Os inteiros são números, então podemos fazer cálculos com eles nas operações. Vejamos um exemplo desse tipo:
declarations { +<numeroDeNotificacoes> : Integer { name = "Notificações" request = "Insira quantas vezes o devedor foi notificado extrajudicialmente" default = 1 }}
Real
O Real, por sua vez, é o tipo dos números reais, das variáveis contínuas, ou seja, das “coisas divisíveis”. Todos os comentários aplicáveis aos inteiros valem para os reais. Podemos, por exemplo, utilizar esse tipo para perguntar quantidades como litros, metros ou a porcentagem da empresa detida por um acionista:
declarations { +<porcentagemMajoritario> : Real { name = "Participação majoritária" request = "Indique, em porcentagem, a participação do acionista majoritário" default = 51 }}
Currency
Currency está para Real assim como Text está para String. A diferença entre está apenas na visualização pelo usuário, mas as propriedades e o tratamento são os mesmos. Currency, ao contrário do Real, aparecerá para o usuário com pontos separando o milhar. No Real o único separador exibido é o de decimal com a vírgula. Essa exibição do Currency é a exibição mais comum para quantias de dinheiro, daí o nome Currency. Nada impede, contudo, que você use Currency para qualquer outro número que você queria que seja exibido com o separador de milhar. Vejamos um exemplo de Currency:
declarations { +<valorNominal> : Currency { name = "Valor nominal" request = "Insira o valor nominal unitário de uma debênture " default = 1000.0 }}
É possível definir limites numéricos para os operandos do tipo Integer, Real e Currency. Para isso, é suficiente expressar o intervalo como argumento (uma String) da diretiva where, conforme é mostrado no exemplo a seguir.
declarations { +<valorPercentual> : Real where ("[0.0;100.0]"), +<numeroDeParcelas> : Integer where ("[1;48]"), +<valorDaMulta> : Currency where ("[100.0;...]")}
Nos dois primeiros exemplos temos restrição nas duas extremidades do intervalo. Já no segundo exemplo, temos apenas um limite inferior. Os três pontos indicam o infinito.
List
Já introduzimos o funcionamento da List quando discutimos a propriedade atomic. Usamos a lista para restringir a resposta do usuário à escolha dentre elementos predeterminados. Há casos em que temos que fazer isso por uma limitação do escopo. Se estivermos programando uma contestação em ação trabalhista é razoável criarmos listas de pedidos. No que tange à jornada de trabalho, por exemplo, a parte reclamante só pode discutir horas extras, intervalos, adicional noturno, horas de sobreaviso, trabalho em domingos e feriados ou a supressão de descanso semanal remunerado. Criando uma lista desses pedidos temos controle de quais argumentos devemos imprimir. Se deixarmos o usuário digitar, ele pode digitar os pedidos de infinitas maneiras, o que dificulta a avaliação. A lógica fica mais precisa e estruturada se reduzimos os pedidos a uma lista definida, a partir da qual o usuário pode estruturar as alegações que existem na inicial e nós podemos programar respostas para cada situação. Vejamos o exemplo a seguir:
declarations { +<pedidosInit> : List("Danos materiais","Danos morais","Tutela de urgência") { name = "Pedidos" request = "Indique o que a parte autora pediu" atomic = false }, +<pedidosDef> : List("Danos materiais", "Danos morais", "Tutela de urgência") { name = "Pedidos" request = "Indique os pedidos deferidos" atomic = false }}operations { <pedidosInicial>.ask(), <pedidosDeferidos>.options = <pedidosInicial>, <pedidosDeferidos>.ask()}
Vamos entender o que essas linhas de código fizeram. Em primeiro lugar, vemos que no plano das declarações há duas variáveis com as mesmas opções. A priori tudo que pode ser pedido a um juiz pode ser deferido por um juiz. O que é passível de deferimento deve responder dinamicamente ao que foi pedido. Então, na parte operativa, vemos que perguntamos ao usuário os pedidos da inicial. Em seguida mudamos a propriedade options. Da variável de pedidos deferidos. Agora serão mostradas como opções passíveis de serem escolhidas apenas aquelas que foram selecionadas na pergunta anterior. Agora que restringimos as opções da segunda variável ela está pronta para ser perguntada ao usuário. Ao realizar esse procedimento é importante lembrar que uma lista nunca pode ter suas opções ampliadas, só restritas. Assim, se você declarou, digamos, cinco opções para uma lista, ao longo da execução o máximo que você poderá fazer é restringir essas opções a um subconjunto delas.
Por fim, vale adiantar um outro aspecto da operação com listas não atômicas. Como as listas não atômicas podem ser respondidas com mais de uma opção, não avaliamos se elas são iguais a algo, mas sim se esse algo pertence ao conjunto de respostas selecionadas. Imagine que o usuário elencou dentre os pedidos horas extras, descanso semanal remunerado e adicional noturno. Nesse caso a avaliação
<pedidosJornada> == "Horas extras"
não faria sentido, pois estamos perguntando se uma lista com vários elementos é igual a uma String. Se a lista fosse atômica e só pudesse ter uma opção, teríamos, na prática, sempre uma única String como resultado, de forma que a avaliação faria sentido. Nesse caso, contudo, a avaliação correta é
"Horas extras" IN <pedidosJornada>;
ou seja, não avaliamos se os pedidos são iguais a horas extras, mas sim se a opção horas extras está entre os pedidos.
Uma ressalva tem de ser feita com relação à lista não atômica. Ela tem características de conjunto, mas ela não é tratada pelo sistema efetivamente como um conjunto. Não é possível, por exemplo, aplicar filtros ou o foreach sobre ela. Para usar as ferramentas aplicáveis aos conjuntos, no caso os vetores, é preciso atribuir a lista a um vetor de Strings. Isso vai ficar mais claro ao fim do material, depois que você já conhecer essas funcionalidades.
As opções de uma lista podem ser alteradas ao longo da execução. Suponha que, na automação de uma sentença, eu pergunte ao usuário quais foram os pedidos da parte autora e, em seguida, pergunte quais pedidos serão deferidos. A segunda lista é uma versão restrita da primeira, pois o juiz não vai deferir um pedido que não foi feito. Portanto as opções de uma lista são uma propriedade, assim como name, request, atomic etc.
As listas têm um comportamento muito semelhante ao das Strings em seu uso, mas restringir o preenchimento do usuário à escolha de uma opção predefinida tem suas particularidades. A primeira delas é a definição da propriedade atomic já descrita. Essa propriedade determina se o usuário deve escolher apenas uma das opções, ou seja, se elas são excludentes entre si, ou se ele pode escolher mais de uma simultaneamente. Como vimos atomic verdadeiro implica que as opções são excludentes e falso que mais de uma opção pode ser escolhida ao mesmo tempo.
Imagine que definimos uma lista com 100 elementos. É muito inconveniente dar Ctrl ‘C’ e Ctrl ‘V’ nessas listas a todo momento. O custo de manutenção é altíssimo. Existe a possibilidade de evitar esse despautério e declarar uma lista uma só vez, e após isso, instanciá-la em outras variáveis, isto é, criar variáveis com esse tipo. As declarações de lista seguem a seguinte sintaxe:
declarations { *list[EstadosDoSudeste] { name = "Lista de estados do Sudeste" options = ("Espírito Santo", "Minas Gerais", "Rio de Janeiro", "São Paulo") type = "String" }}
Sempre o identificador de um tipo, e no caso específico, a lista, deve iniciar com letra maiúscula. Em Lawtex, além de criar listas como a descrita no exemplo anterior, você pode ir além disso e criar listas associativas estruturadas. Com essas listas, conseguimos relacionar informações entre si, conforme o exemplo abaixo.
declarations { *list[EstadosSiglasCapitaisDoSudeste] { name = "Lista associativa de estados do Sudeste" fields = {"nome", "sigla", "capital"} options = ( {"Espírito Santo", "ES", "Vitória"}, {"Minas Gerais", "MG", "Belo Horizonte"}, {"Rio de Janeiro", "RJ", "Rio de Janeiro"}, {"São Paulo", "SP", "São Paulo"} ) type = "String" }}
O exemplo acima, nos permite associar respostas do usuário a outras informações implícitas, como por exemplo, a partir do estado respondido obter a capital e a sigla do mesmo. O asterisco indica que a lista declarado pode ser usada por mais de uma variável (lista global). O conceito de global e local será melhor explorado em seções posteriores.
Document
O tipo Document permite o desenvolvimento de templates muito interessantes. Esse tipo indica que essa variável não é uma informação ou um objeto com um bloco de informações, mas sim um template inteiro. Esse tipo permite que um template gere documentos que buscam informações de outros documentos gerados a partir de um template especificado. É o caso dos instrumentos de garantia que usam informações do contrato principal, do recurso que usa informações da contestação, do aditamento que usa as informações de um contrato e assim por diante. Quando o usuário for responder essa pergunta, ele verá uma lista dos documentos (feitos a partir do template especificado) disponíveis para sua conta. Basta escolher um deles para que suas informações sejam aproveitadas. A única ressalva que deve ser feita é que quando você for utilizar a informação de outro documento no seu template você deve indicar em que ponto do documento está a informação desejada, pois ele terá sua própria topologia e organização. Vejamos um exemplo:
declarations { +<contestacao> : Document[TEMP_Contestacao] { name = "Contestação" request = "Selecione a contestação protocolada neste processo" }}operations { if ("Perícia" IN <contestacao$Metainfo:pedidos.producaoProvas>) { print "\pNa contestação requereu-se a produção de perícia sobre a assinatura discutida no presente caso.\n\b", if ( <contestacao$Metainfo:pericia.pediuPericiaGrafoTecnica> ) { print "\pFoi pedido perícia grafotécnica entre a assinatura da procuração do contrato discutido.\n\b" } }}
No exemplo declaramos, em primeiro lugar, que uma das variáveis utilizadas vem de outro documento, no caso, uma contestação. No exemplo da Linha 8, podemos observar uma comparação envolvendo o campo producaoProvas pertence ao objeto pedidos, que por sua vez foi declarado no metainfo do template TEMP_Contestacao. Analogamente, na Linha 11, encontramos o campo pediuPericiaGrafoTecnica do objeto pericia, também declarado no metainfo do template TEMP_Contestacao. Em ambos os casos, avaliamos os valores dos campos de outro documento, para imprimirmos os textos desejados. Essa funcionalidade poupa o usuário de ler a contestação e inserir novamente informações já inseridas no passado.
Existe ainda uma possibilidade de nem exibir na tela o seletor do documento. Para isso, use a propriedade default para nomear o documento que contém as informações a serem consultadas e declare a variável invisível. Assim, conseguimos criar um ecossistema onde documentos se comunicam e se gerenciam de certa forma.
Struct
Estruturas, ou Structs, são tipos de dados que agrupam outros subtipos. São basicamente containers de operandos. Objetos, campos e vetores podem ser do tipo Struct. Essa informação é extremamente relevante e frequentemente esquecida. Struct é um tipo. Uma estrutura é vista também como uma classe de objetos. Quando você declara uma Struct você define certas informações e propriedades que definem uma categoria, sendo que o que vai na parte operativa do código não é a própria estrutura mais variáveis daquela categoria. As estruturas globais, por exemplo, são tipos reaproveitáveis, já um objeto é um exemplo concreto.
Retomando um exemplo já utilizado, posso declarar uma Struct de pessoa e depois declarar diversos objetos desse tipo. Posso, por exemplo, criar uma Struct chamada de Parte, e a partir dela, instanciar uma parte credora, uma parte devedora e uma série de partes garantidoras, ou seja, duas variáveis e um vetor de pessoas. No caso de uma Struct, a instanciação é a concretização de seu objeto no ato da declaração. Esse exemplo é codificado abaixo:
declarations { *struct[Parte] { name = "Dados da parte" request = "Insira os dados da parte" help = "São os sujeitos do contrato." fields { +[nome] : String { name = "Nome da parte" }, +[genero] : List ("Masculino", "Feminino") { name = "Gênero" request = "Indique o gênero da parte" atomic = true } } }, +<parteCredora> : *Parte, +<parteDevedora> : *Parte, +|partesTerceiras| : Vector[*Parte]}
Vejamos agora o que cada trecho de código significa. Em primeiro lugar, indicamos que estamos declarando uma Struct. Sempre que inserirmos o asterisco () antes da declaração estamos indicando que ela é global. É frequente criar uma estrutura e usá-la só uma vez. Neste caso podemos declará-la inline, ou seja, após os dois pontos da declaração do objeto. Já as estruturas usadas múltiplas vezes são as estruturas globais, que ficam acessíveis a qualquer engenheiro ou engenheira sem a necessidade de declará-las. Em geral, as componentes globais podem ser usados por outros engenheiros sem cópia de código. Discutiremos mais da mecânica dos componentes globais em tópicos seguintes, mas por enquanto basta saber que podemos usar componentes globais que foram declarados em outros documentos ou arquivos. Declarando a estrutura com esse asterisco nos permitimos que outros programadores usem essa estrutura ou que nós mesmos utilizemos em mais de um contexto, mais de uma vez. No código acima, o asterisco define que a estrutura Parte é global.
Em seguida, vemos as propriedades da estrutura. Cada campo da estrutura tem suas propriedades, mas há propriedades que se aplicam ao card todo. É o caso do name, request e help. Em geral essas propriedades são instruções gerais sobre o objeto que será caracterizado. Os fields (ou campos), por sua vez, indicam os operandos que caracterizam uma estrutura.
Relembrando: Não confunda Struct com objeto. O usuário preenche o card de um objeto do tipo Struct, não a Struct em si. Daí a diferença na sintaxe de campos de estruturas e de variáveis.
Outro aspecto importante das estruturas é que elas permitem um uso atípico de operações em uma parte eminentemente declarativa. As estruturas permitem a existência de ifstructs e de loaders. Pode parecer confuso, mas há diversas boas razões para tanto. Embora as operações nas estruturas sejam muito semelhantes às operações no código seu uso tem um objetivo diferente. Em geral de ifstructs e loaders são operações relacionadas à exibição de perguntas para o usuário, ao passo que as operações da parte tipicamente operativa do código são efetivamente as lógicas e impressões que compõem o documento. Um exemplo claro disso é o CPF e o CNPJ. Se o usuário respondeu que o sujeito em questão é uma pessoa física, faz sentido pedirmos o campo CPF e nunca mostrarmos o campo CNPJ. Mostrar todas as informações possíveis e deixar o usuário decidir qual é aplicável e qual não é só dificultaria o uso. Logo, a Struct permite operações para adequarmos as perguntas às respostas anteriormente dadas pelo usuário. Veremos isso com mais detalhes no tópico dedicado exclusivamente às operações dentro de Structs. Seguimos com outro exemplo de estrutura:
declarations { +<emprestimoCCB> : struct[EmprestimoCCB] { name = "Dados do empréstimo" request = "Insira os dados do empréstimo" help = "Esse card só vale para empréstimos feitos por meio de CCB" id = "R$ " & [valorFinanciado] fields { +[numeroDeParcelas] : Integer { name = "Número de parcelas" request = "Indique o número de parcelas" }, +[numeroCedula] : String { name = "NúmerovalorFinanciado da CCB" request = "Insira o número da CCB" }, +[valorFinanciado] : Currency { name = "Valor financiado (em R$)" request = "Insira o valor financiado em reais" }, +[valorParcela] : Currency { name = "Valor das parcelas" request = "Insira o valor das parcelas" }, +[dataEmissao] : Date { name = "Data da emissão" request = "Insira a data de emissão da CCB" }, +[localEmissao] : *Logradouro, +[status] : List("Em atraso","Em dia","Quitado") { name = "Status" request = "Indique o status do pagamento" atomic = true } } }}
No exemplo anterior, temos uma definição de estrutura inline cujas três propriedades da Struct (name, request e help) estão pedindo que o usuário insira os dados do empréstimo, deixando claro que essa estrutura se aplica apenas a empréstimos feitos por meio de uma cédula de crédito bancário (“CCB”). Em seguida, vemos algo que ainda não foi discutido, o id. Ele é o identificador da estrutura. Essa identificação tem duas funções. A primeira é permitir que possamos imprimir o objeto. Se digitamos
print <emprestimoCCB>
seria de se esperar que o sistema não soubesse o que imprimir, pois <emprestimoCCB>
não é uma informação, mas um complexo de informações, com número de parcelas, calores, datas, locais, entre outras informações. Se definimos, contudo, um id o sistema aceitará essa lógica imprimindo a informação que definimos como identificador daquele objeto. Se definimos que o id da Struct *EmprestimoCCB
é seu <valorFinanciado>
, a impressão de <emprestimoCCB>
será o valor financiado acompanhado por R$.
Além disso, o identificador viabiliza a funcionalidade select. Essa funcionalidade permite criar listas em que o usuário escolhe um ou mais elementos de um vetor. Veja que essa é uma lista atípica porque ela não é a escolha de uma ou mais opções de texto previamente definidas. Na lista do select o usuário pode se deparar com os elementos do vetor que ele mesmo preencheu. Um exemplo disso é quando pedimos para o usuário inserir os advogados que assinam a peça e, depois, que escolha um deles para receber notificações. Nesse ponto o id é essencial pois é ele que vai identificar cada elemento na lista. Como o usuário vai escolher um elemento de um conjunto de objetos complexos, se apresentássemos a lista com nome, OAB, endereço etc. de cada um a lista não seria agradável. Assim, quando o sistema tem que transformar um conjunto de objetos complexos em uma lista, ele o faz listando pelo identificador. Assim, se na estrutura de advogados definimos como identificador o nome, o usuário verá uma lista com o nome dos advogados que ele pode escolher. Voltando ao exemplo acima, colocamos como identificador o valor financiado. O pressuposto é que o usuário identifica cada empréstimo pelo seu valor, mas outro candidato forte a identificador seria o número da CCB. Essa é uma questão de usabilidade que depende do conhecimento de quem usará o template.
Em seguida nos deparamos com os fields, ou seja, o conjunto de informações que caracterizam objetos complexos dessa categoria. No caso, as informações associadas a um empréstimo feito por meio de CCB. Não vamos tratar de cada campo pois você já deve estar acostumado com a sintaxe da maior parte deles. Vamos discutir aquilo que ainda não foi estudado nesse material. Veja que o campo local de emissão é do tipo Struct. Pode parecer surpreendente imaginar uma Struct dentro de Struct, mas na verdade é bem comum. O criador do Lawtex, aliás, sempre ressalta que o sistema permite Struct dentro de Struct dentro de Struct dentro de Struct e assim por diante. Imagine uma estrutura de sociedade anônima. Essa sociedade é um objeto complexo e tem seus dados, tais como CNPJ, sua data de constituição etc. Um de seus dados é a composição de sua diretoria. Cada um desses diretores é, por sua vez, uma pessoa natural, um objeto complexo com nome, CPF, RG etc. Cada um desses diretores pode ter como uma das informações que os definem quem é seu procurador. Procurador, mais uma vez, é um objeto complexo com nome, CPF, RG, endereço etc. Ocorre que endereço é um objeto complexo, com rua, número, complemento, CEP, cidade etc. Logo tanto a sociedade anônima, quanto seus diretores e procuradores de seus diretores têm, como um dos campos que os definem, um objeto complexo chamado endereço. Aliás, conectando essa discussão com a anterior, para facilitar o uso da informação endereço e evitar sintaxes longas como
<sociedadeEmissora.presidente.procurador.endereco.rua>
deixamos como identificador do endereço sua qualificação, de forma que, para imprimir o endereço do procurador nesse caso bastaria utilizar
print <sociedadeEmissora.presidente.procurador.endereco>,
se você estiver utilizando os componentes globais da Looplex. Os pontos que você vê na sintaxe acima são a maneira de informar ao sistema que estamos acessando uma informação dentro de uma estrutura anterior.
Voltando ao exemplo acima, o local de emissão da CCB é um endereço, ou seja, um objeto complexo nos termos que acabamos de definir.
Herança: Herança é um conceito emprestado de linguagens orientadas a objeto que nos permite estender uma estrutura, criando uma especialização. Com ela, é possível criar associações de paternidade entre classes por meio da diretiva extends. Ele permite que você informe ao sistema que quer criar uma nova estrutura, com todas as características de outra, acrescentando ou redefinindo apenas as modificações que você deseja. Isso é útil para não termos que reescrever a estrutura toda. É o caso, por exemplo, de uma pessoa natural capaz, que herda suas propriedades de pessoa natural que, por sua vez herda suas propriedades de sujeito e assim por diante. Abaixo segue um exemplo:
declarations { *struct[Sujeito] { fields { +[nome] : String { name = "Nome ou denominação" }, +[genero] : List ("Masculino", "Feminino") { name = "Gênero" atomic = true } } } *struct[PessoaNatural] extends *Sujeito { fields { +[nome] : String { name = "Nome da pessoa" }, +[nacionalidade] : String { name = "Nacionalidade" }, +|profissoes| : Vector[String] { name = "Profissões" }, +[estadoCivil] : String { name = "Estado civil" } } }}
No exemplo acima, a Struct *PessoaNatural
possui, ainda que implicitamente, todos os fields da estrutura Pai, a saber, a Struct *Sujeito
, além dos quatro campos declarados. Ao todo, são cinco campos: [nome]
, [genero]
, [nacionalidade]
, |profissoes|
e [estadoCivil]
. Note que o campo [nome]
foi declarado tanto na estrutura sujeito quanto na estrutura pessoa natural. Nesses casos, prevalecem os campos declarados na estrutura filha, e os campos repetidos na estrutura pai, deixam de existir. Como consequência, a propriedade name do campo [nome]
da Struct *PessoaNatural
é “Nome da pessoa”.
Pré-loaders: Ao fazermos heranças podemos fazer pré-loaders, ou seja, carregamos alguns campos com valores e propriedades. Se estamos, por exemplo, criando uma estrutura de emissora de debêntures sabemos que ela será sociedade por ações, de forma que todas as perguntas sobre tipo de sujeito, tipo de pessoa jurídica e tipo societário podem ser respondidos desde logo, pois a estrutura e seu contexto já têm um escopo restrito. A seguir, vejamos um exemplo de pré-loader.
+<localSujeito> : struct[LocalSujeito] extends *SujeitoQualificado where ( [papel] = "", [nome] = "Almeida Prado Ltda.", [tipoDeSujeito] = "Pessoa jurídica", [tipoDePj]= "Sociedade", [subtipoDePj] = "Sociedade empresária limitada", [cnpj] = "12.946.691/0001-29", [genero].mandatory = "+", [genero].name = "Gênero da Sociedade", [genero].request = "Indique o gênero", [genero].default = "Feminino", |logradouros|.mandatory = "+", |logradouros|.upper = 1, |logradouros|.lower = 1, [logradouros{0}.tipo] = "Sede", [logradouros{0}.pais] = "Brasil", [logradouros{0}.principal]= "Avenida Liberdade", [logradouros{0}.numero] = "1485", [logradouros{0}.complemento] = "15o andar", [logradouros{0}.bairro] = "Liberdade", [logradouros{0}.codigoPostal] = "01503-010", [logradouros{0}.uf] = "São Paulo", [logradouros{0}.cidade] = "São Paulo" ) { name = "Sociedade" request = "Insira os dados da sociedade"}
No exemplo anterior, percebemos os pre-loaders determinando valores de campos e propriedades, senda estas definições feitas todas dentro da cláusula where. Os pré-loaders são ferramentas poderosas e essenciais em grandes projetos de templates, pois além de economizar tempo de preenchimento, trazem mais segurança no preenchimento consistente via Lawtex.
Quanto à herança, supondo que tanto uma estrutura pai quanto uma estrutura filha tenham pré-loaders, os pré-loaders da pai são carregados antes dos pré-loaders da filha. Sendo assim, os pré-loaders da estrutura filha sobrepõem os da estrutura pai caso hajam conflitos.
Operações e Comandos
O print é a operação mais comum. Essa operação já apareceu em diversos dos exemplos acima. Seu papel fundamental é estabelecer que o computador escreva algo no documento final, ou seja, que ele imprima algo. Essa impressão pode ser modificada e personalizada com os tubes de texto que veremos em tópicos seguintes, mas ela deve existir antes da aplicação de qualquer um deles.
Há três aspectos muito importantes dessa operação. A primeira é que ela tem uma sintaxe mais complexa e outra simplificada. A sintaxe simples é a que vimos nos exemplos, a diretiva print associada ao que queremos imprimir. A sintaxe mais complexa deve ser utilizada, contudo, se você quer fazer mais de uma versão do mesmo texto. Isso porque o sistema permite mais de uma forma de redação, quando essas redações todas funcionariam no mesmo ponto do código, do ponto de vista lógico. Imagine que eu tenha cinco formas de dizer que o contrato será por prazo indeterminado. Imagine, agora, um ponto do código em que garantimos que
operations { <prazo.tipo> == "Indeterminado".}
Nesse ponto poderíamos imprimir qualquer uma das cinco alternativas. Logo podemos usar a sintaxe mais longa e já colocar as cinco alternativas lá. Poderemos acionar cada uma delas por escolha nossa ou pela escolha do usuário. Podemos deixar que usuário use cada uma e veja qual prefere. Podemos redigir cada uma delas com viés a favor de um ou de outro contratante e operar esse viés com base em quem pediu a minuta.
O segundo aspecto é que podemos inserir de forma abreviada uma condição no texto se essa condição afeta especificamente a impressão. Vejamos o exemplo a seguir:
operations { print "\pRespondem solidariamente pelas obrigações assumidas " & printIf(<tipoDeContrato> == "Escritura", "nessa","nesse") & <tipoDeContrato> & ":\n\b"}
Aqui precisávamos de um pequeno ajuste no texto, pois para escrituras precisamos de feminino e para todos os outros contratos, instrumentos, termos acordos etc. precisamos de masculino. Daí inserir o printIf. O printIf, é uma forma resumida de fazer uma condição quando ele só trata de texto, daí termos, entre parênteses, a condição, em seguida, separado por vírgula, o texto a ser impresso quando a condição é verdadeira e, por último e também separado por vírgula, o texto a ser impresso quando a condição é falsa.
Por fim, o terceiro aspecto importante da impressão é a possibilidade de aplicação da gramática. Para concordar palavras com vetores de sujeitos ou variáveis cujo gênero gramatical sempre é pedido ao usuário, o sistema automaticamente faz a concordância de gênero e número se indicarmos qual é o vetor ou variável de referência. Ele é capaz de escolher, por exemplo, a opção correta dentre “a autora requer”, “o autor requer”, “as autoras requerem” e “os autores requerem”, de acordo com as informações que o usuário selecionou no sistema, por exemplo o número de autores e o gênero de cada autor. Para outras questões gramaticais que não envolvem sujeitos, a solução mais rápida é o printIf.
A gramática é, contudo, uma funcionalidade poderosa do sistema. Além do vetor ou variável de referência seu uso exige que o engenheiro ou a engenheira se certifique de que a palavra a ser concordada está no dicionário da Looplex.
A manutenção da gramática é atualmente feita pela Looplex mediante a alimentação de uma base gramatical. Dessa forma, infelizmente se a palavra não estiver no dicionário é preciso acrescentá-la ou a gramática não vai funcionar. O uso da gramática exige, atenção redobrada! Além de se certificar de que a palavra exista no dicionário Looplex, o engenheiro ou a engenheira deverá inserir a palavra em sua conjugação singular masculina, com a mesma grafia que se encontra no referido dicionário, por exemplo, respeitando as letras minúsculas e maiúsculas, dentre outros, para que o sistema reconheça a palavra ou expressão, caso contrário a funcionalidade não será executada.
A sintaxe da gramática para concordância de gênero é a seguinte:
operations { <parteAutora>.grammar("solteiro")}
Ou, para concordância de gênero e número com vetores:
operations { |partesDevedoras|.grammar("qualificado")}
Atribuições
As atribuições são muito comuns nos templates. Uma atribuição ocorre quando a informação inserida num operando é definida no código e não pelo usuário. Vamos retomar um exemplo já fornecido para entender o conceito:
declarations { +<numeroDeParcelas> : Integer { name = "Número do parcelas" request = "Insira o número de parcelas" }, +<valorDaParcela> : Currency{ name = "Valor da parcela" request = "Insira o valor individual de cada parcela" }, -<valorTotalDoContrato> : Currency}operations { <valorTotalDoContrato> = <numeroDeParcelas> \* <valorDaParcela>}
Nesse caso não precisávamos pedir ao usuário o valor total do contrato, pois ele podia ser preenchido por atribuição. Um sinal de igual em Lawtex significa uma atribuição. É diferente de dois sinais de igual que indicam uma avaliação se duas coisas são iguais. Lemos a linha acima como "valor total do contrato recebe o número de parcelas multiplicado pelo valor da parcela. É importante ler dessa forma para facilitar a compreensão de linhas como a seguinte:
operations { <valor> = <valor > + <valor> \* <multa>}
Em termos matemáticos essa linha só faria sentido se valor fosse igual a zero ou se a multa fosse igual a zero. Afinal, subtraindo <valor>
dos dois lados teríamos <valor> * <multa>
igual a zero. Ocorre que nem valor nem multa devem ser zero. Justamente porque esse sinal de igual deve ser lido como uma atribuição. Essa linha deve ser lida como "a variável 'valor' recebe seu 'valor original' acrescido de seu 'valor multiplicado pela multa'. Trata-se de uma instrução, não de uma afirmação matemática. A atribuição vale para operandos de qualquer tipo, respeitados os métodos adequados ao tipo (uma String, por exemplo, não pode ser somada com +
, mas sim concatenada com &
).
As modificações de propriedade que mencionamos acima nada mais são do que atribuições realizadas nas propriedades. Podemos, inclusive, fazer atribuições em posições de vetor. Imagine uma ação que discute um contrato. Nela, perguntamos quem é a parte autora e se ela é parte no contrato. Se o usuário responder que sim sabemos que ela é parte, mas pode haver outras. Assim, atribuímos ela como primeira posição no vetor de partes do contrato discutido e trazemos o vetor para que o usuário insira as demais partes. Ele já verá a parte autora como primeira posição do vetor, sabendo que a atribuição ocorreu. Esse comportamento, contudo, não vale sempre. A atribuição feita a uma variável faz com que ela não seja exibida para o usuário. A atribuição só é exibida se ela ocorrer na posição de um vetor ou no campo de uma estrutura.
Por fim, as atribuições não são modificáveis pelo usuário. Elas ficam em cinza quando exibidas na tela e bloqueadas para alterações.
Modificações de propriedades: Como já destacamos nos itens sobre propriedades e nos itens sobre atribuições, podemos modificar as propriedades de operandos a qualquer momento. Podemos restringir as opções de listas, modificar limites mínimos e máximos de vetores, modificar perguntas e nomes de variáveis etc. Para fazer a referência a uma propriedade usamos ponto. Note que o ponto dentro da variável indica o acesso a campos de um objeto, ao passo que o ponto fora dela é usado para acessar ou alterar propriedades. Vejamos dois exemplos:
operations { <papel>.request = "Insira a posição de " & <autor.nome> & " no contrato discutido na ação", <papel>.options = {"Parte devedora principal", "Parte devedora solidária", "Parte garantidora"}}
No exemplo adaptamos a pergunta do papel da parte autora no contrato colocando seu nome na pergunta. Tal operação não é logicamente imprescindível. O usuário sabe quem é a parte autora, mas identificá-la especificamente melhora a usabilidade do template. Além disso, inserimos no exemplo o acesso a um campo da estrutura de autor para diferenciar o uso do ponto na modificação da propriedade. Por fim, também modificamos as opções para ilustrar a sintaxe dessa operação.
Cálculos
Os cálculos também já foram exemplificados acima. A notação dos cálculos em Lawtex é muito parecida com a utilizada em planilhas. Multiplicações são indicadas por ∗
, divisões por /
, adições por +
, subtrações por −
e potenciação por ^
. Lembre-se que a raiz nada mais é do que um expoente fracionário, de forma que podemos sempre expressá-las usando notação de potenciação. Vejamos como a fórmula da taxa de juros composta é expressa em Lawtex:
operations { <valorFinal> = <valorInicial> * (1 + <taxaDeJuros>) ^ <prazo>}
Lembre-se de adequar os tipos das variáveis ao seu papel nos cálculos. O prazo, por exemplo, pode ser um inteiro, mas o valor final esperamos que seja um valor Real ou do tipo Currency. Lembre-se também de garantir que no momento em que os cálculos são executados, todas as informações estão disponíveis (já foram perguntadas aos usuários), que não há divisões por zero, raízes pares de números negativos etc.
If, elseif e else
Já vimos diversos exemplos de if
, elseif
e else
ao longo desse material. Usamos o if quando queremos restringir uma operação a determinada condição. Quando usamos o if estamos impondo que as linhas de código contidas entre as chaves só sejam executadas se a condição entre parênteses for verdadeira. Se inserimos um “elseif” estamos impondo como condição que (1) o previsto no if seja falso e (2) que o previsto no elseif seja verdadeiro. Trata-se de uma alternativa restrita. Isso porque o else é uma alternativa irrestrita: para qualquer outro caso que não o imposto no if será executado o disposto no else.
Essas distinções parecem simples, mas geram confusão com frequência. É comum colocarmos por engano no elseif uma condição que não é excludente da prevista no if. Também é comum colocarmos no else linhas que não queríamos que fossem executadas em todos os casos excluindo o previsto no if. Como regra de bolso faça vários ifs separados se você aceita que talvez essas condições se manifestem em conjunto e use elseif apenas se você quiser que as condições sejam mutuamente excludentes. Seguiremos com um exemplo de como NÃO fazer condições em Lawtex:
operations { if (EXISTS <devedor> IN |devedores| : <devedor.tipoDeSujeito> == "Pessoa natural" AND <devedor.estadoCivil> == "Casado") { use *topic[TOP_Rep_Casado] } elseif (EXISTS <devedor> IN |devedores| : <devedor.tipoDeSujeito> == "Pessoa jurídica" AND <devedor.tipoDePJ> == "Sociedade Anônima") { use *topic[TOP_Rep_SA] } else { use *topic[TOP_Rep_LTDA] }}
A sintaxe do if
, elseif
e else
está certa. O erro desse trecho é lógico. Em primeiro lugar, observe que a condição utilizada é de existência. Veremos com mais detalhes essa condição, mas você deve ler, por exemplo, a primeira condição da seguinte forma: "se existir pelo menos um devedor qualquer, dentre o conjunto de devedores, que é pessoa natural e que é casado". A segunda condição deve ser lida como "se existir pelo menos um devedor qualquer, dentre o conjunto de devedores, que é pessoa jurídica e que é sociedade anônima". Dentro das condições estamos usando um código modularizado global, que são cláusulas de representações de pessoas casadas (dizendo, por exemplo, que as outorgas uxórias necessárias foram concedidas etc.), de representações de S.A. (dizendo, por exemplo, que a celebração do contrato foi autorizada nos termos do estatuto, assembleia geral etc.) e de representações de limitada (dizendo, por exemplo, que a celebração foi autorizada nos termos do contrato social, reunião de sócios etc.).
No fato descrito acima, vemos o primeiro erro lógico. É possível que haja, dentre os devedores, uma pessoa física, uma S.A. e uma limitada. Nesse caso seria necessário inserir três representações diferentes. Ocorre que o código não vai fazer isso. Se ele verificar uma pessoa natural casada no conjunto, ele só vai imprimir a representação de pessoas casadas e parar. Se não houver pessoas naturais casadas, mas houver S.A. e limitada, ele, seguindo a ordem do código, vai imprimir a representação de S.A. e parar.
Por fim, há um erro lógico de deixar a representação de limitada no else: Se não houver pessoa natural casada ou S.A. ele vai automaticamente imprimir a representação de limitada, sendo que o conjunto pode ser inteiro composto por cooperativas, sociedades de advogados ou fundações. Essa lógica não só deixa de imprimir cláusulas necessárias, como pode imprimir cláusulas incompatíveis com os dados informados pelo usuário! Um perigo, de fato!
Vejamos agora a lógica correta:
operations { if (EXISTS <devedor> IN |devedores| : <devedor.tipoDeSujeito> == "Pessoa natural" AND <devedor.estadoCivil> == "Casado") { use *topic[TOP_Rep_Casado] }, if (EXISTS <devedor> IN |devedores| : <devedor.tipoDeSujeito> == "Pessoa jurídica" AND <devedor.tipoDePJ> == "Sociedade Anônima") { use *topic[TOP_Rep_SA] }, if (EXISTS <devedor> IN |devedores| : <devedor.tipoDeSujeito> == "Pessoa jurídica" AND <devedor.tipoDePJ> == "Sociedade Limitada") { use *topic[TOP_Rep_LTDA] }}
Agora sim temos condições não excludentes e restringimos o uso da representação de limitada à situação em que há pelo menos uma limitada no conjunto.
Pertinência
Já indicamos como avaliar se algo pertence a algum conjunto. Vimos essa sintaxe quando discutimos listas e quando discutimos a propriedade atomic. Também vimos essa sintaxe logo cima, no tópico sobre if, elseif e else. A pertinência em Lawtex é expressada pela sintaxe IN. Com ela podemos avaliar se determinado objeto pertence a um vetor e se determinado texto pertence ao conjunto de respostas do usuário numa lista não atômica. Ele pode ser usado de maneira direta, como por exemplo
operations { if ("Horas extras" IN <pedidos>),}
ou em um EXISTS (ou FORALL) conforme se vê no tópico anterior. A primeira avaliação é mais simples, pois o sistema apenas busca o texto pedido nas respostas do usuário. Na segunda o sistema busca num conjunto de objetos complexos qualquer elemento que tenha a característica desejada.
Veja que a avaliação de pertinência não conta o número de elementos de um vetor que têm determinada característica. Para saber se existe algo com a característica ou contar quantos são esses elementos, é preciso outra lógica, que estudaremos adiante. Por enquanto tenha em mente que essa sintaxe é apropriada para conjuntos, que em Lawtex são as listas e os vetores. Por fim, você também verá que na sintaxe foreach o IN também desempenha esse papel.
Quantificadores existenciais e universais
Já utilizamos o EXISTS diversas vezes nos exemplos acima. Nos adiantamos dessa forma simplesmente porque seu uso é o que gera os maiores desafios do ponto de vista dos operadores lógicos e da pertinência. Tentamos usar os exemplos mais difíceis, não por sadismo (ok, talvez por um pouco de sadismo), mas porque dominando os casos mais complexos, resolver os simples é trivial.
O EXISTS é a forma de avaliar se, em determinado conjunto, existe um ou mais elementos que satisfazem determinada condição. Ele não conta quantos satisfazem, apenas retorna “verdadeiro” se existir um ou mais elementos com aquela característica e “falso” se não existir nenhum elemento com aquelas características. Vejamos um exemplo:
if (EXISTS <devedor> IN |devedores| : <devedor.certidaoTrabalhista> == "Positiva")
Um jeito de ler essa linha é “se existir qualquer devedor no conjunto de devedores tal que sua certidão perante a justiça do trabalho seja positiva”. Essa é uma boa condição, por exemplo, para alertar o usuário ou para pedir mais garantias, já que existe um ou mais devedores com passivo trabalhista.
O irmão do EXISTS é o FORALL. O FORALL é a forma de avaliar se, em determinado conjunto, todos os seus elementos satisfazem determinada condição. Ele retorna “verdadeiro” se todos os elementos possuem aquela característica e “falso” se existir algum elemento que não satisfaz aquelas características. Vejamos um exemplo:
if (FORALL <devedor> IN |devedores| : <devedor.certidaoTrabalhista> == "Positiva")
Um jeito de ler essa linha é "se todo devedor no conjunto de devedores tal que sua certidão perante a justiça do trabalho seja positiva".
Contagem e filtros
Como acabamos de descrever, a avaliação de existência não faz contagens. Para contar quantos elementos satisfazem uma condição é frequente usarmos o foreach. Da mesma forma que, no exemplo acima, pedíamos que o sistema imprimisse um texto para cada elemento, aqui podemos pedir que ele acrescente uma variável de contagem para cada elemento, retornando, ao final, uma variável de contagem com o número total de elementos. Vejamos um exemplo:
operations { <contadorCertidaoPositiva> = 0, foreach(<devedora> IN |devedoras|) { if (<devedor.certidaoTrabalhista> == "Positiva") { <contadorCertidaoPositiva> = <contadorCeritdaoPositiva> + 1 } }}
Vejamos o que isso quer dizer. Em primeiro lugar, atribuímos zero ao contador. Isso porque inicialmente a quantidade é zero. Essa atribuição é necessária para “inicializar a variável”. Essa inicialização é necessária pois existe uma diferença entre ser vazia e ser igual a zero. Quando a variável é vazia o sistema suspende a execução de qualquer código que utilize a variável, até que ela seja respondida pelo usuário ou preenchida por atribuição. Logo, estamos preenchendo ela por atribuição, já que ela é puramente interna ao código e não será exibida ao usuário.
Seguimos estabelecendo que o sistema deve passar por cada devedora do conjunto de devedoras e, caso a devedora tenha certidão positiva, some um na variável de contagem. Para deixar essa lógica mais clara, imagine que há quatro devedoras no vetor. A primeira delas tem certidão positiva, a segunda certidão negativa, a terceira certidão positiva e quarta positiva com efeito de negativa. Quando o sistema entra na execução do foreach ele olha para a primeira devedora e executa o código para ela. Ele avalia para ela a condição. Ela tem certidão positiva. Então o contador recebe como valor seu valor original, que era zero, mais um. O sistema segue, então, para o próximo elemento. Ele toma a segunda devedora e executa o código. A condição impõe que o sistema faça a soma apenas se ela tiver certidão positiva, mas ela tem certidão negativa, então o sistema não faz nada. O contador permanece igual a um. O sistema segue para a terceira devedora. Ela tem certidão positiva, então o sistema executa o que está dentro do if e faz com que a variável passe a valer seu valor original, que naquele momento é um, acrescido de um, totalizando, agora, dois. O sistema segue então para a quarta e última devedora. Ele vê que sua certidão não é positiva, mas sim positiva com efeito de negativa, então ele não executa o código encapsulado e a variável se mantém com o valor dois. Como todos os elementos foram avaliados a iteração se encerra e o sistema segue para executar as próximas linhas de código.
Como vimos por essa descrição específica do que está se passando por trás da tela vemos como a variável de contagem encerra a iteração com 2, justamente o número de empresas com certidão positiva.
Uma forma alternativa de proceder com a contagem é mediante ao uso de filtros. O seguinte algoritmo apresenta uma proposta alternativa ao algoritmo de contagem anterior:
operations { |devedorasPositivadas| = |devedoras|.filter ( [this.certidaoTrabalhista] == "Positiva" ), <contadorCertidaoPositiva> = |devedorasPositivadas|.size()}
O filtro é uma ferramenta que seleciona objetos dentro de um vetor do tipo Struct que respeitem determinado critério. Na Linha 2 do algoritmo anterior, esse critério é apontado a partir do identificador this
, que significa a própria posição em iterada.
Foreach
O foreach é a forma mais comum de trabalharmos com vetores. Ele impõe que determinadas linhas de códigos sejam executadas para cada um dos elementos de um vetor. Como dito anteriormente, tal procedimento é chamado de iteração. Seu uso é frequente com partes, pois há um número indefinido delas e, quantas quer que elas sejam, o sistema deverá qualificar cada uma delas. Vejamos um exemplo:
operations { print "\pAs Partes declaram que o disposto nesse Contrato não abrange a seguintes sociedades: ", foreach(<empresa> IN |excluidas|) where (separator = "%f1; %s1; %p1 e %l1.") { print <empresa.nome> & ", " & <empresa.tipoSocietario>.lowercase() & " inscrita no CNPJ sob o no " & <empresa.cnpj> }, print "\n\b"}
Nesse exemplo iniciamos abrindo uma cláusula que exclui determinadas pessoas jurídicas do grupo do disposto no contrato. Após a introdução, pedimos que o sistema descreva quais são essas pessoas. Veja que informamos apenas uma vez o que queremos como descrição: O nome, o tipo de societário e o CNPJ. O que garante que esse procedimento será repetido é o foreach. Lendo o código em português na ordem estamos dizendo "para cada uma das empresas no conjunto de empresas do grupo excluídas do negócio, onde a forma de separar um elemento do outro é ponto e vírgula, imprima seu nome, o tipo societário e o CNPJ".
O lowercase()
é um método aplicável a Strings. Ele tem por função colocar em letras minúsculas todo o texto. Ele foi invocado pois o conteúdo apresentado para o usuário em listagens geralmente vem com a primeira letra maiúscula. Assim, esse método obriga que esse conteúdo seja em letra minúscula no texto impresso.
O separador pode ser alterado. Conforme introduzimos anteriormente, o "f" corresponde a "first", o "s" a "second" e assim por diante. Nele definimos como separar os textos impressos, já que eles serão impressos várias vezes. No exemplo escolhemos separar os elementos entre si com ponto e vírgula, separando apenas o penúltimo do último por "e", como se costuma fazer na linguagem escrita. Por fim, após o último elemento imprimimos um ponto final.
Após fazer essas impressões pedimos que o sistema encerre a cláusula e pule uma linha. Um resultado possível dessa linha de código poderia ser o seguinte:
- As Partes declaram que o disposto nesse Contrato não abrange as seguintes sociedades: Nintendo do Brasil, sociedade por ações inscrita no CNPJ sob o no 12.345.678/0000-00; Atari do Brasil, sociedade limitada inscrita no CNPJ sob o no 23.456.789/0000-00 e Sega do Brasil, sociedade em comandita simples inscrita no CNPJ sob o no 55.555.555/8888-88.
Operadores lógicos
Frequentemente temos que criar condições complexas. Já vimos algumas em exemplos anteriores. Podemos buscar pessoas naturais com mais de 18 anos, pessoas naturais casadas, pessoas jurídicas com capital aberto, capital superior a 100 milhões de reais, mas não listadas no Novo Mercado. Para construirmos essas condições precisamos de operadores lógicos (os conectivos) como OR
, XOR
, AND
e NOT
.
O operador OR
tem um papel de união de conjuntos. O operador XOR
é uma operação de OU exclusivo, não considerando a ocorrência mútua. Quando colocamos uma condição ou outra estamos dizendo que nosso conjunto de interesse é a união do conjunto do que satisfaz a primeira condição com o conjunto que satisfaz a segunda. Assim, ao colocar duas condições com OR entre elas estamos dizendo ao sistema para executar o trecho de código se ocorrer uma, se ocorrer a outra ou se ocorrerem as duas ao mesmo tempo.
Já AND
desempenha o papel de união. Colocando num if suas condições com um AND entre elas implica que o sistema só executará o código se a primeira e a segunda forem verdadeiras. Por fim, o NOT
desempenha o papel de conjunto complementar, ou seja, o sistema só executa o trecho do código se acontecer qualquer outra coisa que não a condição definida.
Diferentemente dos operadores anteriores, o NOT não é um operador binário, a saber, utilizado com duas condições necessariamente. Ele é dito ser operador unário, pois nega o termo imediatamente posterior a ele.
Talvez mais complexo do que o uso dos operadores é saber separá-los com parênteses. Se não delimitarmos bem o que queremos dizer podemos cometer erros de lógica graves. Suponha que queiramos, assim como no exemplo acima, inserir uma representação relativa a autorização de cônjuges. Queremos que ela seja impressa quando alguém é casado, ou vive em união estável, com qualquer regime que não seja o de separação total de bens. Vejamos:
if (EXISTS <devedor> IN |devedores| : <devedor.tipoDeSujeito> == "Pessoa natural" AND ( <devedor.estadoCivil> == "Casado" OR <devedor.estadoCivil> == "Convivente estável") AND NOT ( <devedor.regimeDeBens> == "Separação total de bens" ))
Nessa ordem garantimos que (1) o devedor é pessoa natural, (2) que ele é casado ou convivente estável e (3) que seu regime de bens não é o de separação total de bens. Se não colocássemos parênteses, ficaria assim:
if (EXISTS <devedor> IN |devedores| : <devedor.tipoDeSujeito> == "Pessoa natural" AND <devedor.estadoCivil> == "Casado" OR <devedor.estadoCivil> == "Convivente estável" AND <devedor.regimeDeBens> ~= "Separação total de bens")
Aqui temos um problema. Impomos que (1) a pessoa seja natural e casada ou (2) que ela seja convivente estável e não tenha o regime de separação total de bens.
Em primeiro lugar, isso retornaria um erro, porque só pessoas naturais podem ser conviventes estáveis, o que não é garantido na condição alternativa posterior ao OR.
Em segundo lugar, ainda que não houvesse esse erro de renderização, ou seja, o uso de uma informação que não sabemos se existe, como a avaliação do regime de bens está apenas na condição alternativa o sistema aceitaria situações em que a pessoa é apenas natural e casada, ainda que seu regime de bens seja o de separação total de bens. Isso porque se o sistema verifica que a primeira condição é verdadeira ele nem avalia a segunda, ele executa diretamente o código circunscrito pelo if. Nesse caso é o que ele faria após verificar que a pessoa é natural e casada. Essa falta de parênteses está, portanto, errada.
Ademais, tenha sempre em mete a ordem de precedência desses operadores: uso de parênteses sobre NOT, NOT sobre AND, e AND sobre OR (ou XOR). Lembre-se que na lógica Boolena, AND equivale a multiplicação/divisão enquanto que OR (ou XOR) equivalem a adição/subtração.
Por fim, tente sempre otimizar a forma com que você redige condições. É muito mais fácil dizer que você quer qualquer regime de bens que não o de separação total do que dizer que você quer o de comunhão total ou o de comunhão parcial ou o de participação final nos aquestos. Essas técnicas são análogas às que usamos para resolver exercícios de probabilidade básica na escola. Às vezes é muito mais fácil calcular a probabilidade dos demais cenários do que calcular a probabilidade de ocorrer a situação de interesse. Aqui é a mesma coisa, as vezes é mais fácil definir o conjunto e não nos interessa e negá-lo do que defini-lo diretamente.
Operações dentro de structs
Em Lawtex existe a possibilidade de usar operações dentro de Structs com o intuito de definir condições para existência de alguns fields internos, ou mesmo executar operações no momento em que determinados campos são respondidos.
Ifstruct: O ifstruct, como o nome sugere, é uma regra de existência condicional para campos da estrutura: Perguntar CPF apenas se temos uma pessoa é física, perguntar tipo societário apenas se temos uma pessoa jurídica, perguntar regime de bens apenas se temos uma pessoa física e casada ou em união estável e assim por diante. Impomos, por meio dessa sintaxe, uma condição para a existência de uma informação que caracteriza o objeto.
Lembre-se que essa operação é definida em abstrato, ou seja, ocorrerá na exibição de qualquer objeto concreto daquele tipo. Daí as operações serem feitas com notação de campo. Isso vale para qualquer operação dentro de estrutura. Vejamos um exemplo:
declarations { +<strPessoa> : Struct { fields { +[tipoDeSujeito] : List ("Pessoa natural", "Pessoa jurídica") { name = "Tipo de sujeito" atomic = true default = "Pessoa jurídica" }, if ([tipoDeSujeito] == "Pessoa natural") { +[documento] : String where ("CPF") { name = "CPF" } } else { +[documento] : String where ("CNPJ") { name = "CNPJ" } } } }}
Esse exemplo apresenta um caso especial de ifstruct, conhecido como polimorfismo. Ao considerar o campo [tipoDeSujeito]
pessoa natural ou pessoa jurídica, o campo [documento]
sempre existirá, contudo, com valores semânticos diferentes.
Embora em orientação a objetos, o polimorfismo tenha um significado diferente, nesse caso, é a propriedade de tratar vários campos protegidos por ifstruct de maneira homogênea, ou seja, aplicamos o mesmo nome ao campo em condições alternativas e definimos um default para que ele sempre exista. Em outras palavras, no início da execução, em virtude do default o campo de tipo de sujeito não é vazio, mas sim pessoa jurídica, então o número perante a receita existe, na modalidade CNPJ. Se o usuário modificar a resposta para qualquer outra o campo continuará existindo, ainda que com outro name, de forma que não precisamos mais garantir sua existência na parte operativa.
Observe que o ifstruct está mais relacionado com a definição dos campos do que com a operação deles por si. Isso porque a existência de um campo é condicionada. Sendo assim, deve-se tomar cuidado ao operar um campo "coberto" por um ifstruct. Nesses casos, deve-se sempre garantir que a condição de existência do referido campo coberto se cumpra no momento de sua operação (comparação, impressão, atribuição, iteração, etc.)
Erros de renderização: Como já adiantamos, os erros de renderização ocorrem quando utilizamos uma informação sem garantir sua existência. É importante emitir erros dessa natureza a fim de garantir a consistência do documento jurídica. Por exemplo, não se admite na lei Brasileira que uma pessoa jurídica sem nacionalidade brasileira possua RG. Nesse caso, é inconsistente pedir para um estrangeiro tal informação. Logo, por não requisitá-lo, maior erro ainda seria imprimir o RG de um estrangeiro "por acidente". Isso dificultaria muito a revisão humana do documento final produzido. Sendo assim, encare as mensagens de erro com bons olhos.
Qualquer uso não protegido vai gerar um erro. Considere o exemplo anterior re-escrito sem polimorfismo:
declarations { +<strPessoa> : Struct { fields { +[tipoDeSujeito] : List ("Pessoa natural", "Pessoa jurídica") { name = "Tipo de sujeito" atomic = true }, if ([tipoDeSujeito] == "Pessoa natural") { +[cpf] : String where ("CPF") { name = "CPF" } } else { +[cnpj] : String where ("CNPJ") { name = "CNPJ" } } } }}
Quando mandamos o sistema imprimir o CPF de um sujeito, sem garantirmos que esse sujeito seja pessoa física, o sistema acusará um erro. Assim, uma regra de bolso para prevenir esse erro é repetir a estrutura de ifs da estrutura nas operações. Se você impôs duas condições para que a informação seja perguntada na estrutura, imponha as duas mesmas condições para imprimí-la, multiplicá-la, concatená-la, atribuí-la para uma variável etc.
Conforme discutido anteriormente, o polimorfismo garante que um campo sempre exista. Para isso ele deve, além de existir um if (opcionalmente, com mais elseif), sempre deve existir um else, para que nenhum caso fique descoberto. Ademais, o papel do default é fundamental, como será visto adiante. No exemplo a seguir ocorre esse erro quando se tenta imprimir [documento]
diretamente:
declarations { +<strPessoa> : Struct { fields { +[tipoDeSujeito] : List ("Pessoa natural", "Pessoa jurídica") { name = "Tipo de sujeito" atomic = true }, if ([tipoDeSujeito] == "Pessoa natural") { +[documento] : String where ("CPF") { name = "CPF" } } else { +[documento] : String where ("CNPJ") { name = "CNPJ" } } } }}
Embora se pareça muito com o polimorfismo, não é de fato. Isso porque [tipoDeSujeito]
não foi inicializado por um default. Logo, quando o programa for executar a impressão do documento, por não haver default, o sistema não sabe desambiguar de onde pegar a informação, se do primeiro ou do segundo ifstruct. Para evitar que o campo coberto exista já no início da execução, os campos condicionais necessários não podem ser vazios: insira default nesses campos.
Loaders: O loaders é a parte operativa da estrutura, comportando todas as formas de operações já descritas. Seu objetivo, contudo, é primordialmente fazer operações apenas com os elementos da própria estrutura para modificar a exibição. Vejamos um exemplo:
declarations { *struct[GarantiaContrato] { fields { +[tipoDeContrato] : String { name = "Tipo de contrato" }, +[tipoDeGarantia] : List ("Real","Outras") { name = "Tipos de garantia" atomic = true default = "Real" }, if ([tipoDeGarantia] == "Real") { +[garantiaReal] : List ("Hipoteca", "Penhor", "Alienação fiduciária", "Cessão fiduciária", "Penhora", "Outra") { name = "Tipos de garantia real" atomic = true } } else { +[garantiaFidejussoria] : List ("Fiança", "Caução", "Seguro fiança", "Outra") { name = "Tipo de garantia" atomic = true } } } loaders { if ([tipoDeContrato] == "Contrato de Locação") { [garantiaReal].options = "Cessão fiduciária", [garantiaFidejussoria].options = {"Fiança","Caução","Seguro fiança"} } elseif ([tipoDeContrato] == "Acordo judicial") { [tipoDeGarantia] = "Real", [garantiaReal].options = {"Hipoteca","Penhor", "Alienação fiduciária", "Penhora"} } } }}
A sistemática dessa estrutura é simples. O usuário insere o tipo de contrato, escolhe um tipo de garantia e lhe é apresentada uma lista de garantias, a depender do tipo escolhido. Ocorre que, como já sugerimos, nem toda garantia é pertinente a todo contrato. Logo, vemos no loaders que se o tipo de contrato inserido for de locação a única garantia real possível é a cessão fiduciária e, dentre as demais garantias, as únicas possíveis são a fiança, a caução e o seguro fiança. Por outro lado, no caso de acordo, a estrutura impõe que a única garantia possível seja a real, pois executa uma atribuição bloqueando a escolha do usuário. Por fim, em todas as demais opções a estrutura exclui da escolha do usuário a constituição de penhora, que só é possível em acordo judicial.
É frequente utilizarmos o loaders para modificar dinamicamente os elementos de uma lista de uma maneira mais enxuta do que com diversos ifstructs.
Ordenação de Cards
Visto que sabemos declarar operandos, é essencial sua operação. Um operando declarado mas não operado para nada serve. A operação é um aspecto importante da programação. É onde todas as informações são processadas, o raciocínio e o desenvolvimento da argumentação lógica é colocada em prática.
Como citado anteriormente, cada operando se torna um card. Esses cards são respondido pelo usuário do template a fim de dar substrato lógico para que o programa realize suas impressões. Os cards podem ser levados ao usuário na ordem em que a informação é usada ou em uma ordem determinada pela engenheira ou engenheiro jurídico.
Em geral, o uso da informação segue a lógica jurídica do template, que não necessariamente é a mais fácil do ponto de vista do usuário. Para isso, podemos usar uma funcionalidade que surgiu em exemplos acima, o método ask. Inserindo, por exemplo, a linha |devedores|.ask()
forçamos o sistema a perguntar naquele momento aquelas informações.
É possível, dessa forma, ordenarmos as perguntas como quisermos no início da parte operativa do código e depois operar como desejamos. É possível, inclusive, modularizar a lógica que ordena as perguntas com um branch que chamamos de TOC, ou seja, Table of Contents.
Suponha que os cards de um template se dividem em (1) relativos às partes, (2) relativos ao objeto, (3) relativos ao preço e ao pagamento, (4) relativos às garantias, (5) relativos às assinaturas e testemunhas. Podemos criar um branch para cada um desses grupos, configurando index como true, e nas operações desses branchs apenas fazer o ask() dos operandos que queremos agrupar. Configurar o index como verdadeiro implica que queremos que aquele branch sirva para agrupar perguntas.
Por fim, podemos nomear cada um desses grupos de forma a identificar melhor cada grupo de perguntas. Ainda não discutimos o que são os branchs, mas por enquanto basta saber que podemos declará-los do jeito descrito e depois utilizá-los simplesmente para ordenar as perguntas.
Vejamos, por exemplo, como ficaria o branch de ordenação do grupo (1):
declarations { branch[TOC_Partes] { name = "Partes do contrato" operations { |devedoras|.ask(), |credoras|.ask(), |garantidoras|.ask(), |intervenientesAnuentes|.ask() } }}operations { use branch[TOC_Partes]}
Tubes
Dentre as operações, se destacam os tubes, que são funções ou métodos assinados em Lawtex, mas implementados em Java. O nome "tube" é um sinônimo de "pipe" (do inglês) que significa tubo: um canal de transferência de fluxos usado por diversos ramos da engenharia. Levando para o caso do Lawtex, esse "tubo" pode ser visto como um mecanismo usado para transferir fluxos de operações (trechos de código) para o núcleo do sistema Looplex. Assim, o programador Lawtex delega a responsabilidade da execução de um determinado trecho de código aos programadores de desenvolvimento da plataforma Looplex. Por exemplo, um programador Lawtex deseja enviar um e-mail dentro do sistema. É de se esperar que ele não conheça todos os protocolos de transferência de e-mail (tais como STMP, IMAP), bem como detalhes técnicos para sua implementação (portas e serviços). Assim, a equipe de desenvolvimento Looplex se responsabiliza por encapsular todos os comandos suficientes para enviar um email dentro de um método, e a partir disso, provê um ponto de acesso ao programador Lawtex para que seja possível realizar essa tarefa: sendMail ("lista de e-mails", "mensagem").
Uma função tem a sintaxe bem particular:
nomeDaFuncao(<arg1>, "arg2", etc()),
a qual inicia com letra minúscula e recebe zero, ou mais argumentos, sejam eles constantes, operandos, ou mesmo outras funções. Um método corresponde a uma função específica associada a uma classe semântica específica dentro do Lawtex. Um método sempre vem separado por ponto .
de seu proprietário chamador (invoker):
<invoker>.nomeDoMetodo(<arg1>, "arg2", etc()).
Acesse a página de tubes para obter uma documentação mais completa.
Tubes de texto:
Os tubes de texto são ferramentas para formatação do texto. Não há muito o que explicar no seu uso, com exceção da gramática, então só listaremos as sintaxes e a função de cada um, quando a complexidade for baixa. Observe, contudo, que eles podem ser aplicados sobre qualquer String, seja ela diretamente escrita entre aspas, ou seja ela uma variável do tipo lista, variável do tipo lista atômica, campo de uma posição de vetor etc. Logo, nos exemplos abaixo, onde você ler <texto>
saiba que o mesmo valeria para "texto" ou <objeto.campo>
ou |vetor{2}.campo.subcampo|
. Saiba também que é possível utilizar um tube dentro do outro, como quando queremos algo em negrito e em itálico.
Para colocar um texto em negrito basta inserir bold(<texto>)
e para colocar um texto em itálico basta inserir: it(<texto>)
. Para que o texto fique com recuo lateral de citação, basta digitar o comando citation(<texto>)
.
Já o método <sujeito>.grammar(<texto>)
é usado para vincular uma palavra a um sujeito ou vetor de sujeitos de forma que o sistema fará a concordância de gênero e número da palavra com base na referência indicada. A concordância é feita pois há um dicionário no banco de dados que o sistema acessa para singular masculino, singular feminino, plural masculino e plural feminino. Para que seja conjugada a concordância nominal, a única restrição feita é que o tipo do operando que invocou o grammar tenha um campo especial declarado: o campo [genero]
, cujos valores devem ser "Masculino"
ou "Feminino"
.
operations { print "§ 4.º A reunião torna-se dispensável quando " & |socios|.grammar("o sócio") & " " & |socios|.grammar("decidir") & ", por escrito, sobre a matéria que seria objeto dela.\b\b"}
Para colocarmos todas as letras de um texto em caixa-alta, é suficiente aplicar a função uppercase(<texto>)
. A função uppercase também é tida como método de variáveis do tipo texto, de modo que é válido escrever <texto>.uppercase()
. O mesmo vale para as demais funções análogas. O tube lowercase deixa todas as letras minúsculas, o firstUpper torna a primeira letra do texto maiúscula e o firstLower a primeira letra do texto minúscula.
Podemos mudar o alinhamento de textos, colocando à direita, à esquerda ou centralizados. Isso é útil para posicionar nomes e títulos de maneira diferente.
Para tanto basta fazer align(<texto>, "center")
, trocando o "center" por "left" ou "right", para alinhar ao lado.
Tubes de funções matemáticas:
Tubes de operações matemáticas implementam funções e métodos para realização de tarefas que envolvem cálculos e operações matemáticas que extrapolam o domínio dos operadores aritméticos.
Obviamente, os tubes de funções matemáticas são aplicáveis apenas a tipos numéricos, a saber: Integer, Real ou Currency. Sejam escritas diretamente como valores numéricos (ex. 57 ou 3.23), sejam variáveis, campos de uma posição de vetor numérico, ou qualquer outro operando numérico, onde você ler <numero>
nos exemplos abaixo, saiba que o mesmo representa qualquer operando ou valor numérico. Assim como em todos os outros tubes, é possível utilizar um tube dentro do outro, como quando nos casos apresentado anteriormente nos tubes de texto.
Por exemplo, podemos calcular o módulo de um número com o uso do tube abs(<numero>)
. Encontrar o máximo de um vetor de números é tão trivial quanto max(|numero|)
. Ou ainda, se pode usar a função round(<numero>, 2)
para arredondar um número com 2 casas decimais .
É preciso estar atento quanto aos parâmetros dos tubes de funções matemáticas. Se algum argumento não for numérico, um erro aparecerá.
Tubes de coleções e acessores:
Os tubes acessores e os tubes de coleções servem para acessar elementos dentro de vetores e listas, adicionar elementos, remover elementos, setar elementos default em vetores, entre outras operações. Representa uma classe de métodos e funções especializados em operar vetores e listas.
Por um lado, nos casos de listas vale destacar um tube que toma uma lista associativa e a partir de uma chave, retorna o valor correspondente. Tal tube é o <lista>.getByKey("Nome da chave")
. Considere o seguinte exemplo de uma lista associativa declarada a seguir:
declarations { *list[MoedasPorPaises] { name = "Lista associativa de países e suas moedas respectivas" fields = {"País", "Nome da moeda", "Código", "Unidade monetária"} options = ( {"Brasil", "real", "BRL", "R$"}, {"Argentina", "peso argentino", "ARS", "$"}, {"Colômbia", "peso colombiano", "COP", "$"}, {"Estados Unidos", "dólar americano", "USD", "US$"} ) type = "String" }}
Ao declarar uma lista podemos criar referências e associações entre informações internas. Assim, se um usuário responder apenas qual o país de uma variável do tipo desta lista, digamos <paisDeConstituição>
,
estamos capturando toda uma série de referências vinculadas a este país, como o nome de sua moeda, código de sua moeda e seu símbolo monetário. Para recuperar o nome da moeda, por exemplo, use o seguinte comando <lista>.getByKey("Nome da moeda")
, e assim por diante.
Por outro lado, nos casos de vetores, podemos adicionar um elemento oculto a um vetor existe através do seguinte comando: |vetor|.add(<elemento>)
. Podemos também obter o primeiro elemento do vetor através do comando |vetor|.get(0)
, assim como podemos excluí-lo: |vetor|.remove(0)
.
Para fins de conhecimento, o comando |vetor|.get(0)
é equivalente ao comando de indexação direta do vetor |vetor{0}|
. Note que ao excluir um elemento de uma posição x, todas as outras posições superiores são reposicionadas de modo a se conformar ao novo vetor. Se por exemplo, temos um vetor com duas posições 0 e 1, ao excluirmos a posição 0, a antiga posição 1 passa a ser acessada como a nova posição 0. O uso da propriedade default é restrita a operandos não vetorizáveis. Logo, os vetores não possuem default como propriedade. Mas podemos simular esse uso através do método |vetor|.set(0, <elem>)
. Nesse caso, <elem>
será tido como resposta default da posição 0 de |vetor|
.
Tubes de data e horas:
Os tubes de data e horas representam as classes de métodos e funções especializados em operar valores ou operandos do tipo data e horário. Considere sem perda de generalidade que <dateTime>
nos exemplos abaixo representam valores ou operandos do tipo data e horário.
Por exemplo, podemos extrair o dia de uma data do seguinte modo: day(<dateTime>)
. Assim, o comando day("04/08/1983")
retorna 4. O mesmo funciona para extração de ano (year), mês (month), horas (hour), minutos (minute) e segundos (second). Como foi citado na seção de onde se conceituou o tipo Date e Time, o tube today()
retorna o dia de hoje, bem como o tube now()
retorna o dia e horário atual.
Um par de tubes bem úteis são os tubes before e after. Esses tubes são usados para determinar se uma determinada data ocorreu antes (ou depois) de outra. No caso de
<dateTime_1>.before(<dateTime_2>),
o comando verifica se <dateTime_1>
ocorre antes de <dateTime_2>
, assim como no caso de
<dateTime_1>.after(<dateTime_2>),
o comando verifica se <dateTime_1>
ocorre depois de <dateTime_2>
.
Modificar datas é fundamental em documentos jurídicos. Um comando muito útil para alteração de datas, mediante a adição de x dias a uma data plusDays(<date>, x)
, onde x
é um número inteiro. O mesmo funciona para subtração: minusDays(<date>, x)
. Existem outras variações para Years, Months, Hours, Minutes e Seconds, todas prefixadas com "plus" ou "minus".
Tubes de HTML:
Os tubes de HTML fornecem ferramentas para criação de elementos HTML mais sofisticados, bem como inserções de anexos ao documento, imagens gráficas, tabelas, hiperlinks, entre outros.
Um exemplo desse tipo de tube é o tube de listagem de itens. A listagem é o meio de criar bullet points em Lawtex. Para fazer listagem é preciso, em primeiro lugar, informar que iniciou-se a listagem e, depois, que a listagem se encerrou. Essa informação é uma operação a parte. Vejamos um exemplo:
operations { print "Diante das razões expostas, requer-se:", beginList(), print "\i " & "A produção de todas as provas em Direito admitidas; ", print "\i " & "A citação de " & <reu.nome> & " ;", print "\i " & "A condenação de " & <reu.nome> & " ao pagamento de " & <indenizacao> & " reais a título de indenização pelos danos morais sofridos pela parte autora. ", endList(), print "Termos em que pede deferimento."}
Vemos no exemplo que o beginList()
inicia a listagem e o endList()
a encerra, sendo que cada novo ponto na listagem é identificado por \i
.
As tabelas de HTML também são muito úteis na composição de documentos jurídicos. Antes de mostrarmos como gerar tabelas é preciso compreender o que elas são conceitualmente em Lawtex: Imagine que alguém queira fazer, em uma planilha, um cadastro de clientes de uma loja. Intuitivamente, essa pessoa faria uma tabela com uma coluna para nome, outra para telefone, outra para endereço, outra para produtos comprados etc. Cada pessoa seria uma linha no cadastro. Quando um cliente novo surgisse, o lojista acrescentaria mais uma linha, perguntando ao cliente a informação de cada uma das colunas. Como essa tabela comporta tantos clientes quanto necessário, já podemos antever que a forma de fazer algo análogo será com um vetor, mas há algo faltando. Todos os clientes da tabela hipotética têm as mesmas colunas, ou seja, são compostos pelo mesmo conjunto de informações. Se eles são compostos pelo mesmo conjunto de informações, podemos dizer que eles partilham uma mesma estrutura. Esse é, portanto, o cenário.
A tabela é um vetor de estruturas. As colunas da tabela são os campos da estrutura. Cada linha da tabela é cada observação, ou seja, cada posição do vetor preenchida pelo usuário. Cada nova posição por ele adicionada será traduzida em uma nova linha na tabela.
Como as tabelas não são dinâmicas todas as colunas devem sempre existir para todas as observações. Logo, não podemos fazer tabelas com estruturas que usam ifstruct. Se quisermos fazer uma tabela com informações de estruturas com if temos de declarar uma estrutura dedicada apenas à tabela e atribuir em cada posição dela a informação vinda da estrutura mais complexa exibida ao usuário.
operations { table(|titulosDeDivida|.orderBy("valorDaDivida"), "vertical")}
Basta colocar entre os parênteses da tabela a tabela com o critério de ordenação e, separado por vírgula, o posicionamento vertical ou horizontal da tabela. Essa ordenação deve ser feita com base em um dos fields.
Tubes emuladores de comandos:
Os tubes emuladores de comandos servem para dar maior praticidade à programação. Em geral, eles combinam dois comandos em um só. Os comandos referidos são: print, if e foreach.
No caso do printIf, cuja sintaxe é:
printIf(<condicao>, "Texto caso a condição seja verdadeira", "Texto caso a condição seja falsa")
se combina print a if. O método printIf já foi explicado anteriormente nas seção de definição de Print.
O mesmo ocorre com o tube filter, visto que é a combinação entre um foreach e um if, isto é, percorre por todos os elementos de um vetor e retorna apenas aqueles que satisfaz uma certa condição. O método filter já foi explicado anteriormente nas seção de definição de Vector. Sua sintaxe é:
|vetor|.filter(<condicao>).
Finalmente, podemos combinar print com foreach através do tube vect2str, o qual toma um vetor e uma String correspondente ao separator especificado e imprime os elementos desse vetor devidamente formatados. Sua sintaxe é:
vect2str(|vetor|, "%f, %s, %p e %f")*.*
Vale salientar que caso o vetor seja do tipo Struct, a propriedade id
será impressa. Assim, cobrimos todos os pares de combinação entre as operações até aqui apresentadas.
Tubes emuladores e controladores de operandos:
Essa classe de tubes serve para manipular e emular operandos. Por exemplo, uma imagem é um tipo de operando que não existe na definição dos tipos de operandos em Lawtex. Mas ainda assim, é possível a inserção de imagens dentro do documento ou como anexo ao próprio template.
As imagens podem ser trechos de documentos que devem constar no template, por exemplo, provas que deverão ser inseridas ou mesmo o logo e nota de rodapé com informações do escritório/empresa que deverão constar no template. Abaixo serão indicadas as sintaxes que permitem a inserção de imagens no template.
As imagens inseridas pelo usuário do template deverão ser inseridas por meio da função upload. A função upload, exige que o usuário escolha um arquivo para ser posteriormente anexado ao documento por meio do comando attach. A sintaxe do upload contém quatro parâmetros que deverão ser abordados pelo engenheiro jurídico ou engenheira jurídica, caso contrário o sistema não reconhecerá o comando.
No primeiro parâmetro deverá indicar o nome que deverá ser perguntado ao usuário, como por exemplo "Termo de Rescisão (TRCT)".
Já no segundo parâmetro deverá constar o nome do arquivo com ".png", único formato de imagem aceito pelo sistema. Em outras palavras, todos os arquivos de imagens que serão inseridos no sistema necessariamente deverão constar no formato "png"ou não irão funcionar, como por exemplo "ImagemTRCT.png"
. São aceitos os formatos "png", "jpg", "bmp" e "pdf".
No terceiro parâmetro o engenheiro jurídico ou engenheira jurídica deverá inserir o request (conforme anteriormente explicado), ou seja, a pergunta ao usuário acerca do documento que será inserido, como por exemplo, "Faça o upload do Termo de Rescisão do Contrato de Trabalho (TRCT) no formato .PNG".
No quarto parâmetro, deverá ser inserido a forma como a mensagem deverá ser visualizada pelo usuário e a obrigatoriedade de inserir a imagem no template, conforme explicado acima na funcionalidade do mandatory. Esse parâmetro também comporta três opções: (1) visível e obrigatório, representada pela sintaxe +
; (2) visível, mas opcional, representada pela sintaxe &
e, por fim; (3) invisível, representada pela sintaxe −
.
Como exemplo, podemos indicar +
, em caso de visível e obrigatório o seu preenchimento. Neste caso específico, o template apenas será finalizado quando a imagem for inserida no sistema pelo usuário. Essa informação é importante no momento de pensar na estruturação do template, pois sempre contemplará a hipótese de que uma imagem necessariamente deverá ser inserida pelo usuário.
Abaixo, segue a sintaxe a ser utilizada no caso de upload, utilizando os exemplos trazidos ao longo das explicações dos parâmetros utilizados na funcionalidade:
operations { if (<descricaoVerbasTrabalhistasOuTRCT> == "Juntar cópia do TRCT aos autos") { upload("Termo de Rescisão (TRCT)", "ImagemTRCT.png", "Faça o upload do Termo de Rescisão do Contrato de Trabalho (TRCT) no formato .PNG", "+") }}
A funcionalidade do upload pode ser comparada à declaração de uma estrutura, pois nela constam as características que compõe a estrutura, sendo também necessária a indicação de como as variáveis da estrutura serão utilizadas na impressão. Nesse mesmo sentido, a funcionalidade upload precisa de uma funcionalidade para indicar o local em que será inserida a imagem, o que será indicado pela função attach.
Assim como a sintaxe do upload possui parâmetros fixos, a sintaxe do attach também possui quatro parâmetros que poderão ser inseridos pelo engenheiro jurídico ou engenheira jurídica. No entanto, apenas o primeiro parâmetro é obrigatório e os demais facultativos. Alguns comandos são os mesmos informados no upload, por isso, muita atenção para não inserir informações divergentes e inabilitar o comando.
No primeiro parâmetro deverá ser indicado o nome do arquivo com ".png", conforme indicado no segundo parâmetro do upload. Seguindo a linha de exemplos, seria "ImagemTRCT.png".
No segundo parâmetro o engenheiro jurídico ou engenheira jurídica deverá inserir um rótulo que explica o arquivo, como por exemplo "Termo de Rescisão (TRCT)".
Já no terceiro parâmetro deverá ser indicada a largura da imagem em pixels, por exemplo "600". Já o quarto parâmetro indicará a altura da imagem em pixels, por exemplo "900". Ambos os parâmetros devem ser setados com cuidado, pois distorcerá a imagem de entrada a fim de conformar esses parâmetros.
Abaixo, segue a sintaxe a ser utilizada no caso do comando attach, utilizando os exemplos trazidos ao longo das explicações dos parâmetros utilizados acima:
operations { if (<descricaoVerbasTrabalhistasOuTRCT> == "Juntar cópia do TRCT aos autos") { print "no Termo de Rescisão do Contrato de Trabalho (TRCT) abaixo.\n\b\b", attach("ImagemTRCT.png", "Termo de Rescisão do Contrato de Trabalho") }}
Não se esqueçam, o comando attach deverá ser inserido no local em que a respectiva imagem deverá aparecer no template.
É possível também subir uma imagem vinculada a um template e vinculá-la diretamente ao template. Para que isso seja possível, basta utilizar o attach de modo independente, tomando o cuidado para subir a imagem ao sistema com o mesmo nome definido no argumento do tube attach. Por exemplo, operamos um attach sem o upload correspondente:
operations { attach("ImagemTRCT.png", "Termo de Rescisão do Contrato de Trabalho")}
Logo após isso, podemos subir a imagem "ImagemTRCT.png" pelo sistema da Looplex. Apenas tome cuidado, pois o nome deve respeitar letras maiúsculas e minúsculas.
Nós e Modularização
Nós são agregações de lógicas, encapsulamento de códigos, modularizações. Essas modularizações permitem o uso reiterado de uma lógica em vários contextos e a organização do código. Os nós podem ser independentes, ou seja, simplesmente usados no corpo do código, ou podem funcionar como funções matemáticas, recebendo um input e devolvendo um output. Os nós podem ser interpretados como pequenos templates. Eles têm nome, descrição, declarações e operações próprias. Outra vantagem de modularizar o código é que podemos inserir tags para a lógica.
O Lawtex permite modularizar o código mediante a declaração de um nó na íntegra. São nós em Lawtex os Tópicos, Branches, Períodos, Dependências e Loops. Futuras referências a esses nós são feitas com o comando de uso, como por exemplo o uso de um tópico local previamente declarado:
use topic[TOP_RealidadeDosFatos].
Assim, se programarmos cada cláusula de um contrato como um tópico, além de podermos usá-las em outros contratos, podemos inserir em cada uma delas tags tais como referências a artigos, leis e precedentes a elas aplicáveis. Veremos cada tipo de nó a seguir.
Topic e Branch:
Esses nós são praticamente idênticos. A única diferença entre eles é que o Topic, imprime um título associado a ele, de forma que sempre que ele for utilizado esse título será impresso e o que estiver dentro dele será numerado em relação a ele. Vejamos um exemplo de branch:
declarations { branch[BRC\_DescricaoAutores] { name = "Descrição dos autores" operations { foreach(<reu> IN |reus|) where (separator = "%f2, %s2, %p2 e %l2") { print <reu.nome> & printIf(<reu.tipoDeSujeito> == "Pessoa natural", ", com inscrição no CPF sob o no " & <reu.cpf>, ", com inscrição no CNPJ sob o no " & <reu.cpf> ) }, if (|reus|.size() > 1) { print ", partes rés já qualificadas nos autos deste processo" } else { print ", parte ré já qualificada nos autos deste processo" } } }}
É o que usamos para cláusulas ou argumentos com títulos como "Do Dano Moral" ou "Representações e Garantias". O ideal é que, tirando assinaturas, cabeçalhos, endereçamentos e considerandos, você nunca imprima nada fora de tópicos. Os textos devem estar sempre dentro de tópicos e sempre entre \p
e \n
, para que sejam numerados, como já destacamos.
Seguimos agora com um exemplo de tópico:
declarations { topic[TOP_ClausulaInterpretacao] { name = "Interpretação" title = "Interpretação" operations { print "\pOs títulos e cabeçalhos deste Contrato servem meramente para referência e não devem limitar ou afetar o significado atribuído à cláusula a que fazem referência. Os termos desse contrato deverão ser interpretados com base na prática do mercado para esse tipo de operação, nunca podendo ser interpretados de forma a restringir as ", if (|devedores|.size() > 1) { print "obrigações das Partes Devedoras" } else { print "obrigações da Parte Devedora" }, print ".\n" } }}
Loop:
Esse tipo de nó encapsula uma estrutura de repetição para usos posteriores. O Loop é basicamente um branch que só permite o uso de um foreach. Tal foreach é escrito dentro de sua propriedade operation (está no singular por admitir apenas um único comando). O nó loop foi criado a fim de modularizar o comando foreach. Sua sintaxe é similar aos outros nós apresentados:
declarations { *loop[LOOP_EscreveAutor] { name = "Loop para escrever nomes de autores" description = "Este loop escreve uma lista de nomes de autores" separator = "%f1, %s2, %p2 e %l2." tags { "Qualificação", "Autor" } declarations { +|autores| : Vector[*Autor] { name = "Nome do autor" } } operation { foreach (<autor> IN |autores|) { print <autor.nome> } } }}
Conforme se observa, destaca-se a propriedade separator (a mesma usada na cláusula where) para fins de separar elementos do vetor por vírgulas e outros elementos conectivos da língua portuguesa. Vale salientar que as tags descritas nesse código, servem para indexar buscas e estão disponíveis para todos os nós.
Dependency:
Já a Dependência encapsula uma estrutura de decisão. Observe que o uso de nós ajudam a deixar o código mais organizado. Sua sintaxe é similar aos outros nós apresentados:
declarations { *dependency[DEP_QualificacaoLogradouro] { declarations { +<endereco> : \*Endereco } operation { if (<endereco.pais> == "Brasil") { use branch[BRC\_QualificacaoLogradouroBrasileiro] } else { use branch[BRC\_QualificacaoLogradouroEstrangeiro] } } }}
Dependency é um branch que só permite o uso de um if dentro da propriedade operation (note que também é no singular). Esse exemplo foi propositalmente feito usando branches declarados anteriormente. O uso de um nó com nome intuitivo (nesse caso um branch) é de fundamental importância para compreensão macro do código. Modularização é sempre um bom caminho a se tomar. Essa dependência é global, devidamente sinalizada com asterisco.
Period:
Por fim, o Period encapsula um comando de impressão: print. Observe por fim a declaração de um período:
declarations { period[PER_QualificacaoLogradouroBrasileiro] { description = "Período de qualificação de logradouro brasileiro." tags {"Logradouro", "Endereço", "Localização", "Brasileiro", "Brasileira"} declarations { +<endereco> : *Endereco } operation { print { [version = <endereco.principal> & ", " & <endereco.numero> & printIf (<endereco.complemento>.isNotEmpty(), " " & <endereco.complemento> & ",", "") & <endereco.bairro> & " CEP no~" & <endereco.codigoPostal> & ", na Cidade de " & <endereco.cidade> & ", " & <endereco.uf> & ", " & <endereco.pais>] } } }}
Esse período é local, pois não está sinalizado com asterisco. Além disso, ele possui apenas uma versão declarada. Múltiplas versões podem ser declaradas e setada com o tube printVersion(x), onde x é um número que indica a ordem da versão a ser impressa a partir daquele momento. Note que operation também está no singular, assim como em loops e dependências.
Se ao invés de compormos grandes aglomerados de operações e lógicas em branches complexos, criarmos um nó para cada conjunto de operações com um sentido comum, o código será lido com mais facilidade e o reaproveitamento também será facilitado. Para ver um exemplo de cada um desses nós, basta usá-los no snippet. Você não verá grandes diferenças com os demais.
Escopo:
Considerar o escopo onde os operandos estão sendo declarados e operados é de fundamental importância para o gerenciamento do fluxos de informações de um documento. Saber aonde foram declaradas as variáveis e os objetos, além de ser essencial para estrutura do template, também evita erros inesperados.
É comum nos depararmos com muitos erros de declaração ocasionados por escopos inadequados de operandos. Esses erros ocorrem porque o sistema não encontra o operando respectivo desde o onde foi usado até o nó raiz na árvore que define o template. Nesse caso, o nó raiz do template é o metainfo.
Os nós locais costumam ser declarados no metainfo (ou outro nó) e utilizados no ponto em que se deseja seu texto, contanto que esteja hierarquicamente contextualizado. Já os nós globais podem ser declarados até mesmo fora de qualquer áreas de declaração (declarations).
Lembre-se que quando falamos da topologia do template dissemos que tudo que você for utilizar em todo o template deveria ser declarado no metainfo. Isso porque o que for declarado, por exemplo, no corpo do documento, não será visível no cabeçalho e vice-versa. Com os nós a lógica é a mesma. O que for declarado em um nó não será visível para o outro, a não ser que um nó seja interno ao outro.
O que delimita se um nó é interno ao outro ou não é o fluxo operativo, isto é, se um nós foi usado dentro de outro ou não. Lembrando que a sintaxe de uso de um nó é através do comando use
.
Para nós globais, as informações utilizadas sempre virão do contexto em que forem usados. Dessa forma, essas informações terão a notação externs
, de forma que o uso do componente em um template precisa informar quais informações do template correspondem às informações externas do nó global utilizado. Segue um exemplo bem simples de declaração e uso de nós.
Componentes globais e componentes locais:
Como já destacamos, existem componentes globais, ou seja, há estruturas e nós que podem ser utilizados em templates. Isso acelera a produção de novos templates, pois componentes utilizados em mais de um documento podem ser simplesmente reaproveitados. Além disso se atualizamos o componente, todos os templates que utilizam o componente serão automaticamente atualizados.
Como esse material é introdutório não estudaremos com profundidade o funcionamento dos componentes globais, apenas descreveremos quais são suas funcionalidades para que você saiba pelo menos quais informações buscar nos próximos materiais. No entanto, segue um exemplo para que você entenda um pouco mais sobre escopos de nós globais e locais.
declarations { +<stringDeFora> : String, topic[TOP_Local] { operations { +<stringInterna> : String } declarations { <stringDeFora> = "Esse texto será recuperado lá fora", <stringInterna> = "Esse texto será perdido lá fora" } }, *topic[TOP_Global] { operations { +<stringExterna> : externs String, } declarations { <stringExterna> = "Esse texto será recuperado lá fora" } }}operations { use topic[TOP_Local], use *topic[TOP_Global] where (<stringExterna> : <stringDeFora>)}
O uso das variáveis dentro dos tópicos obedecem o seguinte escopo:
Dentro do tópico
TOP_Local
, a variável<stringInterna>
tem visibilidade restrita ao tópico. Uma vantagem do tópico local é a visibilidade irrestrita de todos os operandos declarados ao longo de todos os nós da rota de execução. Assim, o tópico local consegue manipular livremente<stringDeFora>
.Dentro do tópico
*TOP_Global
por sua vez, a variável<stringExterna>
é apenas um "proxy" (um substituto) para o operando externo a ser acoplado dentro da diretivawhere
. Nesse caso, a<stringExterna>
é apenas um operando que representa<stringDeFora>
. Esse acoplamento garante que qualquer alteração em<stringExterna>
feita dentro do tópico global, reflita integralmente em<stringDeFora>
.
Para usar essas componentes declaradas (Lists e Structs), basta tratá-las como um tipo primitivo, ou seja, declarar os operandos de tal maneira:
declarations { +<objetoLocal> : StructLocal { name = "Nome do objeto local" request = "Informe os dados do objeto local" }, +<objetoGlobal> : *StructGlobal { name = "Nome do objeto global" request = "Informe os dados do objeto global" }, +<objetoLocal> : ListLocal { name = "Nome da lista local" request = "Escolha um elemento da lista local" atomic = true }, +<objetoGlobal> : *StructGlobal { name = "Nome da lista global" request = "Escolha alguns elementos da lista global" atomic = false }}
Acesso a informações internas a nós e às raízes de estruturas:
É possível acessar informações que estão fora do escopo ou permitir que uma estrutura acesse uma informação da estrutura que a contém, chamada de raiz. Nesses casos excepcionais você deve identificar em que parte do template ou em qual nó está a informação que você deseja. Da mesma forma, na estrutura é preciso informar que a informação desejada está na raiz.
declarations { +<strAprovacaoDeContas> : Struct { name = "Aprovação de contas" fields { if ("Aline Mendes" IN [Metainfo:convocacaoPresenca.membrosDoConselho] OR [Metainfo:convocacaoPresenca.temCemPorCento] == true) { +[comoVotouFC] : List ("Aprovou", "Rejeitou", "Se absteve") { name = "Voto da Sr. Aline Mendes" request = "Indique como a Sra. Aline Mendes deliberou sobre aprovação de contas" atomic = true default = "Aprovou" } } } }}