Objetivo: Interpretador de um subconjunto de Python escrito em Python capaz de interpretar o próprio código.
O subconjunto de Python em questão é o descrito pela gramatica PEG extendida a seguir. Que tal subconjunto seja nomeado spy, vindo de Subset of Python.
WhiteSpace=''|'\r'|Comment.
Comment='#' {ascii} nl.
nl='\n'.
Module=Block.
Block= { :StatementNL }.
NL=nl {nl}.
Statement=While|DoWhile|If|Atrib_Expr|Return|Class|Func|Import|FromImport|Pass.
Pass='pass'.
Import='import'IdList.
FromImport='from'id'import'IdList.
IdList=id {CommaNLid} [CommaNL].
DoWhile='do'':' NL >Block 'while' Expr.While = 'while' Expr ':' NL >Block.If = 'if' Expr ':' NL >Block {:Elif} [:Else].Elif = 'elif' Expr ':' NL >Block.Else = 'else'':' NL >Block.Atrib_Expr = Expr [Assign_Op Expr].Return = 'return' Expr.Class = 'class' id ':' NL >Methods.Methods = {:Func}.Func = 'def' id Arguments ':' NL >Block.Arguments = '(' [NL] [ArgList] ')'.ArgList = Arg {CommaNL Arg} [CommaNL].Arg = 'self'|id.
ExprList=Expr {CommaNLExpr} [CommaNL].
CommaNL=',' [NL].
Expr=And {'or'And}.
And=Comp {'and'Comp}.
Comp=Sum {compOpSum}.
compOp='=='|'!='|'>'|'>='|'<'|'<='|'in'.
Sum=Mult {sumOpMult}.
sumOp='+'|'-'.
Mult=UnaryPrefix {multOpUnaryPrefix}.
multOp='*'|'/'|'%'.
UnaryPrefix= {Prefix} UnarySuffix.
UnarySuffix=Term {Suffix}.
Term='self'|'None'|bool|num|str|id|NestedExpr|Dict|List.
prefix='not'|'-'.
Suffix=Call|DotAccess|Index.
Call='(' [NL] [ExprList] ')'.
Index='[' [NL] Expr [':'Expr] ']'.
DotAccess='.'id.
List='[' [NL] ExprList']'.
Dict='{' [NL] KeyValue_List'}'.
KeyValue_List=KeyValue_Expr {CommaNLKeyValue_Expr} [CommaNL].
KeyValue_Expr=Expr [':'Expr].
NestedExpr='('Expr')'.
Assign_Op='='|'+='|'-='|'*='|'/='|'%='.
bool='True'|'False'.
num= /[0-9]+/.
str='"'insideString*'"'.
insideString=ascii|escapes.
escapes='\\n'|'\\"'id= /[a-zA-Z_][a-zA-Z0-9_]*/.Nessa gramática, coisas entre // são expressões regulares,
coisas entre '' são terminais, coisas entre [] são opcionais,
coisas entre {} podem ser repetidas de 0 a N vezes,
o uso de | representa escolhas entre produções (leia-se "ou" ou "or"),
o uso de = representa a definição de uma regra (production) e cada
regra é terminada com ..
Coisas especiais:
- a regra
WhiteSpacedefine caracteres que podem ser ignorados, isso inclui espaço e comentários. - o uso de
>representa uma indentação obrigatória, inspirado pelo artigo Indentation-Sensitive Parsing for Parsec. - o uso de
:indica que a produção seguinte tem que ter o mesmo nivel de indentação que produção atual.
O nivel de indentação de uma produção é definido pelo primeiro token consumido por aquela produção. Quando escrevemos:
Block= { :StatementNL }.Queremos dizer que o nível de indentação de cada Statement
precisa ser necessariamente igual da produção Block.
A indentação de Block é definida pelo primeiro Statement do bloco,
que consome o primeiro token.
Por outro lado, quando escrevemos:
While='while'Expr':'NL >Block.Queremos dizer que o nível de indentação da produção Block tem que estar
estritamente maior que o nível de While, nesse caso, a indentação do
da produção While é definida pela palavra chave 'while'.
O interpretador vai expor apenas uma função evaluate
que toma um escopo de funções built-in, um dicionário de módulos e uma string,
ie, algo tipo evaluate(builtin:scope, dict:str->str, name:str, verbose:bool)
se python tivesse tipos estáticos.
O parametro name tem de estar em dict, e dict[name]
vai ser o módulo pelo qual o interpretador começa a executar.
Qualquer módulo importado terá de estar em dict,
e será importado por nome, ie, import lexer requer que
dict["lexer"] seja uma string representando o módulo lexer.
Desse jeito o interpretador não tem que saber nada sobre arquivos no geral, e pode só ser feliz executando código no mundinho dele, enquanto um script rodando em CPython pode ler uma pasta inteira de arquivos e passar para o auto-interpretador.