4.1.2 Representando Componentes
Os programadores escrevem programas como texto, ou seja, sequências de caracteres, inseridas em um ambiente de programação ou um editor de texto. Para executar nosso avaliador, precisamos começar com uma representação desse texto do programa como um valor JavaScript. Na seção 1.1.4 introduzimos strings para representar texto. Gostaríamos de avaliar programas como "const size = 2; 5 * size;" da seção 1.1.2. Infelizmente, esse texto do programa não fornece estrutura suficiente para o avaliador. Neste exemplo, as partes do programa "size = 2" e "5 * size" parecem semelhantes, mas carregam significados muito diferentes. Funções de sintaxe abstrata como declaration_value_expression seriam difíceis e propensas a erros para implementar examinando o texto do programa. Nesta seção, portanto, introduzimos uma função parse que traduz o texto do programa para uma representação de lista rotulada, reminiscente dos dados rotulados da seção 2.4.2.
Por exemplo, a aplicação de parse à string do programa acima produz uma estrutura de dados que reflete a estrutura do programa: uma sequência consistindo de uma declaração de constante associando o nome size com o valor 2 e uma multiplicação.
parse("const size = 2; 5 * size;");
list("sequence",
list(list("constant_declaration",
list("name", "size"), list("literal", 2)),
list("binary_operator_combination", "*",
list("literal", 5), list("name", "size"))))
As funções de sintaxe usadas pelo avaliador acessam a representação de lista rotulada produzida por parse.
O avaliador lembra o programa de diferenciação simbólica discutido na seção 2.3.2. Ambos os programas operam em dados simbólicos. Em ambos os programas, o resultado de operar em um objeto é determinado operando recursivamente nas partes do objeto e combinando os resultados de uma maneira que depende do tipo do objeto. Em ambos os programas usamos abstração de dados para desacoplar as regras gerais de operação dos detalhes de como os objetos são representados. No programa de diferenciação isso significava que a mesma função de diferenciação poderia lidar com expressões algébricas na forma prefixada, na forma infixada ou em alguma outra forma. Para o avaliador, isso significa que a sintaxe da linguagem sendo avaliada é determinada exclusivamente por parse e as funções que classificam e extraem partes das listas rotuladas produzidas por parse.
Figura: Abstração de sintaxe no avaliador. A barreira de abstração 1 (predicados e seletores) separa o avaliador da representação de lista rotulada. A barreira de abstração 2 (parse) separa a representação de lista da representação de string.
A figura acima representa a barreira de abstração formada pelos predicados e seletores de sintaxe, que interfaceiam o avaliador à representação de lista rotulada de programas, que por sua vez é separada da representação de string por parse. Abaixo descrevemos o parsing de componentes do programa e listamos os predicados e seletores de sintaxe correspondentes, bem como construtores se forem necessários.
Expressão Literal
Expressões literais são analisadas em listas rotuladas com a tag "literal" e o valor real.
⟪ literal-expression ⟫ = list("literal", value)
onde value é o valor JavaScript representado pela string literal-expression. Aqui ⟪ literal-expression ⟫ denota o resultado do parsing da string literal-expression.
parse("1;");
list("literal", 1)
parse("'hello world';");
list("literal", "hello world")
parse("null;");
list("literal", null)
O predicado de sintaxe para expressões literais é is_literal.
function is_literal(component) {
return is_tagged_list(component, "literal");
}
É definido em termos da função is_tagged_list, que identifica listas que começam com uma string designada:
function is_tagged_list(component, the_tag) {
return is_pair(component) && head(component) === the_tag;
}
O segundo elemento da lista que resulta do parsing de uma expressão literal é seu valor JavaScript real. O seletor para recuperar o valor é literal_value.
function literal_value(component) {
return head(tail(component));
}
No restante desta seção, apenas listamos os predicados e seletores de sintaxe, e omitimos suas declarações se eles apenas acessam os elementos óbvios da lista.
Fornecemos um construtor para literais, que será útil:
function make_literal(value) {
return list("literal", value);
}
Nomes
A representação de lista rotulada para nomes inclui a tag "name" como primeiro elemento e a string representando o nome como segundo elemento.
⟪ name ⟫ = list("name", symbol)
onde symbol é uma string que contém os caracteres que compõem o name como escrito no programa. O predicado de sintaxe para nomes é is_name.
function is_name(component) {
return is_tagged_list(component, "name");
}
O símbolo é acessado usando o seletor symbol_of_name.
function symbol_of_name(component) {
return head(tail(component));
}
Fornecemos um construtor para nomes, a ser usado por operator_combination_to_application:
function make_name(symbol) {
return list("name", symbol);
}
Declarações de Expressão
Não precisamos distinguir entre expressões e declarações de expressão. Consequentemente, parse pode ignorar a diferença entre os dois tipos de componentes:
⟪ expression; ⟫ = ⟪ expression ⟫
Aplicações de Função
Aplicações de função são analisadas da seguinte forma:
⟪ fun-expr(arg-expr₁, …, arg-exprₙ) ⟫ =
list("application",
⟪ fun-expr ⟫,
list(⟪ arg-expr₁ ⟫, …, ⟪ arg-exprₙ ⟫))
Declaramos is_application como o predicado de sintaxe e function_expression e arg_expressions como os seletores.
function is_application(component) {
return is_tagged_list(component, "application");
}
function function_expression(component) {
return head(tail(component));
}
function arg_expressions(component) {
return head(tail(tail(component)));
}
Adicionamos um construtor para aplicações de função, a ser usado por operator_combination_to_application:
function make_application(function_expression, argument_expressions) {
return list("application",
function_expression, argument_expressions);
}
Condicionais
Expressões condicionais são analisadas da seguinte forma:
⟪ predicate ? consequent-expression : alternative-expression ⟫ =
list("conditional_expression",
⟪ predicate ⟫,
⟪ consequent-expression ⟫,
⟪ alternative-expression ⟫)
Similarmente, declarações condicionais são analisadas da seguinte forma:
⟪ if (predicate) consequent-block else alternative-block ⟫ =
list("conditional_statement",
⟪ predicate ⟫,
⟪ consequent-block ⟫,
⟪ alternative-block ⟫)
O predicado de sintaxe is_conditional retorna verdadeiro para ambos os tipos de condicionais, e os seletores conditional_predicate, conditional_consequent e conditional_alternative podem ser aplicados a ambos os tipos.
function is_conditional(component) {
return is_tagged_list(component, "conditional_expression") ||
is_tagged_list(component, "conditional_statement");
}
function conditional_predicate(component) {
return list_ref(component, 1);
}
function conditional_consequent(component) {
return list_ref(component, 2);
}
function conditional_alternative(component) {
return list_ref(component, 3);
}
Expressões Lambda
Uma expressão lambda cujo corpo é uma expressão é analisada como se o corpo consistisse de um bloco contendo uma única declaração de retorno cuja expressão de retorno é o corpo da expressão lambda.
⟪ (name₁, …, nameₙ) => expression ⟫ =
⟪ (name₁, …, nameₙ) => { return expression; } ⟫
Uma expressão lambda cujo corpo é um bloco é analisada da seguinte forma:
⟪ (name₁, …, nameₙ) => block ⟫ =
list("lambda_expression",
list(⟪ name₁ ⟫, …, ⟪ nameₙ ⟫),
⟪ block ⟫)
O predicado de sintaxe é is_lambda_expression e o seletor para o corpo da expressão lambda é lambda_body. O seletor para os parâmetros, chamado lambda_parameter_symbols, adicionalmente extrai os símbolos dos nomes.
function lambda_parameter_symbols(component) {
return map(symbol_of_name, head(tail(component)));
}
Sequências
Uma sequência é um componente composto consistindo de zero ou mais componentes a serem avaliados em ordem. Uma sequência aparece como o corpo de um bloco ou como o corpo de uma expressão lambda (que na verdade sempre é um bloco). A função parse representa sequências como listas rotuladas.
⟪ component₁ ... componentₙ ⟫ = list(⟪ component₁ ⟫, ..., ⟪ componentₙ ⟫)
Para sequências, fornecemos um construtor make_sequence (usado pela função switch_to_cond do exercício adicional sobre switch), um predicado is_sequence, e seletores sequence_statements, is_empty_sequence, first_statement, e rest_statements.
function make_sequence(stmts) {
return list("sequence", stmts);
}
function is_sequence(stmt) {
return is_tagged_list(stmt, "sequence");
}
function sequence_statements(stmt) {
return head(tail(stmt));
}
function is_empty_sequence(stmts) {
return is_null(stmts);
}
function is_last_statement(stmts) {
return is_null(tail(stmts));
}
function first_statement(stmts) {
return head(stmts);
}
function rest_statements(stmts) {
return tail(stmts);
}
Blocos e Declarações de Retorno
Blocos são analisados em listas rotuladas, começando com a string "block". O corpo de um bloco é uma sequência de declarações.