También disponible en GitHub .
Necesitas Dart 1.12 y Pub. Simplemente ejecute pub get
para descargar la única dependencia, una biblioteca de análisis.
¡Esperemos que este dure más de 30 minutos! : O
El idioma
El zinc está orientado a redefinir operadores. ¡Puede redefinir todos los operadores en el idioma fácilmente!
La estructura de un programa típico de zinc se ve así:
let
<operator overrides>
in <expression>
Solo hay dos tipos de datos: enteros y conjuntos. No existe un conjunto literal, y los conjuntos vacíos no están permitidos.
Expresiones
Las siguientes son expresiones válidas en Zinc:
Literales
El zinc admite todos los literales enteros normales, como 1
y -2
.
Variables
El zinc tiene variables (como la mayoría de los idiomas). Para hacer referencia a ellos, solo use el nombre. De nuevo como la mayoría de los idiomas!
Sin embargo, hay una variable especial llamada S
que se comporta como Pyth
Q
. Cuando lo use por primera vez, leerá en una línea desde la entrada estándar y lo interpretará como un conjunto de números. Por ejemplo, la línea de entrada 1234231
se convertiría en el conjunto {1, 2, 3, 4, 3, 2, 1}
.
¡¡¡NOTA IMPORTANTE!!! En algunos casos, un literal al final de una anulación del operador se analiza incorrectamente, por lo que debe rodearse con paréntesis.
Operaciones binarias
Se admiten las siguientes operaciones binarias:
- Además a través de
+
: 1+1
.
- La resta a través de
-
: 1-1
.
- La multiplicación a través de
*
: 2*2
.
- División a través de
/
: 4/2
.
- La igualdad con
=
: 3=3
.
Además, también se admite la siguiente operación unaria:
La precedencia es siempre correcta asociativa. Puede usar paréntesis para anular esto.
Solo la igualdad y la longitud trabajan en sets. Cuando intenta obtener la longitud de un número entero, obtendrá el número de dígitos en su representación de cadena.
Establecer comprensiones
Para manipular conjuntos, Zinc ha comprendido conjuntos. Se ven así:
{<variable>:<set><clause>}
Una cláusula es una cláusula when o una cláusula sort.
Una cláusula when parece ^<expression>
. La expresión que sigue al símbolo de intercalación debe dar como resultado un número entero. El uso de la cláusula when tomará solo los elementos del conjunto para los cuales expression
no es cero. Dentro de la expresión, la variable _
se establecerá en el índice actual del conjunto. Es más o menos equivalente a este Python:
[<variable> for _, <variable> in enumerate(<set>) when <expression> != 0]
Una cláusula de ordenación , que parece $<expression>
, ordena el conjunto descendiendo por el valor de <expression>
. Es igual a este Python:
sorted(<set>, key=lambda <variable>: <expression>)[::-1]
Aquí hay algunos ejemplos de comprensión:
Anulaciones
Las anulaciones de operador le permiten redefinir operadores. Se ven así:
<operator>=<operator>
o:
<variable><operator><variable>=<expression>
En el primer caso, puede definir un operador para que sea igual a otro operador. Por ejemplo, puedo definir +
restar realmente a través de:
+=-
Cuando haces esto, puedes redefinir un operador para que sea un operador mágico . Hay dos operadores mágicos:
join
toma un conjunto y un entero y se une al contenido del conjunto. Por ejemplo, unirse {1, 2, 3}
con 4
dará como resultado el entero 14243
.
cut
también toma un conjunto y un número entero y dividirá el conjunto en cada aparición del número entero. Usando cut
on {1, 3, 9, 4, 3, 2}
y 3
creará {{1}, {9, 4}, {2}}
... PERO cualquier conjunto de un solo elemento se aplana, por lo que el resultado será realmente {1, {9, 4}, 2}
.
Aquí hay un ejemplo que redefine al +
operador para que signifique join
:
+=join
Para el último caso, puede redefinir un operador a la expresión dada. Como ejemplo, esto define la operación más para agregar los valores y luego agregar 1:
x+y=1+:x+:y
Pero lo que es +:
? Puede agregar los dos puntos :
a un operador para usar siempre la versión incorporada. Este ejemplo usa la +
vía incorporada +:
para sumar los números, luego agrega un 1 (recuerde, todo es asociativo correcto).
Anular el operador de longitud se parece a:
#x=<expression>
Tenga en cuenta que casi todas las operaciones integradas (excepto la igualdad) utilizarán este operador de longitud para determinar la longitud del conjunto. Si lo definiste como:
#x=1
cada parte de Zinc que funciona en conjuntos, excepto =
que solo funcionaría en el primer elemento del conjunto que se le dio.
Anulaciones múltiples
Puede anular varios operadores separándolos con comas:
let
+=-,
*=/
in 1+2*3
Impresión
No puede imprimir directamente nada en Zinc. Se in
imprimirá el resultado de la siguiente expresión . Los valores de un conjunto se concatenarán con separador. Por ejemplo, toma esto:
let
...
in expr
Si expr
es el conjunto {1, 3, {2, 4}}
, 1324
se imprimirá en la pantalla una vez que finalice el programa.
Poniendolo todo junto
Aquí hay un programa simple de Zinc que parece agregar 2+2
pero hace que el resultado sea 5:
let
x+y=1+:x+:y
in 1+2
El interprete
Esto entra en bin/zinc.dart
:
import 'package:parsers/parsers.dart';
import 'dart:io';
// An error.
class Error implements Exception {
String cause;
Error(this.cause);
String toString() => 'error in Zinc script: $cause';
}
// AST.
class Node {
Obj interpret(ZincInterpreter interp) => null;
}
// Identifier.
class Id extends Node {
final String id;
Id(this.id);
String toString() => 'Id($id)';
Obj interpret(ZincInterpreter interp) => interp.getv(id);
}
// Integer literal.
class IntLiteral extends Node {
final int value;
IntLiteral(this.value);
String toString() => 'IntLiteral($value)';
Obj interpret(ZincInterpreter interp) => new IntObj(value);
}
// Any kind of operator.
class Anyop extends Node {
void set(ZincInterpreter interp, OpFuncType func) {}
}
// Operator.
class Op extends Anyop {
final String op;
final bool orig;
Op(this.op, [this.orig = false]);
String toString() => 'Op($op, $orig)';
OpFuncType get(ZincInterpreter interp) =>
this.orig ? interp.op0[op] : interp.op1[op];
void set(ZincInterpreter interp, OpFuncType func) { interp.op1[op] = func; }
}
// Unary operator (len).
class Lenop extends Anyop {
final bool orig;
Lenop([this.orig = false]);
String toString() => 'Lenop($orig)';
OpFuncType get(ZincInterpreter interp) =>
this.orig ? interp.op0['#'] : interp.op1['#'];
void set(ZincInterpreter interp, OpFuncType func) { interp.op1['#'] = func; }
}
// Magic operator.
class Magicop extends Anyop {
final String op;
Magicop(this.op);
String toString() => 'Magicop($op)';
Obj interpret_with(ZincInterpreter interp, Obj x, Obj y) {
if (op == 'cut') {
if (y is! IntObj) { throw new Error('cannot cut int with non-int'); }
if (x is IntObj) {
return new SetObj(x.value.toString().split(y.value.toString()).map(
int.parse));
} else {
assert(x is SetObj);
List<List<Obj>> res = [[]];
for (Obj obj in x.vals(interp)) {
if (obj == y) { res.add([]); }
else { res.last.add(obj); }
}
return new SetObj(new List.from(res.map((l) =>
l.length == 1 ? l[0] : new SetObj(l))));
}
} else if (op == 'join') {
if (x is! SetObj) { throw new Error('can only join set'); }
if (y is! IntObj) { throw new Error('can only join set with int'); }
String res = '';
for (Obj obj in x.vals(interp)) {
if (obj is! IntObj) { throw new Error('joining set must contain ints'); }
res += obj.value.toString();
}
return new IntObj(int.parse(res));
}
}
}
// Unary operator (len) expression.
class Len extends Node {
final Lenop op;
final Node value;
Len(this.op, this.value);
String toString() => 'Len($op, $value)';
Obj interpret(ZincInterpreter interp) =>
op.get(interp)(interp, value.interpret(interp), null);
}
// Binary operator expression.
class Binop extends Node {
final Node lhs, rhs;
final Op op;
Binop(this.lhs, this.op, this.rhs);
String toString() => 'Binop($lhs, $op, $rhs)';
Obj interpret(ZincInterpreter interp) =>
op.get(interp)(interp, lhs.interpret(interp), rhs.interpret(interp));
}
// Clause.
enum ClauseKind { Where, Sort }
class Clause extends Node {
final ClauseKind kind;
final Node expr;
Clause(this.kind, this.expr);
String toString() => 'Clause($kind, $expr)';
Obj interpret_with(ZincInterpreter interp, SetObj set, Id id) {
List<Obj> res = [];
List<Obj> values = set.vals(interp);
switch (kind) {
case ClauseKind.Where:
for (int i=0; i<values.length; i++) {
Obj obj = values[i];
interp.push_scope();
interp.setv(id.id, obj);
interp.setv('_', new IntObj(i));
Obj x = expr.interpret(interp);
interp.pop_scope();
if (x is IntObj) {
if (x.value != 0) { res.add(obj); }
} else { throw new Error('where clause condition must be an integer'); }
}
break;
case ClauseKind.Sort:
res = values;
res.sort((x, y) {
interp.push_scope();
interp.setv(id.id, x);
Obj x_by = expr.interpret(interp);
interp.setv(id.id, y);
Obj y_by = expr.interpret(interp);
interp.pop_scope();
if (x_by is IntObj && y_by is IntObj) {
return x_by.value.compareTo(y_by.value);
} else { throw new Error('sort clause result must be an integer'); }
});
break;
}
return new SetObj(new List.from(res.reversed));
}
}
// Set comprehension.
class SetComp extends Node {
final Id id;
final Node set;
final Clause clause;
SetComp(this.id, this.set, this.clause);
String toString() => 'SetComp($id, $set, $clause)';
Obj interpret(ZincInterpreter interp) {
Obj setobj = set.interpret(interp);
if (setobj is SetObj) {
return clause.interpret_with(interp, setobj, id);
} else { throw new Error('set comprehension rhs must be set type'); }
}
}
// Operator rewrite.
class OpRewrite extends Node {
final Anyop op;
final Node value;
final Id lid, rid; // Can be null!
OpRewrite(this.op, this.value, [this.lid, this.rid]);
String toString() => 'OpRewrite($lid, $op, $rid, $value)';
Obj interpret(ZincInterpreter interp) {
if (lid != null) {
// Not bare.
op.set(interp, (interp,x,y) {
interp.push_scope();
interp.setv(lid.id, x);
if (rid == null) { assert(y == null); }
else { interp.setv(rid.id, y); }
Obj res = value.interpret(interp);
interp.pop_scope();
return res;
});
} else {
// Bare.
if (value is Magicop) {
op.set(interp, (interp,x,y) => value.interpret_with(interp, x, y));
} else {
op.set(interp, (interp,x,y) => (value as Anyop).get(interp)(x, y));
}
}
return null;
}
}
class Program extends Node {
final List<OpRewrite> rws;
final Node expr;
Program(this.rws, this.expr);
String toString() => 'Program($rws, $expr)';
Obj interpret(ZincInterpreter interp) {
rws.forEach((n) => n.interpret(interp));
return expr.interpret(interp);
}
}
// Runtime objects.
typedef Obj OpFuncType(ZincInterpreter interp, Obj x, Obj y);
class Obj {}
class IntObj extends Obj {
final int value;
IntObj(this.value);
String toString() => 'IntObj($value)';
bool operator==(Obj rhs) => rhs is IntObj && value == rhs.value;
String dump() => value.toString();
}
class SetObj extends Obj {
final List<Obj> values;
SetObj(this.values) {
if (values.length == 0) { throw new Error('set cannot be empty'); }
}
String toString() => 'SetObj($values)';
bool operator==(Obj rhs) => rhs is SetObj && values == rhs.values;
String dump() => values.map((x) => x.dump()).reduce((x,y) => x+y);
List<Obj> vals(ZincInterpreter interp) {
Obj lenobj = interp.op1['#'](interp, this, null);
int len;
if (lenobj is! IntObj) { throw new Error('# operator must return an int'); }
len = lenobj.value;
if (len < 0) { throw new Error('result of # operator must be positive'); }
return new List<Obj>.from(values.getRange(0, len));
}
}
// Parser.
class ZincParser extends LanguageParsers {
ZincParser(): super(reservedNames: ['let', 'in', 'join', 'cut']);
get start => prog().between(spaces, eof);
get comma => char(',') < spaces;
get lp => symbol('(');
get rp => symbol(')');
get lb => symbol('{');
get rb => symbol('}');
get colon => symbol(':');
get plus => symbol('+');
get minus => symbol('-');
get star => symbol('*');
get slash => symbol('/');
get eq => symbol('=');
get len => symbol('#');
get in_ => char(':');
get where => char('^');
get sort => char('\$');
prog() => reserved['let'] + oprw().sepBy(comma) + reserved['in'] + expr() ^
(_1,o,_2,x) => new Program(o,x);
oprw() => oprw1() | oprw2() | oprw3();
oprw1() => (basicop() | lenop()) + eq + (magicop() | op()) ^
(o,_,r) => new OpRewrite(o,r);
oprw2() => (id() + op() + id()).list + eq + expr() ^
(l,_,x) => new OpRewrite(l[1], x, l[0], l[2]);
oprw3() => lenop() + id() + eq + expr() ^ (o,a,_,x) => new OpRewrite(o, x, a);
magicop() => (reserved['join'] | reserved['cut']) ^ (s) => new Magicop(s);
basicop() => (plus | minus | star | slash | eq) ^ (op) => new Op(op);
op() => (basicop() + colon ^ (op,_) => new Op(op.op, true)) | basicop();
lenop() => (len + colon ^ (_1,_2) => new Lenop(true)) |
len ^ (_) => new Lenop();
expr() => setcomp() | unop() | binop() | prim();
setcomp() => lb + id() + in_ + rec(expr) + clause() + rb ^
(_1,i,_2,x,c,_3) => new SetComp(i,x,c);
clausekind() => (where ^ (_) => ClauseKind.Where) |
(sort ^ (_) => ClauseKind.Sort);
clause() => clausekind() + rec(expr) ^ (k,x) => new Clause(k,x);
unop() => lenop() + rec(expr) ^ (o,x) => new Len(o,x);
binop() => prim() + op() + rec(expr) ^ (l,o,r) => new Binop(l,o,r);
prim() => id() | intlit() | parens(rec(expr));
id() => identifier ^ (i) => new Id(i);
intlit() => intLiteral ^ (i) => new IntLiteral(i);
}
// Interpreter.
class ZincInterpreter {
Map<String, OpFuncType> op0, op1;
List<Map<String, Obj>> scopes;
ZincInterpreter() {
var beInt = (v) {
if (v is IntObj) { return v.value; }
else { throw new Error('argument to binary operator must be integer'); }
};
op0 = {
'+': (_,x,y) => new IntObj(beInt(x)+beInt(y)),
'-': (_,x,y) => new IntObj(beInt(x)-beInt(y)),
'*': (_,x,y) => new IntObj(beInt(x)*beInt(y)),
'/': (_,x,y) => new IntObj(beInt(x)/beInt(y)),
'=': (_,x,y) => new IntObj(x == y ? 1 : 0),
'#': (i,x,_2) =>
new IntObj(x is IntObj ? x.value.toString().length : x.values.length)
};
op1 = new Map<String, OpFuncType>.from(op0);
scopes = [{}];
}
void push_scope() { scopes.add({}); }
void pop_scope() { scopes.removeLast(); }
void setv(String name, Obj value) { scopes[scopes.length-1][name] = value; }
Obj getv(String name) {
for (var scope in scopes.reversed) {
if (scope[name] != null) { return scope[name]; }
}
if (name == 'S') {
var input = stdin.readLineSync() ?? '';
var list = new List.from(input.codeUnits.map((c) =>
new IntObj(int.parse(new String.fromCharCodes([c])))));
setv('S', new SetObj(list));
return getv('S');
} else throw new Error('undefined variable $name');
}
}
void main(List<String> args) {
if (args.length != 1) {
print('usage: ${Platform.script.toFilePath()} <file to run>');
return;
}
var file = new File(args[0]);
if (!file.existsSync()) {
print('cannot open ${args[0]}');
return;
}
Program root = new ZincParser().start.parse(file.readAsStringSync());
ZincInterpreter interp = new ZincInterpreter();
var res = root.interpret(interp);
print(res.dump());
}
Y esto entra pubspec.yaml
:
name: zinc
dependencies:
parsers: any
Solución prevista
let
#x=((x=S)*(-2))+#:x,
/=cut
in {y:{x:S/0$#:x}^_=2}