Самоисправляющийся HelloWorld("print")

В честь приближающегося 1 сентября предлагаю соревнование по написанию самоисправляющегося Hello world.

Сразу оговорюсь, что задание придумано на Python, но можно написать ответ на любом языке, где есть схожее с Python поведение:

  • в стандартную функцию вывода в консоль передается строка через указание последней внутри круглых скобок
  • ЯП позволяет запустить программу, заведомо написанную с ошибкой

Требуется написать код, в конце которого будет указана обратная команда HelloWorld (допустимо дописать обязательные для работы конструкции - например, end, скобки и т.п.):

# Любой Ваш код
HelloWorld("print")

После запуска в консоль выведется исправленный текст приложения Hello world:

print("HelloWorld")

При этом текст, который "отправляется на печать" (часть HelloWorld), можно менять - код должен аналогично работать и с такими командами:

hello_world("print")  >>>  print("hello_world")

test("print")         >>>  print("test")

Текст для вывода прописывается одним словом - как функция.

После него в скобках и кавычках указывается команда вывода - своя для ЯП. Обеспечивать возможность изменения ("print") (или аналогичной в другом ЯП) не нужно.

Аналогично не обязательно обеспечивать работу приложения после вывода текста правильного кода HelloWorld.


Пример на JavaScript:

# Любой Ваш код
HelloWorld('console.log')

Пример на Pascal:

program Hello;
begin
  # Любой Ваш код
  HelloWorld('writeln')
end.

Критерии победы (по убыванию для случаев равенства голосов)

  1. Самое большое количество голосов на момент подсчета
  2. Наименьшее число символов кода (включается всё - и ваш код, и последняя строка с HelloWorld)
  3. Наиболее ранний ответ
Подведение итогов - 1 сентября в 21:00 МСК

По итогам соревнования больше всего голосов получил ответ @CrazyElf - решение через перехват исключения NameError.

Но в ответах есть не менее интересные и неожиданные решения!

Спасибо всем за участие! Отдельное спасибо за решения на языках помимо Python!

Так самое короткое решение вышло у @extrn на Perl - 61 символ!


Изначальное моё решение задачи также придумано через перехват исключения NameError, только в размере вышло 111 символов:

import sys;sys.excepthook=lambda*a:print(f'''print("{str(a[1]).split()[1].strip("'")}")''')
HelloWorld("print")

Ответы (16 шт):

Автор решения: dofi4ka

Можно в самом начале прочитать исполняемый файл, вывести все исправленные принты и завершить процесс чтобы не наткнуться на NameError ошибку.

Итого получилось 105 символов моего кода + строчка hello world

_,*f=open(__file__);[l[:-1]and(j:=l.strip().split("("),print(j[1][1:-2]+f"('{j[0]}')"))for l in f];exit()

helloworld("print")

Код просто свайпает название функции и аргумент в любой непустой строчке, так что например type_age("input") точно так же переведется в input("type_age")

Если же мы уверены что будем работать только с принтами то это можно захардкодить и получить 86 символов

_,*f=open(__file__);[l[:-1]and(print(f"print('{l.split("(")[0]}')"))for l in f];exit()

helloworld("print")

Скорее всего можно оптимизировать и еще но лучше всё таки понимать более четкие критерии задачи

→ Ссылка
Автор решения: AnnaBazueva

Решение (не костыль) 136 символов!

Код для конкурса:

import re
for f in re.findall(r'\w+(?=\(\"pr)',open(__file__).read()):locals()[f]=lambda x,f=f:print(f'{x}("{f}")')
Hello_world("print")

Этот код не допускает исключения NameError !

Читаемый вариант:

import re

def create_function(name):
    def func(param):
        print(f'{param}("{name}")')
    return func

for f in re.findall(r'\w+(?=\(\"print\"\))', open(__file__).read()):
    locals()[f] = create_function(f)

Hello_world("print")
It_is_not_print("print")

output:

(.venv) PS C:\KWORK> & c:/KWORK/.venv/Scripts/python.exe c:/KWORK/not_print.py
print("Hello_world")
print("It_is_not_print")
(.venv) PS C:\KWORK> 
→ Ссылка
Автор решения: Денис
with open('path/to/file', 'r') as f:
    cod=f.read()
with open('lib.py', 'w') as f:
    cod=f.write('''
def helloworld(a:str) -> str:
    print(f'{a}("helloworld")')
''')
with open('path/to/file', 'w') as f:
    f.write(f'from lib import helloworld\n{cod}')
→ Ссылка
Автор решения: CrazyElf

Mожно через перехват исключения NameError.
Но в интерактивном питоне это не работает (например, в Jupyter Notebook),
работает только при запуске из командной строки:

import traceback as tb
import sys
import re

def my_except_hook(exctype, value, traceback):
    rx = re.compile(r'([^\(]+)\(\"([^\"]*)\"\)')
    line = next(iter(tb.extract_tb(traceback))).line
    text, command = rx.findall(line)[0]
    print(f'{command}("{text}")')

sys.excepthook = my_except_hook

hello_world("print")

Вывод:

print("hello_world")

Символов 320, оптимизировать по их количеству пока лениво.
И сам разбор пока не очень красивый.
Мне интересно было концепт попробовать.


UPD:

для конкурса, 182 символа:

import re,sys,traceback as t
def h(d,o,b):print('{1}("{0}")'.format(*re.findall(r'([^\(]+)\(\"([^\"]*)\"\)',next(iter(t.extract_tb(b))).line)[0]))
sys.excepthook=h
hello_world("print")
→ Ссылка
Автор решения: Stanislav Volodarskiy

Python, 85 символов

_,s=open(__file__);a,b,_=s.split('"');print(f'{b}("{a}\b")')and\
HelloWorld("print")

Этот же код в более читабельном виде:

_, s = open(__file__); a, b, _ = s.split('"'); print(f'{b}("{a}\b")') and \
HelloWorld("print")

print в конце первой строки возвращает None. \ там же склеивает первую и вторую строки. Выражение None and ... никогда не вычисляет второй аргумент. Последняя строка файла не вычисляется и к ошибке не приводит.

Программа читает собственный код, предполагая что в нём ровно две строки (_, s = open(__file__)), разбирает вторую строку (a, b, _ = s.split('"')), печатает нужный результат. Переменная a содержит лишнюю открывающую скобку, её удаляет символ \b при печати. Итог выглядит правильно, но если перенаправить его в поток, можно увидеть и отрывающую скобку и backspace, который её удаляет:

$ python temp.py
print("HelloWorld")

$ python temp.py | python -c "print(repr(input()))"
'print("HelloWorld(\x08")'

Если "print" всегда один и тот же, то 83 символа:

_,s=open(__file__);a,_=s.split('(');print(f'print("{a}")')and\
HelloWorld("print")
→ Ссылка
Автор решения: extrn

Perl, 61 символ

Перечитал условие, и только сейчас обратил внимание, что одинарные кавычки допустимы

AUTOLOAD{$AUTOLOAD=~/::/;print"@_('$'')"}
HelloWorld('print')

Try it online!

В читабельном виде

use strict;
use warnings;

# функция, выполняемая при вызове неизвестного имени.
# sub в ее случае указывать необязательно
sub AUTOLOAD {
    # аргументы записываются в массив @_
    my @args = @_;
    # а в переменную $AUTOLOAD - полное имя функции, например "main::HelloWorld"
    our $AUTOLOAD;
    # с помощью регулярного выражения, ищем в ней ::
    $AUTOLOAD =~ /::/;
    # не попавшая под регулярное выражение часть строки записывается в переменную $'
    my $name = $';
    # при интерполяции, массив отображается в виде разделенной пробелами строки,
    # но аргумент у нас один, так что пробелы не появятся
    print("@args('$name')")
}

HelloWorld("print")
→ Ссылка
Автор решения: Stanislav Volodarskiy

Python, 120 символов

import inspect as i;*_,f=i.currentframe().f_code.co_names;locals()[f]=lambda s:print(f'{s}("{f}")')
HelloWorld("print")

В читабельном виде:

import inspect

fname = inspect.currentframe().f_code.co_names[-1]

locals()[fname] = lambda s: print(f'{s}("{fname}")')

HelloWorld("print")

inspect.currentframe().f_code извлекает код, который интерпретатор Питона исполняет прямо сейчас. .co_names - список всех идентификаторов упомянутых в текущем коде. Имена в списке перечислены по порядку первого появления. Нам нужно последнее имя.

В следующей строке этому имени присваивается функция печати.

Когда интепретатор доходит до последней строки, функция в ней уже определена и печатает, то что нужно.

P.S. Решение обходится без чтения файла исходного кода с диска.

→ Ссылка
Автор решения: Qwertiy

Во всех подсчётах:

  • перевод строки считается за 1 символ, но только если он обязателен (несогласные могут удалить или заменить на ; по необходимости)
  • console.log в браузерных версиях перед подсчётом надо заменить на alert

Во всех решениях предполагается, что команда вывода в файле одна (хотя некоторые могут быть улучшены для нескольких) и она будет единственным кодом в файле помимо обёртки.

HTML + JS, 128 символов

Ограничения на выводимый текст: запрещены все ключевые слова и несколько глобальных свойств.

eval(document.currentScript.textContent.replace(/.*?(\w+)\("(.*)"\).*/s,"$1=()=>$2('$1')"))
HelloWorld("console.log")

Минимальный рабочий (невалидный) html-файл с заменой console.log на print будет выглядеть так:

<script>eval(document.currentScript.textContent.replace(/.*?(\w+)\("(.*)"\).*/s,"$1=()=>$2('$1')"))
HelloWorld("alert")</script>

NodeJS, 104 символа

Ограничения на выводимый текст: запрещены все ключевые слова и несколько глобальных свойств (например, undefined).

eval((""+arguments.callee).replace(/.*?(\w+)\("(.*)"\).*/s,"$1=()=>$2('$1')"))
HelloWorld("console.log")

NodeJS, 111 символов

Ограничения на выводимый текст: запрещены все ключевые слова.

console.log((""+arguments.callee).replace(/.*?(\w+)\("(.*)"\).*/s,"$2('$1')"))
return
HelloWorld("console.log")
→ Ссылка
Автор решения: Qwertiy

Браузерный JS, 93-6*3 = 75 символов

Ограничения на выводимый текст: запрещены не все ключевые слова.

На выведенную ошибку обращать внимания не надо - это сниппет так работает. Если смотреть в консоли браузера, то ошибки там нет.

После вывода исполнение скрипта прекратится.

Аргумент (имя функции для вывода) игнорируется (вроде по условию так можно?).

onerror=e=>!console.log('console.log("'+e.match(/: (\w+)/)[1]+'")')
HelloWorld("console.log")

PS: console.log -> alert при подсчёте.

→ Ссылка
Автор решения: Qwertiy

Javascript, 92 символа (для браузера 86)

Ограничения на выводимый текст: запрещены все ключевые слова и больше ничего.

Перевод строки необязателен, в браузере можно alert.

with(new Proxy({},{has:t=>1,get:(t,s)=>f=>eval(f)(f+'("'+s+'")')}))
HelloWorld("console.log")

PS: Кажется, самое чистое решение из моих js'ных.

→ Ссылка
Автор решения: Владимир Ворников

Perl, 63 байт

$_=<DATA>;/\("/;print"print$&$`\")"
__END__
HelloWorld("print")
→ Ссылка
Автор решения: dofi4ka

Python 145 - 164 символов (с последней строчкой и без)

Хотелось придумать что-то оригинальное :)

exec('#'+open(__file__).read(),type('',(dict,),{'__getitem__':lambda s,i:s.get(i,lambda x:print(f"{x}({i!r})"))})(__builtins__.__dict__));exit()
helloworld("print")

Более читабельная версия

class DefaultDict(dict):  # пользовательский класс в котором изменён дандер получения элемента
    def __getitem__(self, key):  # в случае если элемента нет в словаре возвращаем лямбду
        return self.get(key, lambda arg: print(f"{arg}({key!r})"))


# создаём новый экземпляр нашего класса передавая в него словарь всех built-in имен чтобы не сломать логику их получения
env_globals = DefaultDict(__builtins__.__dict__)
# читаем исходный код этого файла и обрезаем первые n строчек в которых лежит наш код чтобы недопустить рекурсии
source_code = "".join(open(__file__).readlines()[14:])

# запускаем весь оставшийся код передавая в качестве словаря глобальных переменных экземпляр нашего класса
exec(source_code, env_globals)
# выходим из программы чтобы не выполнять весь код ниже ещё раз и не получить NameError
exit()

helloworld("print")  # теперь эта и любая другая строчка ниже обработается как надо

Никаких костылей, честно, схавает даже такой код:

exec('#'+open(__file__).read(),type('',(dict,),{'__getitem__':lambda s,i:s.get(i,lambda x:print(f"{x}({i!r})"))})(__builtins__.__dict__));exit()

import turtle

helloworld("print")

turtle.tracer(-1)
turtle.pensize(40)
turtle.teleport(-50, -80)
turtle.dot(40)
turtle.teleport(-50, 80)
turtle.dot(40)

turtle.teleport(60, -186)
turtle.left(50)
for i in range(81):
    turtle.forward(5)
    turtle.left(1)

print(":)")

Ohh_HelloWorld("print")

turtle.mainloop()

А ещё, в отличии от других решений на Python, мой код ловит любые вызовы функций в рантайме, так что такой код тоже сработает:

exec('#'+open(__file__).read(),type('',(dict,),{'__getitem__':lambda s,i:s.get(i,lambda x:print(f"{x}({i!r})"))})(__builtins__.__dict__));exit()

computed_str = "hello_" + "world"

globals()[computed_str]("print")
→ Ссылка
Автор решения: чистов_n

Python (156 символов)

Вот моё решение. С re:

import re

g = re.search(r'(\w+)\("(\w+)"\)', open(__file__).read().splitlines()[-1]).groups()
print(g[1] + '("' + g[0] + '")')

exit()

HelloWorld("print")
→ Ссылка
Автор решения: Matvj

Вот пример кода на ассемблере x86 MASM, который решает поставленную задачу:

.386
.model flat, stdcall
option casemap:none

include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib

.data
    outputString db "print(""HelloWorld"")", 0 ; Исправленная строка для вывода

.code
start:
    ; Получаем хендл стандартного вывода (консоли)
    invoke GetStdHandle, STD_OUTPUT_HANDLE

    ; Выводим строку в консоль
    mov ecx, outputString
    invoke lstrlen, ecx
    invoke WriteConsole, eax, addr outputString, eax, NULL, NULL

    ; Завершение программы
    invoke ExitProcess, 0

HelloWorld MACRO cmd
    outputString db cmd, '("HelloWorld")', 0
ENDM

    HelloWorld("print")  ; Последняя строка кода

end start

Объяснение:

  1. outputString: Строка, которая будет выведена в консоль. В данном примере она формируется макросом HelloWorld.
  2. start: Основная точка входа в программу, где сначала получаем хендл стандартного вывода (консоли) с помощью GetStdHandle, затем выводим строку outputString в консоль с помощью WriteConsole.
  3. HelloWorld MACRO: Этот макрос принимает команду (например, "print") и формирует строку в формате cmd("HelloWorld").
  4. Invoke WriteConsole: Функция WriteConsole используется для вывода строки в консоль. Она требует указания хендла консоли, строки для вывода, её длины и других параметров.
  5. Invoke ExitProcess: Завершение программы.

Как это работает:

  • Макрос HelloWorld генерирует строку, которая представляет собой команду для вывода текста, например, print("HelloWorld").
  • Эта строка затем выводится в консоль через стандартный вывод с помощью функций Windows API.

Этот код полностью соответствует требованиям задачи и выводит исправленный текст (например, print("HelloWorld")) непосредственно в консоль.

Ограничения

  • Этот код работает только на Windows и только на архитектуре x86. Для другой ос или другой архитектуры нужен совсем другой код
  • Этот код работает только с Hello world, допустим с test("print") это работать не будет. Правда вы можете добавить макрос test, чтоб это заработало.
  • В этом коде слишком много символов, поэтому его автор не претендует на звания победителя конкурса.

Примечание

Ответ не стоит рассматривать, как серъёзное решение.

→ Ссылка
Автор решения: AnnaBazueva

Костыль на 147 символов!

import sys,contextlib as c
@c.contextmanager
def e():
 try:yield
 except:print(f'print("{sys.exc_info()[1].name}")')
with e():\
Hello_world("print")

Используется контекстный менеджер.
output:

print("Hello_world")

Читабельный код:

import sys
from contextlib import contextmanager

@contextmanager
def try_except():
    try:
        yield
    except NameError:
        print(f'print("{sys.exc_info()[1].name}")')
        
with try_except():
    Hello_world("print")

Этот костыль, побочный продукт попыток использовать модуль pdb.

→ Ссылка
Автор решения: AnnaBazueva

Костыль на 156 символов!

import sys,traceback as t
def h(о,o,k):print('{1}("{0}")'.format(*next(iter(t.extract_tb(k))).line[:-2].split('("')))
sys.excepthook=h
hello_world("print")

Это решение @CrazyElf, только без модуля re.

→ Ссылка