Egalement disponible sur GitHub .
Vous avez besoin de Dart 1.12 et Pub. Il suffit de lancer pub get
pour télécharger la seule dépendance, une bibliothèque d'analyse.
En espérant que cela dure plus de 30 minutes! : O
La langue
Le zinc est axé sur la redéfinition des opérateurs. Vous pouvez facilement redéfinir tous les opérateurs de la langue!
La structure d'un programme de zinc typique ressemble à ceci:
let
<operator overrides>
in <expression>
Il n'y a que deux types de données: les entiers et les ensembles. Il n'existe pas de littéral d'ensemble, et les ensembles vides sont interdits.
Expressions
Les expressions suivantes sont valides dans le zinc:
Littéraux
Le zinc prend en charge tous les littéraux entiers normaux, comme 1
et -2
.
Variables
Le zinc a des variables (comme la plupart des langues). Pour les référencer, utilisez simplement le nom. Encore comme la plupart des langues!
Cependant, il existe une variable spéciale appelée S
qui se comporte un peu comme celle de Pyth
Q
. Lorsque vous l'utilisez pour la première fois, il lit une ligne à partir d'une entrée standard et l'interprète comme un ensemble de chiffres. Par exemple, la ligne d’entrée 1234231
deviendrait l’ensemble {1, 2, 3, 4, 3, 2, 1}
.
NOTE IMPORTANTE!!! Dans certains cas, un littéral à la fin d'une substitution d'opérateur est analysé de manière incorrecte. Vous devez donc l'entourer de parenthèses.
Opérations binaires
Les opérations binaires suivantes sont supportées:
- Addition par
+
: 1+1
.
- Soustraction via
-
: 1-1
.
- MULTIPLICATION via
*
: 2*2
.
- Division par
/
: 4/2
.
- Égalité avec
=
: 3=3
.
De plus, l'opération unaire suivante est également prise en charge:
La préséance est toujours droite associative. Vous pouvez utiliser des parenthèses pour remplacer ceci.
Seules l'égalité et la longueur travaillent sur les décors. Lorsque vous essayez d'obtenir la longueur d'un entier, vous obtenez le nombre de chiffres dans sa représentation sous forme de chaîne.
Compréhension d'ensemble
Afin de manipuler les ensembles, le zinc a des compréhensions définies. Ils ressemblent à ceci:
{<variable>:<set><clause>}
Une clause est une clause When ou une clause Sort.
Une clause quand ressemble ^<expression>
. L'expression qui suit le curseur doit donner un entier. L'utilisation de la clause when ne prend que les éléments de l'ensemble pour lesquels expression
est non nul. Dans l'expression, la variable _
sera définie sur l'index actuel de l'ensemble. C'est à peu près équivalent à ce Python:
[<variable> for _, <variable> in enumerate(<set>) when <expression> != 0]
Une clause de tri , qui ressemble à $<expression>
, trie l'ensemble décroissant par la valeur de <expression>
. C'est égal à ce Python:
sorted(<set>, key=lambda <variable>: <expression>)[::-1]
Voici quelques exemples de compréhension:
Les dérogations
Les remplacements d’opérateurs vous permettent de redéfinir les opérateurs. Ils ressemblent à ceci:
<operator>=<operator>
ou:
<variable><operator><variable>=<expression>
Dans le premier cas, vous pouvez définir un opérateur égal à un autre opérateur. Par exemple, je peux définir +
de soustraire réellement via:
+=-
En faisant cela, vous pouvez redéfinir un opérateur en opérateur magique . Il y a deux opérateurs de magie:
join
prend un ensemble et un entier et joint le contenu de cet ensemble. Par exemple, rejoindre {1, 2, 3}
avec 4
donnera le nombre entier 14243
.
cut
prend également un ensemble et un entier et le partitionnera à chaque occurrence de cet entier. Utiliser cut
sur {1, 3, 9, 4, 3, 2}
et 3
créera {{1}, {9, 4}, {2}}
... MAIS tous les ensembles à élément unique sont aplatis, le résultat sera donc réel {1, {9, 4}, 2}
.
Voici un exemple qui redéfinit l' +
opérateur comme suit join
:
+=join
Dans ce dernier cas, vous pouvez redéfinir un opérateur à l'expression donnée. À titre d'exemple, cela définit l'opération plus d'ajouter les valeurs, puis d'ajouter 1:
x+y=1+:x+:y
Mais quoi +:
? Vous pouvez ajouter les deux points :
à un opérateur pour toujours utiliser la version intégrée. Cet exemple utilise le +
via intégré +:
pour additionner les nombres, puis ajoute un 1 (rappelez-vous que tout est associatif à droite).
Remplacer l'opérateur de longueur ressemble un peu à:
#x=<expression>
Notez que presque toutes les opérations internes (à l'exception de l'égalité) utiliseront cet opérateur de longueur pour déterminer la longueur de l'ensemble. Si vous l'avez défini comme étant:
#x=1
chaque partie du zinc qui fonctionne sur des ensembles sauf =
n'agit que sur le premier élément de l'ensemble qui lui a été attribué.
Plusieurs substitutions
Vous pouvez remplacer plusieurs opérateurs en les séparant par des virgules:
let
+=-,
*=/
in 1+2*3
Impression
Vous ne pouvez rien imprimer directement dans le zinc. Le résultat de l'expression suivante in
sera imprimé. Les valeurs d'un ensemble seront concaténées avec séparateur. Par exemple, prenez ceci:
let
...
in expr
Si expr
est défini {1, 3, {2, 4}}
, 1324
sera imprimé à l'écran une fois le programme terminé.
Mettre tous ensemble
Voici un programme de zinc simple qui semble s’ajouter 2+2
mais qui donne 5 comme résultat:
let
x+y=1+:x+:y
in 1+2
L'interprète
Cela va dans 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());
}
Et cela entre pubspec.yaml
:
name: zinc
dependencies:
parsers: any
Solution envisagée
let
#x=((x=S)*(-2))+#:x,
/=cut
in {y:{x:S/0$#:x}^_=2}