Skip to content

Instantly share code, notes, and snippets.

@waneck
Last active Sep 13, 2020
Embed
What would you like to do?
Very simple Haxe REPL
--macro Repl.main()
--interp
output sample:
var x = 10;
=> 10
x;
=> 10
var y = "hello, world";
=> hello, world
y.split(" ");
=> [hello,,world]
function add(a,b) return a+b;
=> #function:2
add(10,20);
=> 30
add("a",10);
Error: String should be Int
For function argument ''
var file = sys.io.File.write('test");
Error: Unterminated string
var file = sys.io.File.write('test');
=> { __f => #abstract }
file.writeString('a');
=> null
file.writeString('b');
=> null
file.close();
=> null
var file = sys.io.File.read('test');
=> { __f => #abstract }
file.readLine();
=> ab
/*
Copyright (c) 2014 Cauê Waneck
All rights reserved.
Redistribution and use in source and binary forms are permitted
provided that the above copyright notice and this paragraph are
duplicated in all such forms and that any documentation,
advertising materials, and other materials related to such
distribution and use acknowledge that the software was developed
by the <organization>. The name of the
<organization> may not be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
import haxe.macro.Expr;
import haxe.macro.ExprTools;
import haxe.macro.Context;
import haxe.macro.Compiler;
import sys.FileSystem.*;
import sys.io.File;
using haxe.macro.ExprTools;
using haxe.macro.TypeTools;
using haxe.macro.ComplexTypeTools;
typedef Ctx = { vars:Map<String,haxe.macro.Type> };
class Repl
{
public static function eval(ctx:Ctx, code:String):Dynamic
{
var count = count++;
code = handleVars(ctx,'{' +code + '}');
var txt = new StringBuf();
txt.add('class Code__$count { public static function exec() { return {' + code + '} } }');
File.saveContent(tmpdir + '/Code__$count.hx', txt.toString());
var name = "Helper__" + count;
var txt = new StringBuf();
txt.add('class Importer__');
txt.add(count);
txt.add('{\npublic static function build() {\n\t');
txt.add('Code__$count');
txt.add(';\n\treturn haxe.macro.Context.getType("String"); } }\n\n');
txt.add('typedef ');
txt.add(name);
txt.add(' = haxe.macro.MacroType<[Importer__$count.build()]>;\n');
File.saveContent(tmpdir + '/$name.hx', txt.toString());
Context.getType(name);
deleteFile(tmpdir + '/' + name + ".hx");
deleteFile(tmpdir + '/Code__' + count + ".hx");
var cls:Dynamic = Reflect.field(untyped $exports.__classes, 'Code__' + count);
return cls.exec();
}
private static function handleVars(ctx:Ctx, code:String)
{
var expr = Context.parseInlineString(code, Context.makePosition({min:0,max:0,file:""}));
var tlevel = [],
locals:Array<Map<String,Bool>> = [];
function lookupVar(name:String)
{
for (v in locals)
if (v.exists(name))
return true;
return false;
}
inline function addVar(v:String)
{
locals[locals.length-1].set(v,true);
}
inline function pushStack(?map)
{
locals.push(map != null ? map : new Map());
}
inline function popStack()
{
locals.pop();
}
function map(e:Expr)
{
return switch(e.expr)
{
case EVars(v):
if (locals.length == 1)
{
// top level
for (v in v)
tlevel.push(v.name);
}
for (v in v)
addVar(v.name);
return ExprTools.map(e,map);
case EFunction(name,f):
if (name != null)
{
addVar(name);
if (locals.length == 1)
{
tlevel.push(name);
}
}
pushStack([ for(arg in f.args) arg.name => true ]);
var ret = ExprTools.map(e,map);
popStack();
ret;
case EConst(CIdent(v)) if (!lookupVar(v) && ctx.vars.exists(v)):
var global = { expr: EConst(CIdent("$exports")), pos:e.pos };
var type = ctx.vars[v].toComplexType();
var ret = macro ( untyped $global.toplevel.$v : $type );
ret;
//TODO switch and for
case EBlock(b):
pushStack();
var ret = [ for (e in b) map(e) ];
popStack();
{ expr: EBlock(ret), pos:e.pos };
case _:
ExprTools.map(e,map);
}
}
var ret = map(expr);
// register vars
if (tlevel.length > 0)
{
var anon = { expr:EObjectDecl([ for (f in tlevel) { field:f, expr: macro $i{f} } ]), pos:ret.pos };
var global = { expr: EConst(CIdent("$exports")), pos:ret.pos };
var setvars = [ for (f in tlevel) macro untyped $global.toplevel.$f = $i{f} ];
var r = switch(ret.expr)
{
case EBlock(bl):
ret = { expr : EBlock(bl.concat(setvars)), pos : ret.pos };
{ expr: EBlock(bl.concat([anon])), pos: ret.pos };
case _:
ret = { expr : EBlock([ret].concat(setvars)), pos : ret.pos };
{ expr: EBlock([ret,anon]), pos: ret.pos };
};
switch (Context.typeof(r))
{
case TAnonymous(a):
var a = a.get();
for (field in a.fields)
{
ctx.vars.set(field.name, field.type);
}
default: throw "assert";
}
} else {
// get any typing errors
Context.typeof(ret);
}
return ret.toString();
}
static var tmpdir:String;
static var count =0;
public static function main()
{
tmpdir = Sys.getEnv("TEMP");
if (tmpdir == null)
tmpdir = "/tmp";
tmpdir = tmpdir + '/' + Std.random(10000) + '-' + Std.random(10000);
createDirectory(tmpdir);
Compiler.addClassPath(tmpdir);
tmpdir = "."; // issue #2815
repl();
}
private static function repl()
{
var ctx = { vars : new Map() };
untyped $exports.toplevel = {};
while(true)
{
try
{
// read
var input = getInput();
// eval
var ret = eval(ctx,input);
// print
Sys.println(" => " + ret);
Sys.println("");
}
catch(e:haxe.io.Eof)
{
break;
}
catch(e:Dynamic)
{
Sys.println('Error: ' + e);
Sys.println("");
}
}
}
private static function getInput()
{
return Sys.stdin().readLine();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment