Perl, 1116 1124 바이트, n = 3, 점수 = 1124 ^ (2/3) 또는 약 108.1
업데이트 : 이제 이것이 무차별 대입을 통해 n = 3에서 작동한다는 것을 확인했습니다 (2 일이 걸렸습니다). 이 복잡한 프로그램을 사용하면 손으로 방사선 저항을 확인하기가 어렵습니다 (이전 버전에서는 실수가 발생하여 바이트 수가 증가했습니다). 최종 업데이트
보이지 않는 곳에서 stderr를 리디렉션하는 것이 좋습니다. 이 프로그램은 문자를 삭제 하지 않아도 의심스러운 구문에 대한 경고를 생성 합니다.
프로그램이 단축 될 수 있습니다. 이 작업은 상당히 고통스럽기 때문에 가능한 미세 최적화를 놓치기 쉽습니다. 나는 프로그램 의 정말 어려운 부분 이기 때문에 가능한 많은 삭제 가능한 문자를 최대한 많이 목표로 삼고 있었고 , 코드 골프 타이 브레이크는 목표로하기 좋은 것이지만 내가 넣지 않은 것으로 취급했습니다. 우발적으로 방사선 저항을 깨뜨리기가 매우 쉽다는 근거로 최적화에 대한 우스운 노력.
프로그램
참고 : _
의 네 가지 발생 직전에 리터럴 제어 문자 (ASCII 31)가 -+
있습니다. StackOverflow에 복사하여 붙여 넣은 것으로 생각하지 않으므로 프로그램을 실행하기 전에 다시 추가해야합니다.
eval+<eval+<eval+<eval+(q(FoPqOlengthFoBBPP181XXVVVVJJJKKKNdoWchopJFtPDevalMODx4KNFrPIPA-MN-TUV-ZPINFsPIFoPqOI.Fo.IQNevalFoINevalIFsPZyI.Fr.IT-UPINsayDFtJqJFsKPZyPT-UFWrYrKD.DEEEEQDx6NsayDNDforB1..4YforB1..4NexitQNevalFo)=~y=A-Z=-+;-AZz-~=r)####>####>####>####>####>####>
;
;
;
;
eval+<eval+<eval+<eval+(q(FoPqOlengthFoBBPP181XXVVVVJJJKKKNdoWchopJFtPDevalMODx4KNFrPIPA-MN-TUV-ZPINFsPIFoPqOI.Fo.IQNevalFoINevalIFsPZyI.Fr.IT-UPINsayDFtJqJFsKPZyPT-UFWrYrKD.DEEEEQDx6NsayDNDforB1..4YforB1..4NexitQNevalFo)=~y=A-Z=-+;-AZz-~=r)####>####>####>####>####>####>
;
;
;
;
eval+<eval+<eval+<eval+(q(FoPqOlengthFoBBPP181XXVVVVJJJKKKNdoWchopJFtPDevalMODx4KNFrPIPA-MN-TUV-ZPINFsPIFoPqOI.Fo.IQNevalFoINevalIFsPZyI.Fr.IT-UPINsayDFtJqJFsKPZyPT-UFWrYrKD.DEEEEQDx6NsayDNDforB1..4YforB1..4NexitQNevalFo)=~y=A-Z=-+;-AZz-~=r)####>####>####>####>####>####>
;
;
;
;
eval+<eval+<eval+<eval+(q(FoPqOlengthFoBBPP181XXVVVVJJJKKKNdoWchopJFtPDevalMODx4KNFrPIPA-MN-TUV-ZPINFsPIFoPqOI.Fo.IQNevalFoINevalIFsPZyI.Fr.IT-UPINsayDFtJqJFsKPZyPT-UFWrYrKD.DEEEEQDx6NsayDNDforB1..4YforB1..4NexitQNevalFo)=~y=A-Z=-+;-AZz-~=r)####>####>####>####>####>####>
;
;
;
;
설명
이 프로그램은 서로 분명히 연결된 4 개의 동일한 작은 프로그램으로 구성되어 있습니다. 기본 아이디어는 프로그램의 각 사본이 실행하기에 너무 심하게 손상되었는지 여부를 확인한다는 것입니다. 만일 그렇다면, 경고를 뿌릴 수있는 것 외에는 아무 것도하지 않고 다음 사본을 실행시킵니다; 삭제되지 않았거나 삭제 된 문자가 프로그램 작동에 아무런 영향을 미치지 않는 문자 인 경우, 프로그램의 전체 소스 코드를 인쇄하는 기발한 작업을 수행합니다. 각 부분에 전체 소스 코드의 인코딩이 포함되어 있음)을 종료 한 다음 종료합니다 (다른 손상되지 않은 복사본이 소스 코드를 다시 인쇄하지 못하도록하여 너무 많은 텍스트를 인쇄하여 quine을 망칠 수 없음).
각 부분은 기능적으로 독립적 인 두 부분으로 구성됩니다. 외부 래퍼 및 일부 내부 코드 따라서 별도로 고려할 수 있습니다.
외부 포장지
외부 래퍼는 기본적으로 eval<+eval<+eval< ... >####>####...>###
(반드시 세미콜론과 줄 바꿈이 많은 목적을 가지고 있어야합니다. 세미콜론 또는 그 이전 줄 바꿈에 관계없이 프로그램의 일부가 분리되어 유지되도록하는 것입니다) ). 이것은 매우 단순 해 보이지만 여러 가지면에서 미묘하고이 과제에 대해 Perl을 선택한 이유입니다.
먼저 래퍼가 프로그램의 손상되지 않은 복사본에서 어떻게 작동하는지 봅시다. eval
내장 함수로 구문 분석하여 하나의 인수를 사용합니다. 논쟁이 예상되기 때문에 +
여기에 단항 +
이 있습니다 (지금까지 Perl 골퍼에게 매우 친숙 할 것입니다. 놀랍게도 종종 유용합니다). 우리는 여전히 인수를 기대하고 있습니다 (우리는 단항 연산자를 보았습니다). <
다음에 오는 것은 <>
연산자 의 시작으로 해석됩니다 (접두사 또는 접두사 인수를 취하지 않으므로 피연산자 위치에서 사용할 수 있습니다).
<>
상당히 이상한 연산자입니다. 일반적으로 파일 핸들을 읽고 파일 핸들 이름을 꺾쇠 괄호 안에 넣 습니다. 또는 표현식이 파일 핸들 이름으로 유효하지 않은 경우 표현식이 기본적으로 사용됩니다 (기본적으로 UNIX 쉘이 사용자가 입력 한 텍스트를 일련의 명령 행 인수로 변환하는 데 사용하는 것과 동일한 프로세스, 실제로는 이전 버전의 Perl이 사용됨) 이것에 대한 쉘이지만 요즘 Perl은 내부적으로 globbing을 처리합니다. 따라서 의도 된 용도는의 행을 따라 있으며 <*.c>
일반적으로와 같은 목록을 반환합니다 ("foo.c", "bar.c")
. 스칼라 컨텍스트에서 (예 :eval
), 처음 실행될 때 찾은 첫 번째 항목 만 반환하고 (첫 번째 인수와 동일) 절대 발생하지 않는 가상의 미래 실행에 대해서는 다른 항목을 반환합니다.
이제 쉘은 종종 명령 행 인수를 처리합니다. -r
인수가없는 것과 같은 것을 주면 해당 이름의 파일이 있는지 여부에 관계없이 프로그램에 그대로 전달됩니다. 펄은 같은 방식으로 동작하므로, 쉘과 펄에 특수한 문자가없는 <
것과 일치하는 것 사이에 문자가없는 한 >
, 이것을 문자 그대로 리터럴처럼 사용할 수 있습니다. 따옴표와 같은 연산자에 대한 Perl의 파서는 이해가 안되는이와 같은 상황에서도 대괄호를 일치시키는 강박적인 경향이 있으므로 <>
안전하게 중첩 할 수 있습니다 (이 프로그램이 가능한 발견). 이러한 중첩 된 모든의 가장 큰 단점은 <>
의 내용을 탈출이다<>
거의 불가능하다. 각각에 2 개의 <>
이스케이프 처리 가있는 것처럼 보이 므로 3 개의 내부에서 무언가를 피하려면 63 개의 백 슬래시가 선행되어야합니다. 나는이 문제에서 코드 크기가 단지 이차적 인 고려 사항이라고 생각했지만, 내 점수에 이런 종류의 페널티를 지불 할 가치는 거의 없었으므로, 문제의 문자를 사용하지 않고 나머지 프로그램을 작성하기로 결정했습니다.
래퍼의 일부가 삭제되면 어떻게됩니까?
- 단어의 삭제는
eval
그것이로 전환하는 원인 bareword는 어떤 의미로, 문자열을. Perl은 이것을 좋아하지 않지만 인용 부호로 묶인 것처럼 처리합니다. 따라서 다음과 같이 eal<+eval<+...
해석됩니다"eal" < +eval<+...
. 기본적으로 많이 중첩 된 에바 (어쨌든 사용하지 않는)의 결과를 가져 와서 정수로 변환하고 무의미한 비교를 수행하기 때문에 프로그램 작동에는 영향을 미치지 않습니다. (이러한 종류의 상황은 정상적인 상황에서 유용한 것은 아니기 때문에 경고 스팸이 많이 발생합니다. 우리는이를 사용하여 삭제를 흡수합니다.) 이렇게하면 필요한 여는 꺾쇠 괄호 수가 변경됩니다 (열기 괄호 때문에) 이제는 비교 연산자로 해석되고 있지만 마지막에 주석 체인은 문자열이 중첩 된 횟수에 관계없이 안전하게 종료되도록합니다. (여기에 #
엄격히 필요한 것보다 더 많은 표시가 있습니다. 프로그램을 압축하기 위해 작성했던 것처럼 작성했습니다.
- A는 경우
<
삭제됩니다, 코드는 이제 구문 분석합니다 eval(eval<...>)
. 보조, 외부는 eval
우리는 그들이 전혀 정상적으로 돌아 오면 (프로그램 등 실제 효과가 아무것도 반환하지 평가하고있는 프로그램은, 그것은 일반적으로 NULL 문자열 또는 bareword는 때문에, 아무런 효과가 없습니다, 더 일반적으로 그들은을 통해 반환 예외 eval
는 null 문자열을 반환하거나 exit
전혀 반환하지 않도록 사용 합니다).
- a
+
가 삭제되면 인접한 코드가 손상되지 않은 경우 즉시 영향을 미치지 않습니다. 단항 +
은 프로그램에 영향을 미치지 않습니다. (원본 +
이 존재 하는 이유는 손상을 복구하는 데 도움 이되므로 관계 연산자가 아닌 <
단항으로 해석 되는 상황의 수가 증가하므로 <>
유효하지 않은 프로그램을 생성하려면 삭제가 더 필요합니다.)
랩퍼 가 충분히 삭제되면 손상 될 수 있지만 구문 분석되지 않는 항목을 생성하려면 일련의 삭제를 수행해야합니다. 네 번의 삭제로 다음을 수행 할 수 있습니다.
eal<evl<eval+<...
Perl에서 관계 연산자 <
는 비 연관 적이므로 구문 오류 (와 동일한 구문 오류 1<2<3
)가 발생합니다. 따라서 작성된 프로그램의 한도는 n = 3입니다. 더 많은 단항을 추가 +
하는 것은 그것을 늘리는 유망한 방법처럼 보이지만 래퍼 내부가 손상 될 가능성이 높아짐에 따라 새 버전의 프로그램이 작동하는 것이 매우 어려울 수 있음을 확인합니다.
랩퍼가 중요한 이유는 eval
Perl에서 구문 오류를 컴파일하려고 할 때 발생하는 예외와 같은 예외를 포착하기 때문입니다. 이 때문에 eval
문자열 리터럴이며, 문자열의 컴파일은 런타임에 발생하고, 문자 그대로 컴파일하지 않을 경우, 결과 예외가 잡힐. 이로 인해 eval
널 문자열이 리턴되고 오류 표시기가 설정 $@
되지만, 우리는 결코 어느 것도 확인하지 않습니다 (반환 된 널 버전의 프로그램에서 리턴 된 널 문자열을 실행하는 경우는 제외). 결정적으로 이것은 내부 코드에 문제가 발생하면래퍼는 구문 오류를 일으킨 다음 래퍼는 코드가 아무것도하지 않게합니다 (그리고 프로그램은 손상되지 않은 자체 사본을 찾기 위해 계속 실행됩니다). 따라서 내부 코드는 래퍼만큼 방사선에 강하지 않아도됩니다. 우리가 걱정하는 것은 손상되면 프로그램의 손상되지 않은 버전과 동일하게 작동하거나 그렇지 않으면 충돌 ( eval
예외를 포착하고 계속할 수 있음)하거나 아무것도 인쇄하지 않고 정상적으로 종료된다는 것입니다.
포장지 내부
래퍼 내부의 코드는 기본적으로 다음과 같습니다 (다시 말해서 _
스택 교환이 바로 앞에 표시되지 않는 Control 이 있습니다 -+
).
eval+(q(...)=~y=A-Z=-+;-AZz-~=r)
(우리가 사용할 수없는이 코드는 글로브 안전 문자로 완전히 작성된 것입니다, 그 목적은 음역과 문자열 리터럴 평가를 통해 가능 실제 프로그램을 작성할 수 있도록 문장 부호의 새로운 문자를 추가하는 것입니다 '
또는 "
우리의 인용 등 마크는하지만 q(
... )
또한) 펄의 문자열을 형성 할 수있는 유효한 방법입니다. (인쇄 할 수없는 문자의 이유는 프로그램에서 문자 공간 문자없이 문자를 공백 문자로 음역해야하기 때문입니다. 따라서 ASCII 31에서 시작하여 범위를 형성하고 해당 범위의 두 번째 요소로 공백을 잡습니다.) 음역을 통해 일부 캐릭터를 제작하는 경우 분명히 음역을하기 위해 캐릭터를 희생해야합니다 .대문자는 그다지 유용하지 않으며 문장 부호에 액세스 할 수없는 것보다 대문자를 쓰는 것이 훨씬 쉽습니다.
glob의 결과로 사용할 수있는 문장 부호 알파벳은 다음과 같습니다 (위 줄은 인코딩을, 아래 줄은 인코딩하는 문자를 나타냅니다).
BCDEFGHIJKLMNOPQRSTUVWXYZ
! "# $ % & '() * +; <=>? @ AZz {|} ~
가장 주목할만한 점은 글로 안전하지 않지만 공백 문자와 함께 Perl 프로그램을 작성하는 데 유용한 문장 부호가 많이 있습니다. 또한 문자 그대로의 문자를 대문자로 된 두, 저장 A
하고 Z
(인코딩되지 자체에 만에를 T
하고 U
있기 때문에, A
상위뿐만 아니라 낮은 범위의 끝점으로 필요했다) 이렇게하면 새로운 인코딩 된 문자 집합을 사용하여 음역 명령을 작성할 수 있습니다 (대문자는 그다지 유용하지 않지만 대문자 변경을 지정하는 데 유용합니다). 우리가 사용할 수 없었던 가장 눈에 띄는 문자는 [
, \
및 ]
이지만 필요하지 않습니다 (출력에 줄 바꿈이 필요할 때 암시 적 줄 바꿈을 사용하여 생성했습니다.say
쓰지 말고 \n
; chr 10
또한 작동했지만 더 장황합니다).
평소와 같이 래퍼 내부가 문자열 리터럴 외부에서 손상되면 어떻게 될지 걱정해야합니다. 손상 eval
되면 실행되는 것이 방지됩니다. 우린 괜찮아 따옴표가 손상되면 문자열 내부가 유효한 Perl이 아니기 때문에 래퍼가 그것을 붙잡을 것입니다. 따라서 문자열에서 많은 빼기를 사용하면 유효한 Perl을 만들 수 있다고해도 아무것도하지 않습니다. 허용되는 결과입니다). 이 구문 오류가 아닌 경우 일반적으로 원인이 평가되는 문자열을 난도질 것, 음역에 손상 이 구문 오류가 될; 나는 이것이 깨지는 경우가 100 % 확실하지 않다. 그러나 나는 그것을 확실하게하기 위해 무차별 강제하고있다.
인코딩 된 프로그램
문자열 리터럴 내부를 살펴보고 내가 사용한 인코딩을 되돌리고 공백을 추가하여 더 읽기 쉽게 만들면 다음 -+
과 같이 A
됩니다.
$o=q<
length$o ==181 || zzzz((()));
do {
chop ($t = "eval+<"x4);
$r = '=-+;-AZz-~=';
$s = '$o=q<' . $o . '>;eval$o';
eval '$s=~y' . $r . 'A-Z=';
say "$t(q($s)=~y=A-Z${r}r)" . "####>"x6;
say ";" for 1..4
} for 1..4;
exit>;
eval $o
Quies에 익숙한 사람들은이 일반적인 구조를 인식 할 것입니다. 가장 중요한 부분은 처음에 $ o가 손상되지 않았 음을 확인합니다. 문자가 삭제 된 경우, 길이가 일치하지 않습니다 181
우리가 실행할 수 있도록, zzzz((()))
이 때문에 타의 추종을 불허 괄호에 구문 오류가 아닌 경우, 이는 당신이 때문에 없음, 모든 세 개의 문자를 삭제하더라도 런타임 오류 것, zzzz
, zzz
, zz
, 그리고 z
함수이며,이 삭제가 아닌 다른 함수로 분석 방지 할 수있는 방법이 없습니다 (((
와 obvous 구문 오류의 원인은. 수표 자체도 손상에 영향을받지 않습니다. 가 ||
손상 될 수 |
있지만이 발생합니다 zzzz((()))
무조건 실행하는 전화를; 변수 또는 상수를 손상 시키면 다음 중 하나를 비교하기 때문에 불일치가 발생합니다 0
.180
, 179
, 178
의 숫자의 어떤 부분 집합에 평등 181
; 1 =
을 제거 하면 구문 분석 오류가 발생하고 2 =
는 필연적으로 LHS가 정수 0 또는 널 문자열로 평가되게하며, 둘 다 거짓입니다.
업데이트 :이 검사는 이전 버전의 프로그램에서 약간 잘못되었으므로 문제를 해결하기 위해 편집해야했습니다. 이전 버전은 디코딩 후 다음과 같습니다.
length$o==179||zzzz((()))
이를 위해 처음 세 문장 부호를 삭제하는 것이 가능했습니다.
lengtho179||zzz((()))
lengtho179
간결한 말은 진실하기 때문에 점검을 어기는 것입니다. B
공백 문자를 인코딩하는 두 개의 문자 를 추가 하여이 문제를 해결했습니다 . 최신 버전의 quine은 다음을 수행합니다.
length$o ==181||zzzz((()))
이제 구문 오류없이 =
부호와 부호 를 숨길 수 없습니다 $
. (난의 길이가 있기 때문에 두 개의 공간이 아닌 하나를 추가했다 180
문자 그대로 둘 것 0
정수 비교 성공한 bareword는, 제로.이 상황에서 악용 될 수있는 소스로 문자를) 최종 업데이트
길이 검사가 통과되면 최소한 문자 삭제 측면에서 사본이 손상되지 않았 음을 알 수 있습니다. 따라서 사본이 간단합니다 (손상된 디코딩 테이블로 인한 구두점 대체는이 검사에서 포착되지 않습니다) 그러나 나는 해독 테이블 에서만 3 번의 삭제 가 퀴인을 깨뜨리지 않는다는 것을 무차별 강제를 통해 이미 확인했습니다 . 아마도 대부분 구문 오류가 발생합니다. 우리는 $o
이미 변수를 가지고 있으므로 외부 래퍼를 하드 코딩하면됩니다 (약간의 압축 정도; 질문 의 코드 골프 부분을 완전히 생략하지는 않았습니다 ). 한 가지 요령은 대부분의 인코딩 테이블을$r
; 내부 래퍼의 인코딩 테이블 섹션을 생성하기 위해 문자 그대로 인쇄하거나 그 주위에 코드를 연결 eval
하고 디코딩 프로세스를 역으로 실행하기 위해 ( $ o 의 인코딩 된 버전이 무엇인지 파악할 수 있습니다 ) 이 시점에서 디코딩 된 버전 만 사용할 수 있습니다.
마지막으로, 우리가 완전한 사본이어서 전체 원본 프로그램을 출력 할 수 있다면 exit
다른 사본도 프로그램을 인쇄하려고 시도하는 것을 막기 위해 호출 합니다.
확인 스크립트
아주 예쁘지는 않지만 누군가가 물었 기 때문에 게시했습니다. 나는 다양한 설정으로 여러 번 실행했습니다 (일반적으로 변경 $min
하고 $max
관심있는 다양한 영역을 확인하기 위해). 완전 자동화 된 프로세스가 아니 었습니다. 다른 곳의 CPU로드로 인해 실행이 중지되는 경향이 있습니다. 이 일이 발생했을 때, 나는 $min
그 첫 번째 값으로 $x
완전히 변경 되지 않았고 스크립트를 계속 실행했습니다 (그래서 범위의 모든 프로그램이 결국 확인되었는지 확인하십시오). 다른 사본에서 삭제하면 더 이상 할 수 없다는 것이 명백하기 때문에 프로그램의 첫 번째 사본에서만 삭제를 확인했습니다.
use 5.010;
use IPC::Run qw/run/;
undef $/;
my $program = <>;
my $min = 1;
my $max = (length $program) / 4 - 3;
for my $x ($min .. $max) {
for my $y ($x .. $max) {
for my $z ($y .. $max) {
print "$x, $y, $z\n";
my $p = $program;
substr $p, $x, 1, "";
substr $p, $y, 1, "";
substr $p, $z, 1, "";
alarm 4;
run [$^X, '-M5.010'], '<', \$p, '>', \my $out, '2>', \my $err;
if ($out ne $program) {
print "Failed deleting at $x, $y, $z\n";
print "Output: {{{\n$out}}}\n";
exit;
}
}
}
}
say "All OK!";
Subleq
. 이런 종류의 도전에 이상적이라고 생각합니다!