ローカル変数の一覧を取得
Posted feedbacks - Nested
Flatten Hidden
これはpythonからの挑戦状かな?
1 2 3 4 5 6 | def foo():
x = 1
y = "hello"
result = locals()
return result
print foo()
|
他の言語からの挑戦状があまりたくさん届かないので どうしても僕の得意なPythonからの挑戦状が増えます… RubyのuniqとかPerlのマジカルインクリメントとか いろいろ手を出してはいるんですけどね…
> foo()
x y
"1" "hello"
1 2 3 4 5 6 | foo <- function(){
x = 1
y = "hello"
result <- sapply(ls(), function(s) eval(as.symbol(s)))
return(result)
}
|
1 2 3 4 5 6 | def foo
x = 1
y = "hello"
Hash[*local_variables.map{|v| [v,eval(v)]}.flatten]
end
foo() # => {"x"=>1, "y"=>"hello"}
|
1 2 3 4 5 6 7 8 9 10 | def foo
x = 1
y = "hello"
local_variables.inject({}) {|result, name|
result[name] = eval(name)
result
}
end
puts "{"+foo.map{|k,v| "'#{k}' : #{v}'"}.join(",")+"}"
|
これはキツイ… Gaucheでは最適化によって、定数で初期化されて一度も 変更されないローカル変数とかは実行時には消えてしまいますし、 逆にマクロ展開によってプログラム上には無いローカル変数が 挿入される場合もあります。 やるとしたらマクロで処理系そのものを置き換えるしか なさそうです。ここではletだけ再定義していますが、 ちゃんと動かすにはdefine, lambda, let*, letrec, あたりの束縛系を全部再定義しないと…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | (use util.match)
(define-macro (let binds . body)
(match binds
[((var init) ...)
`((lambda (local-vars) (apply (lambda ,var ,@body) (map cdr local-vars)))
(list ,@(map (lambda (v i) `(cons ',v ,i)) var init)))]))
;;;
(define (foo)
(let ((x 1)
(y "hello"))
local-vars))
;; => ((x . 1) (y . "hello"))
|
Squeak Smalltalk で。
1 | thisContext tempsAndValues
|
すみません。タグを付け間違えていました(^_^;)。
お手数をおかけし恐縮ですが可能でしたら、Squeak_Smalltalk への変更を、お願いいたします。あわせて、#82 のタグも Squeak_Smalltalk に変えて、元のタグの Squeak は(今回の s といっしょに)削除していただければ幸いです。不注意でもうしわけありません。
修正しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | function locals()
local i = 1
local t = {}
while true do
local k, v = debug.getlocal(2, i)
if k == nil then break end
t[k] = v
i = i + 1
end
return t
end
function foo()
local x = 1
local y = "hello"
return locals(), nil
end
for k, v in pairs(foo()) do print(k, v) end
--> y hello
--> x 1
|
正直、C#では厳しいので、静的な情報出力でお茶を濁している。 変数の名前も内容も出力していない。 できるとすれば、以下のような感じかな。デバッガ作るようなもんですね。 ・コンパイルしてアセンブリにする。 ・ホスティングプロセスでアセンブリを読込んで実行する。 ・ProgramDebugDatabaseを読みこんでシンボルを取得。 ・LocalVariablesを取得して表示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | using System;
using System.Reflection;
using System.Diagnostics;
class Program
{
static void Main()
{
int x = 1;
string y = "abc";
ShowLocalVariables(new StackFrame(true));
// System.Int32 (0)
// System.String (1)
}
static void ShowLocalVariables(StackFrame sf)
{
foreach (LocalVariableInfo lv in sf.GetMethod().GetMethodBody().LocalVariables)
Console.WriteLine(lv);
}
}
|
それ、get_defined_vars() で
1 2 3 4 5 6 | <?php
function foo(){
$x = 1;
$y = 'hello';
return get_defined_vars();
}
|
それだとglobalも含まれている気が・・。
Perlでは局所変数を扱うためにはPadWalkerモジュールを使います。
1 2 3 4 5 6 7 8 9 10 11 | use PadWalker qw(peek_my);
use Data::Dumper;
sub foo {
my $x = 1;
my $y = 'hello';
my $result = peek_my(0);
return $result;
}
warn Dumper foo;
|
ひたすら厳しい・・・。
とりあえずJDIで無理やり。
JAVA_OPTSに
-Xrunjdwp:transport=dt_socket,address=5056,server=y,suspend=n
と設定して実行します。なにかいい方法があったら是非教えてほしいです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | import com.sun.jdi._
import com.sun.jdi.connect._
import com.sun.jdi.connect.Connector._
import scala.collection.mutable.JavaMapAdaptor
import scala.collection.mutable.HashMap
class _localVariables(threadName:String) extends Thread {
var result:HashMap[String, AnyRef] = null
override def run = {
val vmm = Bootstrap.virtualMachineManager
val acs = vmm.attachingConnectors().toArray.map(_.asInstanceOf[AttachingConnector])
val ac = acs.find(_.name == "com.sun.jdi.SocketAttach").get
val args = ac.defaultArguments
val portNumber = args.get("port").asInstanceOf[IntegerArgument]
portNumber.setValue(5056)
val hostname = args.get("hostname").asInstanceOf[StringArgument]
hostname.setValue("localhost")
val vm = ac.attach(args)
val threads = vm.allThreads.toArray.map(_.asInstanceOf[ThreadReference])
val mainthread = threads.find(_.name==threadName).get
mainthread.suspend
val f = mainthread.frame(4).asInstanceOf[StackFrame]
val result = new
JavaMapAdaptor[LocalVariable,Value](f.getValues(f.visibleVariables))
this.result = result.foldLeft(new HashMap[String, AnyRef]){(r, p) =>
r(p._1.name) = p._2
r
}
mainthread.resume
}
}
def localVariables = {
val th = new _localVariables(currentThread.getName)
th.start
th.join
th.result
}
def foo = {
val x = 1
val y = "hoge"
localVariables
}
println(foo)
|
コンパイルオプションで -g を指定することでクラスファイルにローカル変数名を入れることができるのですが、それを認識するAPIはありません。
クラスパスからクラスファイルを取得・解析してローカル変数名を取得することも多分可能ですが、変数名に対応する値を取得する方法がありません。
どうしようもないので内部クラスを使って似たようなことをやってみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | import java.util.HashMap;
import java.util.Map;
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) {
Map map = foo();
System.out.println(map);
}
static Map foo() {
Map map = new HashMap();
class Hoge {
int x = 1;
String y = "hello";
Hoge() {
}
}
Hoge hoge = new Hoge();
Field[] fields = hoge.getClass().getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
try {
map.put(fields[i].getName(), fields[i].get(hoge));
} catch (Exception e) {
e.printStackTrace();
}
}
return map;
}
}
|
ふと、自分自身(orソースコード)をgrepとかしてしまってもいいんじゃないかと思ってしまった今日この頃。
宣言と同時でも良ければそれで十分できそうな予感。
でもやらない(笑
関数のソースを解析して、変数名を抽出し、evalで値を求めています。
ソース解析処理がすごく適当なので、一つのvarに複数の変数が定義されている場合に対応していないです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | Function.prototype.listLocalVariableNames = function() {
var regexp = /var\s+(\w+)/g;
var source = this.toString();
var match = null;
var variables = [];
while(match = regexp.exec(source)) {
variables.push(match[1]);
}
return variables;
}
Array.prototype.toHash= function(iter) {
var hash = {};
for(var i=0; i<this.length; i++) {
try {
hash[this[i]] = iter(this[i]);
} catch(e) {
// エラーはパス
}
}
return hash;
};
function func() {
var x = 1;
var y = "hello";
return arguments.callee.listLocalVariableNames().toHash(function(p) {
return eval(p);
});
}
var variablesMap = func();
for(var prop in variablesMap) {
alert(prop + " => " + variablesMap[prop]);
}
|
ネストできない><
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | (defmacro let-inspect (spec &body body)
(let* ((vars (mapcar (lambda (x) (if (consp x) (car x) x)) spec))
(expr (loop for v in vars collect `(list ',v (eval ,v)))))
`(let ,spec
(symbol-macrolet ((inspect (list ,@expr)))
,@body))))
(defparameter *gvar* 1)
(defun foo ()
(let-inspect ((x 1)
(y "hello")
z)
inspect))
(foo) ; => ((X 1) (Y "hello") (Z NIL))
|
(list 'x (eval x)) だと x が二回評価されちゃいます。
ネストも扱うならこんな感じでしょうか。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | (defpackage :doukaku-lisp (:use :cl) (:shadow #:let))
(in-package :doukaku-lisp)
(define-symbol-macro local-variables nil)
(defmacro let (binding &body body)
`(cl:let ,binding
(cl:let ((local-variables (append
(list ,@(mapcar (lambda (e)
(if (consp e)
`(cons ',(car e) ,(car e))
`(cons ',e nil)))
binding))
local-variables)))
,@body)))
(defun test ()
(let ((x 1)
(y "hello"))
local-variables))
|
C 言語の場合ローカル変数名はデバッグ情報にしか含まれませんので gcc の -g オプションが必須です。 デバッグ情報を得るために libelf, libdwarf と、ハッシュテーブルを使うために glib を使用しました。 コンパイルこんな感じです。 % gcc -g `pkg-config glib --cflags` locals.c -o locals `pkg-config glib --libs` -lelf -ldwarf 実行結果 % ./locals x=hello y=1 スタックフレームにある変数を得るのに複数の種類の型が混在していると厄介だったのでローカル変数は文字列(char*)のみとなってしまいました。 あと、X86 32bit 環境でしか動作しないと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <libelf.h>
#include <libdwarf/libdwarf.h>
#include <libdwarf/dwarf.h>
#include <glib.h>
GHashTable *locals();
GHashTable *foo(){
char *x = "1";
char *y = "hello";
return locals();
}
GHashTable *locals(){
GHashTable *hash;
int fd;
Elf *elf;
int ret;
Dwarf_Debug dbg;
Dwarf_Die cu_die;
Dwarf_Die func_die;
Dwarf_Die var_die;
Dwarf_Die var_die_tmp;
Dwarf_Error err;
Dwarf_Unsigned cu_header_length = 0;
Dwarf_Unsigned abbrev_offset = 0;
Dwarf_Half version_stamp = 0;
Dwarf_Half address_size = 0;
Dwarf_Unsigned next_cu_offset = 0;
Dwarf_Half tag;
Dwarf_Attribute attr;
char *name;
Dwarf_Addr high_pc;
Dwarf_Addr low_pc;
int i;
void *frame = __builtin_frame_address(1);
unsigned long retaddr = (unsigned long)__builtin_return_address(0);
hash = g_hash_table_new((GHashFunc)g_str_hash, (GCompareFunc)g_str_equal);
elf_version(EV_CURRENT);
fd = open("/proc/self/exe", O_RDONLY);
elf = elf_begin(fd, ELF_C_READ, (Elf*)0);
ret = dwarf_elf_init(elf, DW_DLC_READ, NULL, NULL, &dbg, &err);
while ((ret = dwarf_next_cu_header(
dbg, &cu_header_length, &version_stamp, &abbrev_offset,
&address_size, &next_cu_offset, &err)) == DW_DLV_OK){
cu_die = NULL;
while(dwarf_siblingof(dbg, cu_die, &cu_die, &err) != DW_DLV_NO_ENTRY){
dwarf_tag(cu_die, &tag, &err);
if(tag != DW_TAG_compile_unit) continue;
ret = dwarf_child(cu_die, &func_die, &err);
if(ret != DW_DLV_OK) continue;
do{
dwarf_tag(func_die, &tag, &err);
if(tag != DW_TAG_subprogram) continue;
dwarf_attr(func_die, DW_AT_high_pc, &attr, &err);
dwarf_formaddr(attr, &high_pc, &err);
dwarf_attr(func_die, DW_AT_low_pc, &attr, &err);
dwarf_formaddr(attr, &low_pc, &err);
if(low_pc > retaddr || retaddr > high_pc) continue;
ret = dwarf_child(func_die, &var_die, &err);
if(ret != DW_DLV_OK) continue;
i=0;
var_die_tmp = var_die;
while(dwarf_siblingof(dbg, var_die, &var_die, &err) != DW_DLV_NO_ENTRY) i++;
var_die = var_die_tmp;
do{
dwarf_tag(var_die, &tag, &err);
if(tag != DW_TAG_variable) continue;
dwarf_attr(var_die, DW_AT_name, &attr, &err);
dwarf_formstring(attr, &name, &err);
g_hash_table_insert(hash, strdup(name),
strdup((frame - 4 - (4 * i--))));
}while(dwarf_siblingof(dbg, var_die, &var_die, &err) != DW_DLV_NO_ENTRY);
}while(dwarf_siblingof(dbg, func_die, &func_die, &err) != DW_DLV_NO_ENTRY);
}
}
dwarf_finish(dbg, &err);
elf_end(elf);
close(fd);
return hash;
}
int main(int argc, char *argv[]){
GHashTable *hash = foo();
printf("x=%s\n", *(char**)g_hash_table_lookup(hash, "x"));
printf("y=%s\n", *(char**)g_hash_table_lookup(hash, "y"));
g_hash_table_destroy(hash);
return EXIT_SUCCESS;
}
|
ローカル変数の取得はリフレクションというよりはデバッグ用という感じがします。
そこで、Java Debug Interface を使ってみました。
public class Sample {
public static void main(String[] args) {
int x = 1;
String y = "hello";
}
}
というサンプルの場合は、-g オプションつきで Sample をコンパイルした後に、
java TinyDB Sample
で起動すると、mainメソッドのローカル変数(引数つき)が出力されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | import com.sun.jdi.*;
import com.sun.jdi.connect.LaunchingConnector;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.event.*;
import com.sun.jdi.request.*;
import java.util.Map;
public class TinyDB {
public static void main(String[] args) throws Exception {
LaunchingConnector lc = Bootstrap.virtualMachineManager().
defaultConnector();
Map<String, Connector.Argument> arg = lc.defaultArguments();
arg.get("main").setValue(args[0]);
VirtualMachine vm = lc.launch(arg);
EventQueue q = vm.eventQueue();
EventSet e = q.remove(); // get VMStartEvent
EventRequestManager mgr = vm.eventRequestManager();
MethodExitRequest exitReq = mgr.createMethodExitRequest();
exitReq.addClassFilter(args[0]);
exitReq.enable();
e.resume();
e = q.remove(); // MethodExitEvent
LocatableEvent ev = (LocatableEvent)e.eventIterator().nextEvent();
ThreadReference tr = ev.thread();
StackFrame frame = tr.frame(0);
for (LocalVariable var : frame.visibleVariables()) {
System.out.print(var.name() + ": ");
Value val = frame.getValue(var);
if (val instanceof PrimitiveValue) {
System.out.println(((PrimitiveValue)val).doubleValue());
} else if (val instanceof StringReference) {
System.out.println(((StringReference)val).value());
} else if (val instanceof ObjectReference) {
System.out.println(val.type().name());
}
}
e.resume();
e = q.remove(); // get VMDisconnectEvent
}
}
|
この技は pnuts -pure で起動したときだけ使えます。
1 2 3 4 5 6 7 | import pnuts.tools.StackFrameInspector
function f(){
x=1
y=2
StackFrameInspector.localSymbols(getContext())
}
println(f())
|
Haskellでは実行時に変数を操作する手段がないので、Template Haskellでコンパイル時にコードを挿入することで対処します。力ずくです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | {-# OPTIONS_GHC -fth #-}
module LocalVariables(localVariables, withLocalVariables) where
import Control.Monad
import Data.Dynamic
import Language.Haskell.TH
import qualified Data.Map as Map
localVariables :: Map.Map String Dynamic
localVariables = error "localVariables outside withLocalVariables"
localVariablesName :: Name
localVariablesName = 'localVariables
withLocalVariables :: Q [Dec] -> Q [Dec]
withLocalVariables = (>>=trDecs)
where
trDecs decs = mapM (trDec []) decs
trDec vars dec = case dec of
FunD name cls -> liftM (FunD name) $ mapM (trClause vars) cls
ValD pat body decs -> do
decs' <- mapM (trDec vars) decs
body' <- trBody (vars ++ concatMap decVars decs) body
return $ ValD pat body' decs'
_ -> return dec
trClause vars (Clause pats body decs) = do
decs' <- mapM (trDec vars) decs
body' <- trBody (vars ++ concatMap patVars pats ++ concatMap decVars decs) body
return $ Clause pats body' decs'
trBody vars (GuardedB ges) = liftM GuardedB $ mapM trGuarded ges
where
trGuarded (guard, exp) = do
exp' <- trExp vars exp
return (guard, exp')
trBody vars (NormalB exp) = liftM NormalB $ trExp vars exp
trExp :: [Name] -> Exp -> Q Exp
trExp vars exp = case exp of
VarE name
| name == localVariablesName -> replacement vars
| otherwise -> return exp
AppE x y -> liftM2 AppE (rec x) (rec y)
InfixE x op y -> liftM3 InfixE (maybeMapM rec x) (rec op) (maybeMapM rec y)
LamE pats e -> liftM (LamE pats) $ trExp (vars ++ concatMap patVars pats) e
TupE es -> liftM TupE $ mapM rec es
CondE c t f -> liftM3 CondE (rec c) (rec t) (rec f)
LetE decs e -> do
decs' <- mapM (trDec vars) decs
e' <- trExp (vars ++ concatMap decVars decs) e
return $ LetE decs' e'
CaseE e matches -> liftM2 CaseE (rec e) (mapM (trMatch vars) matches)
DoE ss -> liftM DoE $ trStmts vars ss
CompE ss -> liftM CompE $ trStmts vars ss
ArithSeqE rng -> liftM ArithSeqE $ trRange vars rng
ListE es -> liftM ListE $ mapM rec es
SigE e tp -> liftM2 SigE (rec e) (return tp)
RecConE name fexps -> liftM (RecConE name) $ mapM (trFexp vars) fexps
RecUpdE e fexps -> liftM2 RecUpdE (rec e) (mapM (trFexp vars) fexps)
_ -> return exp
where
rec e = trExp vars e
maybeMapM f Nothing = return Nothing
maybeMapM f (Just v) = liftM Just $ f v
trFexp vars (name, e) = do
e' <- trExp vars e
return (name, e')
trRange vars rng = case rng of
FromR x -> liftM FromR $ tr x
FromThenR x y -> liftM2 FromThenR (tr x) (tr y)
FromToR x y -> liftM2 FromToR (tr x) (tr y)
FromThenToR x y z -> liftM3 FromThenToR (tr x) (tr y) (tr z)
where
tr e = trExp vars e
trStmts :: [Name] -> [Stmt] -> Q [Stmt]
trStmts vars [] = return []
trStmts vars (stmt:rest) = case stmt of
BindS pat exp -> let vars' = vars ++ patVars pat
in liftM2 (:) (liftM (BindS pat) (trExp vars' exp)) (trStmts vars' rest)
LetS decs -> liftM2 (:) (liftM LetS $ mapM (trDec vars) decs) (trStmts (vars ++ concatMap decVars decs) rest)
NoBindS exp -> liftM2 (:) (liftM NoBindS $ trExp vars exp) (trStmts vars rest)
ParS _ -> error "ParS: what's this?"
trMatch vars (Match pat body decs) = do
decs' <- mapM (trDec vars) decs
body' <- trBody (vars ++ patVars pat ++ concatMap decVars decs) body
return $ Match pat body' decs'
decVars dec = case dec of
FunD name _ -> [name]
ValD pat _ _ -> patVars pat
_ -> []
patVars pat = case pat of
VarP name -> [name]
TupP pats -> concatMap patVars pats
ConP name pats -> concatMap patVars pats
InfixP p0 _ p1 -> patVars p0 ++ patVars p1
TildeP p -> patVars p
AsP name p -> name : patVars p
RecP _ fps -> concatMap fpatVars fps
ListP pats -> concatMap patVars pats
_ -> []
fpatVars (_, pat) = patVars pat
replacement vars = [| Map.fromList $(list) |]
where
list = listE $ map entry vars
entry name = [| ($(str), toDyn $(obj)) |]
where
str = return $ LitE $ StringL $ show name
obj = return $ VarE name
{-
使い方:
別モジュールで
import LocalVariables
$(withLocalVariables [d|
foo :: IO (Map.Map String Dynamic)
foo = do
let x = 1 :: Int
let y = "hello"
return localVariables
|])
のようにすると、localVariablesの出現を置換した上でfooが定義される
-}
|
Tcl にも info locals というずばりなコマンドがありますが Python と違って返されるのは名前のリストのみなので記述量は若干増えます。
1 2 3 4 5 6 7 8 9 10 11 12 | proc foo {} {
set x 1
set y hello
set locals [info locals]
set result {}
foreach k $locals { lappend result $k [set $k] }
return $result
}
# % foo
# x 1 y hello
|
BASHには連想配列がないのですが、表示がそのまま連想配列になってるように見えるというところでかんべんしてください。
1 2 3 4 5 | foo () {
local -i x=1
local y="Hello"
|





にしお
#3391()
Rating0/0=0.00
Pythonで表現すると、下のコードの???部分を埋めることになります。
>>> def foo(): x = 1 y = "hello" ??? return result >>> foo() {'y': 'hello', 'x': 1}[ reply ]