Disponibile anche su GitHub .
Hai bisogno di Dart 1.12 e Pub. Basta eseguire pub get
per scaricare l'unica dipendenza, una libreria di analisi.
Speriamo che questo duri più di 30 minuti! : O
La lingua
Lo zinco è orientato alla ridefinizione degli operatori. Puoi ridefinire facilmente tutti gli operatori nella lingua!
La struttura di un tipico programma di zinco è simile a:
let
<operator overrides>
in <expression>
Esistono solo due tipi di dati: numeri interi e set. Non esiste un set letterale e gli insiemi vuoti non sono consentiti.
espressioni
Le seguenti sono espressioni valide in zinco:
letterali
Lo zinco supporta tutti i normali letterali interi, come 1
e -2
.
variabili
Lo zinco ha variabili (come la maggior parte delle lingue). Per fare riferimento a loro, basta usare il nome. Ancora una volta come la maggior parte delle lingue!
Tuttavia, esiste una variabile speciale chiamata S
che si comporta in qualche modo come quella di Pyth
Q
. Quando lo usi per la prima volta, leggerà in una riga dall'input standard e lo interpreterà come un insieme di numeri. Ad esempio, la riga di input 1234231
si trasformerebbe nel set {1, 2, 3, 4, 3, 2, 1}
.
NOTA IMPORTANTE!!! In alcuni casi, un valore letterale alla fine di una sostituzione dell'operatore viene analizzato in modo errato, quindi è necessario racchiuderlo tra parentesi.
Operazioni binarie
Sono supportate le seguenti operazioni binarie:
- Aggiunta via
+
: 1+1
.
- Sottrazione via
-
: 1-1
.
- MOLTIPLICAZIONE via
*
: 2*2
.
- Divisione via
/
: 4/2
.
- Parità con
=
: 3=3
.
Inoltre, è supportata anche la seguente operazione unaria:
La precedenza è sempre associativa giusta. È possibile utilizzare le parentesi per ignorare questo.
Solo l'uguaglianza e la lunghezza funzionano sui set. Quando si tenta di ottenere la lunghezza di un numero intero, si otterrà il numero di cifre nella sua rappresentazione di stringa.
Imposta le comprensioni
Al fine di manipolare i set, lo zinco ha impostato le comprensioni. Sembrano così:
{<variable>:<set><clause>}
Una clausola è una clausola when o una clausola di ordinamento.
Una clausola di quando assomiglia ^<expression>
. L'espressione che segue il punto di inserimento deve comportare un numero intero. L'uso della clausola when prenderà solo gli elementi nel set per i quali expression
è diverso da zero. All'interno dell'espressione, la variabile _
verrà impostata sull'indice corrente nel set. È approssimativamente equivalente a questo Python:
[<variable> for _, <variable> in enumerate(<set>) when <expression> != 0]
Una clausola di ordinamento , che assomiglia $<expression>
, ordina l'insieme in ordine decrescente per valore di <expression>
. È uguale a questo Python:
sorted(<set>, key=lambda <variable>: <expression>)[::-1]
Ecco alcuni esempi di comprensione:
Sostituzioni
Le sostituzioni operatore consentono di ridefinire gli operatori. Sembrano così:
<operator>=<operator>
o:
<variable><operator><variable>=<expression>
Nel primo caso, è possibile definire un operatore per eguagliare un altro operatore. Ad esempio, posso definire +
di sottrarre effettivamente tramite:
+=-
Quando lo fai, puoi ridefinire un operatore per essere un operatore magico . Esistono due operatori magici:
join
prende un set e un numero intero e unisce il contenuto del set. Ad esempio, l'unione {1, 2, 3}
con 4
comporterà il numero intero 14243
.
cut
accetta anche un set e un numero intero e partizionerà il set ad ogni occorrenza del numero intero. Usando cut
on {1, 3, 9, 4, 3, 2}
e 3
creerai {{1}, {9, 4}, {2}}
... MA tutti i set di elementi singoli sono appiattiti, quindi il risultato sarà effettivamente {1, {9, 4}, 2}
.
Ecco un esempio che ridefinisce l' +
operatore nel senso join
:
+=join
Per quest'ultimo caso, è possibile ridefinire un operatore per l'espressione data. Ad esempio, questo definisce l'operazione più per aggiungere i valori e quindi aggiungere 1:
x+y=1+:x+:y
Ma che cos'è +:
? È possibile aggiungere i due punti :
a un operatore per utilizzare sempre la versione integrata. Questo esempio usa l'integrato +
via +:
per sommare i numeri, quindi aggiunge un 1 (ricorda, tutto è associativo a destra).
L'override dell'operatore lunghezza sembra un po 'come:
#x=<expression>
Si noti che quasi tutte le operazioni integrate (tranne l'uguaglianza) useranno questo operatore di lunghezza per determinare la lunghezza dell'insieme. Se lo hai definito come:
#x=1
ogni parte di zinco che funziona sui set tranne che =
opererebbe solo sul primo elemento del set che gli è stato dato.
Sostituzioni multiple
È possibile ignorare più operatori separandoli con virgole:
let
+=-,
*=/
in 1+2*3
Stampa
Non è possibile stampare direttamente nulla in zinco. in
Verrà stampato il risultato dell'espressione seguente . I valori di un set verranno concatenati al separatore. Ad esempio, prendi questo:
let
...
in expr
Se expr
è impostato {1, 3, {2, 4}}
, 1324
verrà stampato sullo schermo al termine del programma.
Mettere tutto insieme
Ecco un semplice programma di zinco che sembra aggiungere 2+2
ma fa sì che il risultato sia 5:
let
x+y=1+:x+:y
in 1+2
L'interprete
Questo va in 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());
}
E questo va dentro pubspec.yaml
:
name: zinc
dependencies:
parsers: any
Soluzione prevista
let
#x=((x=S)*(-2))+#:x,
/=cut
in {y:{x:S/0$#:x}^_=2}