α置換
Posted feedbacks - Nested
Flatten HiddenSqueak Smalltalk で。
ふつう、こういうことはしないのですが、リフレクション機能を試す問題として捉え、システム組み込みのパーサに委譲するかたちで実現してみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 | | code tree old new |
code := 'sampleCode
| a |
a := ''This is a pen''.
^a'.
old := 'a'. new := 'b'.
tree := Parser new parse: code class: UndefinedObject.
(tree instVarNamed: #temporaries) do: [:each |
each name = old ifTrue: [each key: new; name: new]].
^tree decompileString "=> 'sampleCode
| b |
b := ''This is a pen''.
^ b' "
|
変数v0を全てv1に置換した後に, 文字列とヒアドキュメント内のv1をv0に戻す処理をしています.
テストに使用したファイルを生成するコードも添付します.
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 | def subst_var(file, v0, v1)
hdocr = '[-]?[\"\'`]?([A-Z]+)'
shared_reg = 's,;&|=+-*/[](){}'
former, latter = %w[@$^ .].map{|reg|
"#{shared_reg}#{reg}".each_char.inject(''){|a,b| "#{a}\\#{b}"}}
gsub, rev_gsub = [:to_a, :reverse].map{|meth|
proc{|s| s.gsub(*[v0, v1].__send__(meth))}}
open(file, 'r'){|f| f.read}.
gsub(/(?:^#{v0})?(?:[#{former}]#{v0})+[#{latter}]/, &gsub).
gsub(/".*"/, &rev_gsub).
gsub(/<<#{hdocr}(?:.|\s)+[^<]#{hdocr}/, &rev_gsub)
end
file = 'sample.rb'
puts subst_var(file, 'v0', 'v1')
open(file, 'w'){|f| f.write(<<EOF)}
v0+v0=v0
@v0 = 0
$v00, ^v0
"v0 v0 "
<<EOS
v0 v0
EOS
$v0, v0, v0
v0/v0*v0-v0+v0
EOF
|
Cのソースを変換します。
1文字ずつ結構まじめに解析してます。
お題のソースの変換ができてることを確認しました。
一番外側の変数しか変換しないという仕様を満たすのがしんどかったです。
よーく見ると実はバグで動いてたりするのは内緒。(正規表現のあたり)
1文字ずつ結構まじめに解析してます。
お題のソースの変換ができてることを確認しました。
一番外側の変数しか変換しないという仕様を満たすのがしんどかったです。
よーく見ると実はバグで動いてたりするのは内緒。(正規表現のあたり)
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 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 | using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
class AlphaReader {
public bool IsDebug = false;
void DBG(String s, params Object[] args) {
if (!IsDebug) return;
Console.WriteLine(s, args);
}
TextReader input;
String before; //変換前文字列
String after; //変換後文字列
enum State { //状態
Global, Func, Struct, //グローバル, 関数定義, 構造体(共用体・列挙体)
Block, Name, Comment, InStr //ブロック, 識別子, コメント, 文字列
}
String IdDelimiter = "\t (){};,=+-*/%&|^!?"; //識別子分離文字
Stack<State> state; //現在のReaderの状態
int targetDepth; //変換対象のスコープ(stateの深さ)
int targetHideDepth; //変換対照が再定義された深さ
//コンストラクタ
public AlphaReader(TextReader input,
String before, String after) {
this.input = input;
this.before = before;
this.after = after;
this.state = new Stack<State>();
this.state.Push(State.Global);
this.targetDepth = int.MaxValue;
this.targetHideDepth = int.MaxValue;
}
//行を読み込んで変換処理するメソッド
public String ReadLine() {
String strIn = this.input.ReadLine();
if (strIn == null) {
return null;
}
DBG("## [{0}]", strIn);
StringBuilder buf = new StringBuilder(); //バッファ
StringBuilder name = new StringBuilder(); //識別子バッファ
/* 変数宣言の判定 */
/* この判定は、変数宣言と演算が別の行にわかれていること、
および、変数宣言が比較的シンプルなことを前提としています。 */
String decVar = @".*(\w+|(struct|union)\s+\w+)(\s+[^;,]+,)*\s+"
+ before + @"\s*[=,;\)].*"; //型+(変数, ...)+変換前文字列
bool hasDecVar = false;
if ((new Regex(decVar)).IsMatch(strIn)) {
DBG("## 変数宣言発見");
hasDecVar = true;
}
/* 行を1文字ずつ解析 */
for (int pos = 0; pos < strIn.Length; pos++) {
State st = this.state.Peek();
char c = strIn[pos];
String cs = strIn.Substring(pos);
DBG("## pos={0} c={1} state={2} depth={3}", pos, c, st.ToString(), state.Count);
switch (st) {
case State.InStr: //文字列リテラルの中
if (c == '\\') {
buf.Append(c);
if (++pos < strIn.Length) {
buf.Append(strIn[pos]);
}
} else if (c == '"') {
buf.Append(c);
this.state.Pop();
}
break;
case State.Comment: //コメントの中
buf.Append(c);
if (cs.StartsWith("*/")) {
buf.Append(strIn[++pos]);
this.state.Pop();
}
break;
case State.Name: //識別子の中
if (IdDelimiter.IndexOf(c) < 0) {
name.Append(c); //識別子続行
} else if (cs.StartsWith("->")) {
name.Append("->"); //識別子続行
pos++;
} else {
//識別子を抽出
String id = name.ToString();
this.state.Pop();
st = this.state.Peek();
switch (st) {
case State.Struct: //構造体の中は変換対象外
buf.Append(id);
break;
default: //変換対象
if (hasDecVar && id == this.before) { //変数宣言
if (this.state.Count < this.targetDepth) { //新たに宣言の場合
this.targetDepth = this.state.Count; //targetDepthをセット
} else if (this.state.Count < this.targetHideDepth
&& this.targetDepth < this.state.Count) { //再宣言した場合
this.targetHideDepth = this.state.Count; //targetHideDepthをセット
}
DBG("## set targetDepth:{0} hideDepth:{1}", targetDepth, targetHideDepth);
}
if (this.targetDepth <= this.state.Count
&& this.state.Count < this.targetHideDepth) {
//変換対象スコープの場合
if (id == this.before) {
buf.Append(this.after);
} else if ((new Regex(this.before + @"[\.\-]")).IsMatch(id)) {
//変換対象変数のメンバを参照してる場合
buf.Append(this.after);
buf.Append(id.Substring(this.before.Length));
} else {
buf.Append(id); //変換なし
}
} else {
buf.Append(id); //変換なし
}
break;
}
buf.Append(c);
if (st == State.Global && c == '(') { //関数宣言の場合
this.state.Push(State.Func);
}
name = new StringBuilder();
}
break;
default: //その他の状態
if (cs.StartsWith("/*")) { //コメント開始
buf.Append("/*");
pos++;
this.state.Push(State.Comment);
} else if (c == '"') { //文字列開始
buf.Append(c);
this.state.Push(State.InStr);
} else if (IdDelimiter.IndexOf(c) >= 0) { //識別子外文字
buf.Append(c);
if (c == '{') {
if (st != State.Struct) {
this.state.Push(State.Block);
}
} else if (c == '(') {
if (st == State.Global) {
this.state.Push(State.Func);
}
} else if (c == '}') {
if (st == State.Block) {
this.state.Pop();
if (this.state.Peek() == State.Func) {
this.state.Pop();
}
} else if (st == State.Struct) {
this.state.Pop();
}
if (this.state.Count < this.targetDepth)
this.targetDepth = int.MaxValue;
if (this.state.Count < this.targetHideDepth)
this.targetHideDepth = int.MaxValue;
DBG("## unset targetDepth:{0} hideDepth:{1}", targetDepth, targetHideDepth);
} else if (c == ';') {
if (st == State.Func) {
this.state.Pop();
}
}
} else if (cs.StartsWith("struct ") //構造体・共用体・列挙体のブロック内は
|| cs.StartsWith("union ") //変換を行わない
|| cs.StartsWith("enum ")) {
int index = cs.IndexOf(" ");
pos += index;
name.Append(cs.Substring(0, index + 1));
this.state.Push(State.Struct);
} else { //識別子開始
name.Append(c);
this.state.Push(State.Name);
}
break;
}
}
if (name.Length > 0) {
buf.Append(name.ToString());
if (this.state.Peek() == State.Name) {
this.state.Pop();
}
}
return buf.ToString();
}
}
class Driver {
public static int Main(String[] args) {
if (args.Length < 2) {
Console.WriteLine("alpha.exe before after [debug]");
return -1;
}
AlphaReader ar = new AlphaReader(
Console.In, args[0], args[1]);
if (args.Length >= 3 && args[2] == "debug") {
ar.IsDebug = true;
}
String line;
while ((line = ar.ReadLine()) != null) {
Console.WriteLine(line);
}
return 0;
}
}
|
文字列の処理がうまくいってませんでした。。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | --- alpha.cs.release1 2008-04-09 01:11:32.000000000 +0900
+++ alpha.cs.release2 2008-04-09 01:14:15.000000000 +0900
@@ -65,13 +65,12 @@
switch (st) {
case State.InStr: //文字列リテラルの中
+ buf.Append(c);
if (c == '\\') {
- buf.Append(c);
if (++pos < strIn.Length) {
buf.Append(strIn[pos]);
}
} else if (c == '"') {
- buf.Append(c);
this.state.Pop();
}
break;
|
まず、ソースファイルをVisualStudioで開きます。 エディタにコードをコピペします。 変更したい変数の宣言部で、変数名を書き替えます。 変数の上にマウスを置くとアイコンが出るのでクリックします。 ”名前を[変数名]から[変数名]に変更します。"を選ぶとVisualStudioが全部自動でやってくれます。
Ripperを使ってみました。Ruby 1.9.0以上じゃないと動かないと思います。
AlphaConverter.newの第2引数以降で置換する変数を指定します。
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 | require 'ripper'
class AlphaConverter<Ripper::Filter
def initialize(src, *vars)
super(src)
@alpha_dict = {}
vars.each do |o, n|
@alpha_dict[o] = n
end
end
def var_common(token, data)
if @alpha_dict[token] then
data << @alpha_dict[token]
else
data << token
end
end
def on_default(event, token, data)
data << token
end
# クラス変数
def on_cvar(token, data)
var_common(token, data)
end
# 大域変数
def on_gvar(token, data)
var_common(token, data)
end
# インスタンス変数
def on_ivar(token, data)
var_common(token, data)
end
# ローカル変数
def on_ident(token, data)
var_common(token, data)
end
end
print AlphaConverter.new(ARGF, ['event', 'ee'], ['@alpha_dict', '@ad']).parse('')
|
GHC6.8.1にパーサーとprettyprintのライブラリがあったのを発見したので、途中まで作ってみました。
ppという名前をpopに、popをppに置き換えるようになっています。
もう少しがんばらなくてはいけないところ:
1.入力ソースがprettyprintされてしまうので、アルファ置換以外の変更が起きてしまう。(改行、インデントが変わってしまう、コメントが保持されない)
2.スコープ管理がされていない。
3.ネームスペースに関する処理が入っていない…(Data.Stack.popは置換されるべきではないけれど、pop, Main.popはされるべき...)
コメントの問題はパーサーの機能不足なので、解決には違うアプローチが必要です...パーサーがサポートしてくれれば、簡単ですが…
2,3に関してはパーサーのサポートはあるので、コードを書くだけなんですが...誰かやってください...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | module Main where
import Language.Haskell.Syntax
import Language.Haskell.Parser
import Language.Haskell.Pretty
import Data.Generics
pp :: ParseResult HsModule -> String
pp (ParseOk hsm) = prettyPrint hsm
pp _ = "parse failed"
repl :: ParseResult HsModule -> ParseResult HsModule
repl (ParseOk hsm) = ParseOk $ (everywhere (mkT conversion)) hsm
where
conversion :: HsName -> HsName
conversion (HsIdent "pp") = HsIdent "pop"
conversion (HsIdent "pop") = HsIdent "pp"
conversion pop = pop
repl a = a
main :: IO ()
main
= do mod <- getContents
putStr $ pp $ repl $ parseModule mod
|
一番外側の変数 = グローバル変数と解釈して書きました。 R5RS の範囲内の構文は網羅しています。入力は一行に置換対象の識別子と置換後の識別子を空白で区切って与えます。
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 139 140 141 142 143 144 145 | (use srfi-1)
(use gauche.collection)
(use util.match)
(define (alpha-replace sexp maps)
(define (remove-map k ms)
(remove-first (lambda (p) (eq? k (car p))) ms))
(define (remove-maps ks ms)
(fold remove-map ms ks))
(define (remove-first pred? xs)
(let loop ((ys xs)
(zs '()))
(cond ((null? ys)
(reverse! zs))
((pred? (car ys))
(append-reverse! zs (cdr ys)))
(else
(loop (cdr ys) (cons (car ys) zs))))))
(define (replace-symbol key maps)
(cond ((assq sexp maps) => cdr)
(else key)))
(define (replace-qq exp level maps)
(match exp
([? symbol? exp]
(if (zero? level)
(alpha-replace exp maps)
exp))
([? vector?]
(map-to <vector> (cut replace-qq <> level maps) exp))
(('quasiquote x)
(replace-qq x (+ level 1) maps))
(([and [or 'unquote 'unquote-splicing] uquote] x)
(list uquote (replace-qq x (- level 1) maps)))
((x . y)
(cons (replace-qq x level maps)
(replace-qq y level maps)))
(_ exp)))
(define (improper-list->list xs)
(let loop ((ys xs)
(rs '()))
(cond ((null? ys) (reverse rs))
((pair? ys) (cons (car ys) rs))
(else (reverse (cons ys rs))))))
(define (take-internal-define-syms sexps)
(map (lambda (exp)
(if (pair? exp)
(caadr exp)
(cadr exp)))
(take-while (lambda (exp)
(and (pair? exp)
(or (eq? (car exp) 'define)
(eq? (car exp) 'define-syntax))))
sexps)))
(match sexp
([? symbol?] (replace-symbol sexp maps))
([not [? pair?]] sexp)
(('quote exp) sexp)
(('quasiquote exp)
(list 'quasiquote (replace-qq exp 1 maps)))
(('case key clauses)
`(case ,(alpha-replace key maps)
,@(map (lambda (c)
`(,(car c) ,@(map (cut alpha-replace <> maps) (cdr c))))
clauses)))
(('define name val)
`(define ,(replace-symbol name maps)
,(alpha-replace val maps)))
(('define (name args ...) body ...)
(let ((maps* (remove-maps (take-internal-define-syms body)
(remove-maps (improper-list->list args) maps))))
`(define (,(replace-symbol name maps) ,@args)
,@(alpha-replace body maps*))))
(('do step test body)
(let ((maps* (remove-maps (map car step) maps)))
`(do ,(map (lambda (c)
`(,(first c)
,(alpha-replace (second c) maps*)
,@(if (null? (cddr c))
'()
(list (alpha-replace (third c) maps*))))))
,(alpha-replace step maps*)
,(alpha-replace test maps*)
,(alpha-replace body maps*))))
(('lambda args body ...)
(let ((maps* (remove-maps (take-internal-define-syms body)
(remove-maps (improper-list->list args) maps))))
`(lambda ,arg
,@(alpha-replace body maps*))))
(('let [? symbol? name] binds body)
(let ((maps* (remove-maps (take-internal-define-syms body)
(remove-maps (map car binds) maps))))
`(let ,name (map (lambda (c)
`(,(car c) ,(alpha-replace (cadr c) maps)))
binds)
,@(alpha-replace body maps*))))
(([or 'let 'let-syntax] binds body ...)
(let ((maps* (remove-maps (take-internal-define-syms body)
(remove-maps (map car binds) maps))))
`(let ,(map (lambda (c)
`(,(car c) ,(alpha-replace (cadr c) maps)))
binds)
,@(alpha-replace body maps*))))
(('let* binds body ...)
(receive (cs maps*) (map-accum
(lambda (c knil)
(values `(,(car c) ,(alpha-replace (cadr c) knil))
(remove-map (car c) knil)))
maps
binds)
(let ((maps** (remove-maps (take-internal-define-syms body)
maps*)))
`(let* ,cs
,@(alpha-replace body maps**)))))
(([or 'letrec 'letrec-syntax] binds body)
(let* ((maps* (remove-maps (map car binds) maps))
(maps** (remove-maps (take-internal-define-syms body) maps*)))
`(letrec ,(map (lambda (c)
`(,(car c) ,(cadr (alpha-replace (cadr c) maps*))))
binds)
,@(alpha-replace body maps**))))
(('syntax-rules keys clauses)
`(syntax-rules ,keys
(map (lambda (c)
`(,(car c)
(,(alpha-replace (caadr c) maps) ,@(cdadr c))))
clauses)))
((x . y)
(cons (alpha-replace x maps)
(alpha-replace y maps)))))
(define (read-maps iport)
(port-map (lambda (line)
(let ((ts (map string->symbol (string-split line #[\s]))))
(cons (car ts) (cadr ts))))
(cut read-line iport)))
(define (main args)
(let ((maps (read-maps (standard-input-port))))
(call-with-input-file (cadr args)
(lambda (iport)
(port-for-each
(lambda (sexp)
(write (alpha-replace sexp maps)))
(cut read iport))))
0))
|
substitute()を使って、Rのparserにお任せします。
お題の「一番外側の変数」というのは、「スコープ内の変数」という解釈でいいのでしょうか。例えば以下のようなコードでは、xの最終的な値は10になるので、スコープをさしているのであれば「一番外側」にこだわる必要はないと判断しました。
例では、変数"x"を"aaaa"に変更しています。リストの項目名である"x"や文字列中の"x"、別の変数名の一部に含まれる"x"など、置換するべきでないものは残されているようです。:
> alpha.replace("x", "aaaa")
(入力)
x <- 1
y <- list(x=10)
z <- "w x y z"
xyz <- 1
zzz <- {
x <- 10
}
x
(出力)
{
aaaa <- 1
y <- list(x = 10)
z <- "w x y z"
xyz <- 1
zzz <- {
aaaa <- 10
}
aaaa
}
1 2 3 4 | alpha.replace <- function(from, to){
script <- c("substitute({",readLines(),"}, list(",from,"=quote(",to,")))")
eval(parse(text=script))
}
|





gandalf #6153() Rating-2/6=-0.33
標準入力から与えられたソースコードの変数名 を置換するプログラムを作ってください。 最近はリファクタリングツールなどの普及でこ のような需要は少ないかと思われますが、viな ど貧弱なエディタを使っているときに困る のが変数名の置換です。さすがに以下の例のよ うなプログラムは例としてしか書きませんが、 置換しようとしている変数名と同じ綴りの他の ものがプログラム中に出てくることはまれにあ ります。そこで、与えられたソースコードに現 れる変数だけを指定された名前に置換してくだ さい。 置換対象となるソースコードと使用言語は同じ ものを使ってください。与えられるソースコー ドは、完全なコンパイル単位、もしくはパース して意味が通る範囲のものどちらであってもか まいません。後者の場合、一番外側の変数だけ 置換できるようにしてください。 C言語での解答例をつけたかったのですが、と ても難しかったためまだ作成できていません。 ご容赦ください。 例 $ cat a.c /* a */ int foo() { struct a {int a;} a; #if FOO a.a = 1; #endif { int a; } return 0; } $ alpha -DFOO=1 b a < a.c /* a */ int foo() { struct a {int a;} b; #if FOO b.a = 1; #endif { int a; } return 0; }[ reply ]