challenge α置換

標準入力から与えられたソースコードの変数名
を置換するプログラムを作ってください。
最近はリファクタリングツールなどの普及でこ
のような需要は少ないかと思われますが、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;
}

Posted feedbacks - Nested

Flatten Hidden

Squeak 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
  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))
}

Index

Feed

Other

Link

Pathtraq

loading...