ハスケル、327 360 418 394 バイト
g.(m.w.r.r=<<).lines.f
n:c:z="\n#_0123456789"++['A'..'Z']++['a'..'z']
(!)x=elem x
f('\\':'\n':a)=f a
f(a:b)=a:f b
f a=a
m('#':a)=c:a++[n]
m a=a
g(a:'#':b)=a:[n|a/=n]++c:g b
g(a:b)=a:g b
g a=a
s=span(!" \t")
r=reverse.snd.s
l n(a:b)d|a==d,n=a:w(snd$s b)|1>0=a:l(not$n&&a=='\\')b d
w('/':'/':_)=[]
w(a:b)|a!"\"'"=a:l(1>0)b a|(p,q:u)<-s b=a:[' '|p>"",a!z&&q!z||[a,q]!words"++ -- /*"]++w(q:u)
w a=a
オンラインで試してみてください!
これは書くのがとても楽しかったです!まず f
関数が通過し、行末のすべてのバックスラッシュを削除してから lines
改行で文字列のリストに分割します。次に、一連の関数を行にマップし、それらをすべて連結して元に戻します。これらの関数:左から空白を取り除きます (t
) 右から (r.t.r
どこで r
reverse
です );文字列と文字リテラルを無視し、コメントを削除して、中間の空白を削除します (w
);行が # で始まる場合、最後に改行文字を最後に追加します。すべての行が連結された後 g
# 文字を検索し、その前に改行があることを確認します。
w
少し複雑なので、詳しく説明します。最初に、w
以降の "//" をチェックします 私は文字列リテラルではないことを知っています。これはコメントであることを知っているので、残りの行を削除します。次に、先頭が文字列または文字リテラルの区切り文字かどうかを確認します。もしそうなら、私はそれを前に付けて l
にバトンを渡します n
で「エスケープ」状態を追跡しながら、文字を実行します。 これは、連続するスラッシュが偶数回ある場合に当てはまります。 l
のとき 区切り文字を検出し、エスケープ状態にない場合はバトンを w
に戻します 、 w
のため、リテラルの後の空白を削除するためにトリミング 最初の文字が空白でないことを期待します。 w
のとき 区切り文字が見つからない場合は、スパンを使用して末尾の空白を探します。存在する場合は、周囲のキャラクターが接触できないかどうかをチェックし、そうであればスペースを挿入します。その後、空白が終了した後に再発します。空白がなかった場合、スペースは挿入されず、とにかく先に進みます。
編集:私のプログラムのバグを指摘してくれた @DLosc に感謝します。パターン マッチング万歳!
EDIT2:私は仕様を読み終えていないばかです!それを指摘してくれたDLoscに再び感謝します!
EDIT3:e=elem
になった面倒な型削減に気付きました Char->[Char]->Bool
に 何らかの理由で、e[a,q]
で壊れる .強制的に正しいものにするために、型シグネチャを追加する必要がありました。どうすれば修正できるか知っている人はいますか? Haskell でこの問題が発生したことはありません。 TIO
EDIT4:@FelixPalmenが私に示したバグの迅速な修正。後で時間があるときにゴルフを試してみるかもしれません.
EDIT5:@Lynn のおかげで -24 バイト!ありがとうございました! n:c:z=...
のようなパターン マッチングを使用して、グローバル スコープで物事を割り当てることができるとは知りませんでした かっこいい! elem
の演算子を作成するのも良い考えです
ピップ、148 135 133 138 バイト
aRM"\
"R`("|').*?(?<!\\)(\\\\)*\1`{lPBaC:++i+191}R[`//.*``#.*`{X*aJw.`(?=`}.')M[A`\w`RL2"++""--""/*"]w`¶+`'·C(192+,#l)][x_WR'¶{aRw'·}xnsl]
CP-1252 ではバイト数がカウントされるため、¶
と ·
はそれぞれ 1 バイトです。これは、C コードが単一のコマンド ライン引数として想定されていることに注意してください。これには、(実際のコマンド ラインでは) 大量のエスケープ シーケンスを使用する必要があります。オンラインでお試しください!
やや非ゴルフバージョンの説明
このコードは、いくつかのトリックを使用して、一連の置換操作を実行します。
バックスラッシュの継続
私たちは RM
リテラル文字列のすべての出現
"\
"
つまり、バックスラッシュの後に改行が続きます。
文字列と文字リテラル
コールバック関数で正規表現の置換を使用します:
`("|').*?(?<!\\)(\\\\)*\1`
{
lPBa
C(++i + 191)
}
正規表現は一重引用符または二重引用符に一致し、その後に貪欲でない .*?
が続きます 0 個以上の文字に一致するものをできるだけ少なくします。前の文字がバックスラッシュでないことを確認するために、否定的な後読みがあります。次に、偶数個のバックスラッシュとその後に続く開始区切り文字を一致させます。
コールバック関数は文字列/文字リテラルを受け取り、それをリスト l
の後ろにプッシュします .次に、文字コード 192 (À
) で始まる文字を返します。 ) であり、リテラルが置換されるたびに増加します。したがって、コードは次のように変換されます:
printf("%c", '\'');
printf(À, Á);
これらの置換文字は、ソース コードで発生しないことが保証されているため、後で明確に逆置換できます。
コメント
`//.*`
x
正規表現は //
に一致します さらに、改行までのすべてを x
に置き換えます (空の文字列にあらかじめ設定されています)。
プリプロセッサ ディレクティブ
`#.*`
_WR'¶
¶
のポンド記号で始まる非改行文字の実行をラップします .
なくしてはいけないスペース
{
(
X*a J w.`(?=`
) . ')
}
M
[
A`\w` RL 2
"++"
"--"
"/*"
]
{
a R w '·
}
ここでは多くのことが起こっています。最初の部分は、置換する正規表現のこのリストを生成します:
[
`(?a)\w\s+(?=(?a)\w)` Whitespace surrounded by [a-zA-Z_]
`\+\s+(?=\+)` Whitespace surrounded by +
`\-\s+(?=\-)` Whitespace surrounded by -
`\/\s+(?=\*)` Whitespace surrounded by / *
]
先読みを使用して一致させることに注意してください。たとえば、e
だけです。 define P printf
で .そうすれば、このマッチは P
を消費しません 、これは次の試合で使用できることを意味します。
関数をリストにマッピングすることにより、この正規表現のリストを生成します。リストには
[
[`(?a)\w` `(?a)\w`]
"++"
"--"
"/*"
]
関数は各要素に対してこれを行います:
(X*aJw.`(?=`).')
X*a Map unary X to elements/chars a: converts to regex, escaping as needed
Regexes like `\w` stay unchanged; strings like "+" become `\+`
J Join the resulting list on:
w Preset variable for `\s+`
.`(?=` plus the beginning of the lookahead syntax
( ).') Concatenate the closing paren of the lookahead
正規表現を取得したら、それらの出現箇所をこのコールバック関数に置き換えます:
{aRw'·}
·
で各一致の空白の実行を置き換えます .
空白の削除とクリーンアップ
[w `¶+` '·]
[x n s]
3 つの連続した置換は、残りの空白の実行を置換します (w
) 空の文字列の場合 (x
)、¶
の実行 改行、および ·
の場合
文字列および文字リテラルの後方置換
C(192+,#l)
l
192 + range(len(l))
を取得して、リテラルの置換として使用したすべての文字のリストを作成します そして文字に変換します。 l
で、これらのそれぞれを関連するリテラルに置き換えることができます。 .
以上です!結果の文字列は自動出力されます。
C、497 494 490 489 バイト
C を処理しているので、使用してみましょう C!関数 f()
char ポインター p
から入力を受け取ります ポインタ q
に出力します 、入力が ASCII であると仮定します:
#define O*q++
#define R (r=*p++)
#define V(c)(isalnum(c)||c==95)
char*p,*q,r,s,t;d(){isspace(r)?g():r==47&&*p==r?c(),g():r==92?e():(O=s=r)==34?b():r==39?O=R,a():r?a():(O=r);}a(){R;d();}b(){((O=R)==34?a:r==92?O=R,b:b)();}c(){while(R-10)p+=r==92;}e(){R-10?s=O=92,O=r,a():h();}j(){(!isspace(R)?r==47&&*p==r?c(),j:(t=r==35,d):j)();}f(){t=*p==35;j();}i(){V(s)&&V(r)||s==47&&r==42||(s==43||s==45)&&r==s&&*p==s?O=32:0;d();}h(){isspace(R)?g():i();}g(){(r==10?t?O=r,j:*p==35?s-10?s=O=r,j:0:h:h)();}
ファイルは整形式であると想定しています。文字列と文字リテラルは閉じられており、最終行にコメントがある場合は、それを閉じるための改行が必要です。
説明
残念ながら、ゴルフ前のバージョンはわずかに読みやすくなっています:
#define O *q++=
#define R (r=*p++)
#define V(c)(isalnum(c)||c=='_')
char*p,*q,r,s,t;
d(){isspace(r)?g():r=='/'&&*p==r?c(),g():r=='\\'?e():(O s=r)=='"'?b():r=='\''?O R,a():r?a():(O r);}
a(){R;d();}
b(){((O R)=='"'?a:r=='\\'?O R,b:b)();}
c(){while(R!='\n')p+=r=='\\';}
e(){R!='\n'?s=O'\\',O r,a():h();}
j(){(!isspace(R)?r=='/'&&*p==r?c(),j:(t=r=='#',d):j)();}
f(){t=*p=='#';j();}
i(){V(s)&&V(r)||s=='/'&&r=='*'||(s=='+'||s=='-')&&r==s&&*p==s?O' ':0;d();}
h(){isspace(R)?g():i();}
g(){(r=='\n'?t?O r,j:*p=='#'?s!='\n'?s=O r,j:0:h:h)();}
末尾再帰によるステート マシンを実装します。ヘルパー マクロと変数は
O
o の場合 出力R
rへ 入力をr
に読み込むV
v を決定する 有効な識別子文字 (!isalnum('_')
以降) )p
そしてq
- 説明されている I/O ポインターr
- 最後の文字は r です 読むs
- 最近の非空白文字を保存t
- t プリプロセッサ ディレクティブの作業中の ag
私たちの州は
a()
- 通常の C コードb()
- 文字列リテラルc()
- コメントd()
-r
を読んだ後の通常の C コードe()
- エスケープ シーケンスf()
- 初期状態 (主な機能)g()
- 空白でh()
- 空白 -g()
にディスパッチ またはi()
i()
- 空白の直後 - スペース文字を挿入する必要がありますか?j()
- 最初の空白 - スペース文字を挿入しないでください
テスト プログラム
#define DEMO(code) \
do { \
char in[] = code; \
char out[sizeof in]; \
p=in;q=out;f(); \
puts("vvvvvvvvvv"); \
puts(out); \
puts("^^^^^^^^^^"); \
} while (0)
#include<stdio.h>
#include<stdlib.h>
int main()
{
DEMO(
"main() {\n"
" printf(\"Hello, World!\"); // hi\n"
"}\n"
);
DEMO(
"#define max(x, y) \\\n"
" x > y ? x : y\n"
"#define I(x) scanf(\"%d\", &x)\n"
"a;\n"
"b; // just a needless comment, \\\n"
" because we can!\n"
"main()\n"
"{\n"
" I(a);\n"
" I(b);\n"
" printf(\"\\\" max \\\": %d\\n\", max(a, b));\n"
"}\n"
);
DEMO(
"x[10];*c;i;\n"
"main()\n"
"{\n"
" int _e;\n"
" for(; scanf(\"%d\", &x) > 0 && ++_e;);\n"
" for(c = x + _e; c --> x; i = 100 / *x, printf(\"%d \", i - --_e));\n"
"}\n"
);
DEMO(
"// often used functions/keywords:\n"
"#define P printf(\n"
"#define A case\n"
"#define B break\n"
"\n"
"// loops for copying rows upwards/downwards are similar -> macro\n"
"#define L(i, e, t, f, s) \\\n"
" for (o=i; o e;){ strcpy(l[o t], l[o f]); c[o t]=c[s o]; }\n"
"\n"
"// range check for rows/columns is similar -> macro\n"
"#define R(m,o) { return b<1|b>m ? m o : b; }\n"
"\n"
"// checking for numerical input is needed twice (move and print command):\n"
"#define N(f) sscanf(f, \"%d,%d\", &i, &j) || sscanf(f, \",%d\", &j)\n"
"\n"
"// room for 999 rows with each 999 cols (not specified, should be enough)\n"
"// also declare \"current line pointers\" (*L for data, *C for line length),\n"
"// an input buffer (a) and scratch variables\n"
"r, i, j, o, z, c[999], *C, x=1, y=1;\n"
"char a[999], l[999][999], (*L)[999];\n"
"\n"
"// move rows down from current cursor position\n"
"D()\n"
"{\n"
" L(r, >y, , -1, --)\n"
" r++ ? strcpy(l[o], l[o-1]+--x), c[o-1]=x, l[o-1][x]=0 : 0;\n"
" c[y++] = strlen(l[o]);\n"
" x=1;\n"
"}\n"
"\n"
"// move rows up, appending uppermost to current line\n"
"U()\n"
"{\n"
" strcat(*L, l[y]);\n"
" *C = strlen(*L);\n"
" L(y+1, <r, -1, , ++)\n"
" --r;\n"
" *l[r] = c[r] = 0;\n"
"}\n"
"\n"
"// normalize positions, treat 0 as max\n"
"X(b) R(c[y-1], +1)\n"
"Y(b) R(r, )\n"
"\n"
"main()\n"
"{\n"
" for(;;) // forever\n"
" {\n"
" // initialize z as current line index, the current line pointers,\n"
" // i and j for default values of positioning\n"
" z = i = y;\n"
" L = l + --z;\n"
" C = c + z;\n"
" j = x;\n"
"\n"
" // prompt:\n"
" !r || y/r && x > *C\n"
" ? P \"end> \")\n"
" : P \"%d,%d> \", y, x);\n"
"\n"
" // read a line of input (using scanf so we don't need an include)\n"
" scanf(\"%[^\\n]%*c\", a)\n"
"\n"
" // no command arguments -> make check easier:\n"
" ? a[2] *= !!a[1],\n"
"\n"
" // numerical input -> have move command:\n"
" // calculate new coordinates, checking for \"relative\"\n"
" N(a)\n"
" ? y = Y(i + (i<0 | *a=='+') * y)\n"
" , x = X(j + (j<0 || strchr(a+1, '+')) * x)\n"
" :0\n"
"\n"
" // check for empty input, read single newline\n"
" // and perform <return> command:\n"
" : ( *a = D(), scanf(\"%*c\") );\n"
"\n"
" switch(*a)\n"
" {\n"
" A 'e':\n"
" y = r;\n"
" x = c[r-1] + 1;\n"
" B;\n"
"\n"
" A 'b':\n"
" y = 1;\n"
" x = 1;\n"
" B;\n"
"\n"
" A 'L':\n"
" for(o = y-4; ++o < y+2;)\n"
" o<0 ^ o<r && P \"%c%s\\n\", o^z ? ' ' : '>', l[o]);\n"
" for(o = x+1; --o;)\n"
" P \" \");\n"
" P \"^\\n\");\n"
" B;\n"
"\n"
" A 'l':\n"
" puts(*L);\n"
" B;\n"
"\n"
" A 'p':\n"
" i = 1;\n"
" j = 0;\n"
" N(a+2);\n"
" for(o = Y(i)-1; o<Y(j); ++o)\n"
" puts(l[o]);\n"
" B;\n"
"\n"
" A 'A':\n"
" y = r++;\n"
" strcpy(l[y], a+2);\n"
" x = c[y] = strlen(a+2);\n"
" ++x;\n"
" ++y;\n"
" B;\n"
"\n"
" A 'i':\n"
" D();\n"
" --y;\n"
" x=X(0);\n"
" // Commands i and r are very similar -> fall through\n"
" // from i to r after moving rows down and setting\n"
" // position at end of line:\n"
"\n"
" A 'r':\n"
" strcpy(*L+x-1, a+2);\n"
" *C = strlen(*L);\n"
" x = 1;\n"
" ++y > r && ++r;\n"
" B;\n"
"\n"
" A 'I':\n"
" o = strlen(a+2);\n"
" memmove(*L+x+o-1, *L+x-1, *C-x+1);\n"
" *C += o;\n"
" memcpy(*L+x-1, a+2, o);\n"
" x += o;\n"
" B;\n"
"\n"
" A 'd':\n"
" **L ? **L = *C = 0, x = 1 : U();\n"
" y = y>r ? r : y;\n"
" B;\n"
"\n"
" A 'j':\n"
" y<r && U();\n"
" }\n"
" }\n"
"}\n";);
}
これにより
制限
これは、
などの定義を破ります。#define A (x)
名前と展開を区切るスペースを削除して、
#define A(x)
全く違う意味で。このケースはテスト セットに含まれていないため、取り上げません。
マルチパスのインプレース変換で短いバージョンを作成できると思います - 来週それを試すかもしれません.