d99kris 4 months ago

I do something similar (but less portable and more verbose) in C++ sometimes when I want to prototype something. My boilerplate is something like this:

  #if 0
  TMP=$(mktemp -d);
  c++ -std=c++11 -o ${TMP}/a.out ${0} && ${TMP}/a.out ${@:1}; RV=${?};
  rm -rf ${TMP};
  exit ${RV};
  #endif
    
  #include <iostream>
    
  int main()
  {
    std::cout << "Hello, world!\n";
  }
(the trailing semi-colons in the script part is to make my editor indent the C++ code properly)
  • stefanos82 4 months ago

    HA! I was about to share my way, but you seem to have done more or less what I have in C instead!

    I was having fun with old Golang C code before they started bootstrapping in Go and have found they were using interpunct to separate function names, so I decided to play around with shellscript-like C code to goof around:

            #if 0
            set -e; [ "$0" -nt "$0.bin" ] &&
            gcc -O2 -Wall -Wextra -Wpedantic -std=c11 "$0" -o "$0.bin" -s
            exec "$0.bin" "$@"
            #endif
    
            #include <stdio.h>
    
            void runtime·sysAlloc(void);
    
            int main(void)
            {
                unsigned age = 44;
                printf("I am %u years old.\n", age);
                runtime·sysAlloc();
                return 0;
            }
    
            void runtime·sysAlloc(void) {
                printf("Hello from C code, %d!\n", 90);
            }
    • hawski 4 months ago

      That's exactly how I keep my quick test C or C++ programs. I like how one can keep all the compilation options in the same file this way.

  • seeknotfind 4 months ago

    I love the opening #if 0. So many tricks from multilingual quines, maybe they can actually be useful :)

  • ktm5j 4 months ago

    I absolutely love this, super clever!

seanw265 4 months ago

Based on the title, I was expecting this to be about quines [1].

If you aren't familiar, quines are programs which produce their own source as their only output. They're quite interesting and worth a dive if you haven't explored them before.

My personal favorites are the radiation-hardened variety, which still produce the original pre-modified source even when any single character is removed before the program is run.

[1]: https://en.wikipedia.org/wiki/Quine_(computing)

  • f1shy 4 months ago

    You can chain 128 quines: https://github.com/mame/quine-relay

    • cmdlineluser 4 months ago

      Their "keep ruby weird" quine is my favourite: https://www.youtube.com/watch?v=IgF75PjxHHA

        %q!.!;eval$s=%q{eval(%w{$s=("%q!.!;eval$s=%q{#$s}"+'.gsub(/#{27.chr<<92<<91}[0-9]+m/,"")').lines;C=->x,y{Complex(x,y)};P=->r,a{Complex.polar(r,a*Math::PI)};S=->((a,b),(c
            ,d)){[a-c,b-d]};D=->((a,b),(c,d)){(a*c.conj).real+b*d};R=->((a,b),(c,d)){e,f=a.rect;g,h=c.rect;[C[f*d-b*h,b*g-e*d],e*h-f*g]};a=[];b=[];6.times{|i|a<<[P[1,i/3.0],1];b<<[P
            [2,(i+0.5)/3.0],0]};F=[a];6.times{|j|F<<[a[i=j-1],b[i],a[j]]<<[a[j],b[i],b[j]]<<[b[i],[0,-2],b[j]]};J=->k,v{k=[k/500.0,0].min+2.5;(v/(k+0.5)+C[k,k/2])*48};r=0;T=->p{x,y=
            p.rect;z    =O[y/2];x>=0&&y>=0&&z&&z[x]&&z[x]|=(y%2>1?2:1)|r};L=->p,q{s=(p+q)/2;(p-q).abs<1?(T[s];q):L[L[p,s],q]};N=->((a,b)){s=(a.abs2+b*b)**0.5;[a/s,b/s]};E=->p,r,a{10
            0.times  {|  i|m=-P[r,1.5+i*a*0.02];c,d=(p+m).rect;(c.abs-d>2||d<-1)&&(T[J[0,p+m]];T[J[0,C[-c,d           ]]])}};A=27.chr;$><<"%q#{33.           chr+A}[H#{A}[2J";g="NZDD
            CLYJXMX  ;Y  K(OQ'PP  YZA5YTZ7M(VOBBSYVXQQ[SUZV(U:G[NVZ[ZS&V[(YUU(ZTT[[X'X&Y%Y'ZZWW['Z&$$[%(''  '(&[$(%($(  CRGZHZI)DIOZ;IVPZ(SP)[X*  DRZCGJJT<<  +XI,%%:S=[E==RE&LEXX-'.
            RMY:(>>U(HU  /U[/[R   KOO0Y$1F?LZ%&M@(2NGU341RU?+6S2(NVYVAFVR(8FFRYRN4W'NHI@>(EUM6H@ZISSMS-XL  .LLVL?RR8O[O K9B,$%Y[3Y0X";41.upto(91 ){|c|g=g[2..  -1].gsub(c.chr){g[0,2]
            }};G=g.spli  t(?();  Z=[C[7,10],C[13,76]];U=(0..48).map{[0]*169};O=[];srand(0);q=20;m=40;x=0; Threa   d.new  {open("/dev/dsp","w"){  |f|50   0.dow nto(0){|k|e=[];300.tim
            es{|i|e<<((  x=(x+  k**1.9/9e4+0.001)%8)>4?138:118)};f<<e.pack("C*")}}};I=(0..48).map{[0]*169  };9.t  imes{ |y|76.times{|x|"1ea8yyjb v4x7d  zlzqj  sxd8dz4uqjfpb66bq7tu6l
            wql6vdbds6f  6h60  xz2iglxie44ax1nygtie5t8xpgk2oq00uzj0ucoq2gqc70y9fplfzez0d682syamnhicpwflot4  o9s".to_i(  36)[x+y*76]>0&&I[24+y][4  +x]=1}};-5  00.upto(518){|k|k==1&&s
            leep(1);k=   =q&  &(n=rand(11);q+=m;m-=2      ;spawn("espeak","-s",(60+m*2).to_s,"-ven+#{n<7?"m    #{n+    1}":"f#{n-6}"}",["keep",k<    320|    |k>360?"ruby":"Austin","
            weird"]*32  .c  hr));a=[-k,0].max**1.9/  1e4;v  ,z=d=N[[P[1,a/3],Math.sin(Math::PI*a)]];u=N[z==0 ?      [0,-1]:z>0?[v,z-1/z]:[-v,1/z-z]]      ; n=R[d,u];O.replace(U.map{
            |l|l+       [  ]});F.m    ap{    |f|a,  b,c=f;D  [R[S[b,a],S[c,a]],d]>0&&f.size.times{|i|a,b=f[  i], f[i-1];L[J[k,C[D[n,a],D[u,a]]],J[k,C[D [n,  b],D[u,b]]]]}};k>0&&(b=M
            ath.  sin(    [k,210]  .m  i  n*  Math  :   :PI  /15)/36;a=P[1,b*2];y=(30-[k,30].min)/10.0-2.8; E[C[ -1.2,y]*a.conj,(2-k/10%2)*0.06,-1];E[C [-1. 2,y]*a.conj,0.45,-1];E[C
            [-1.  2,y    +1.1]*a,  2.  7  5,  b-0.   3];    E[C[-0.6,y+0.5]*a,1.85,b-0.25]);k>=90&&k<210&&2 .tim es{|i|G[(k-90)*2+i].scan(/./){|c|Z[i]+ =[C[ 1,0],C[0,2],C[-1,0],C[0,
            -2]][c     .  ord%4]}  ;i  =  k/  10%   7*8+92  ;U[Z[0].  imag/2][Z[0].real,2]=U[Z[1].imag/2][Z [1].  real,2]=[i,i];r=8;L[J[k,C[-1.2,-2.8]  *a.c onj],Z[0]];L[J[k,C[1.2,-
            2.8]*a],   Z[  1]];r=0}                ;      j=        0;k==400&&15.times{|y|65.times{|x|"7gtz whx13 bfmrr9tsr8y0d007qlmygnh47axi9g9v609t cxjuv la0k6y1r96drdisqmfpao411
            n6e661l3  zykt   bqk   p4i33eecq7i2u  tfm  2n0bhrviijbr51nwcuhm5ufx3t79a9whf01e3a8kzzepid45ro83 n9r07k xxeht1pycrqo72".to_i(36)[x+y*65]>0 &&U[21 +y][14+x]=88}};s=O.map{|
            l|i=0;j+  =1;l.m     ap{|n|(i+=1)>2&  &i<8  3&&k>260&&k<420&&((k-j/6)%80>60||k>320&&k<400&&I[j- 1][i]>0 )&&n=88;a=("%c^_@****"%32)[n%8]; n>7?"%c [%dm%s%c[0m"%[27,30+n/8,
            a,27]:a}  *""};$><<A+"[H"+(0..47).ma  p{|i  |k+i>517?$s[i].chomp.gsub(32.chr){27.chr+"[44m"+$&+ 27.chr+"  [0m"}:s[i]}*10.chr;sleep(0.0  2)};puts }*'');%q{%q.;eval$s=%qev
            al(%w$s=  ("%q.;eval$s=%q$s"+'.gsub(/  27.  chr<<92<<91[0-9]+m/,"")').lines;C=->x,yComplex(x,y)  ;P=->r,a  Complex.polar(r,a*Math::PI  );S=->((  a,b),(c,d))[a-c,b-d];D=-
            >((a,b),  (c,d)          )(a*c.conj).  re  al+b*d;R=->((a,b),(c   ,d))e,f=a.   rect;g,h=c.rect;[ C[f*d-b*h   ,b*g-e*d],e*h-f*g];a=[   ];b=[];6. times|i|a<<[P[1,i/3.0],1]
            ;b<<[P[2,(i+0.5   )/3.0],   0];F=   [a    ];6   .t       imes|j|   F<<[a[i=   j-1],b[i],a[j]]<<[  a[j],b                                [i],b[  j]]<<[b[i],[0,-2],b[j]];J
            =->k,vk=[k/500.   0,0].min+   2.5   ;(v/(k+0.   5)   +C[k,   k/2]   )*48;r   =0;T=->px,y=p.rect;z =O[   y /2];x>=0&&  y>=0  &&z&&z[x]& &   z[x ]|=(y%2>1?2:1)|r;L=->p,qs=
            (p+q)/2;(p-q).a   bs<1?(T[s]   ;q   ):L[L[p,s   ],   q];N=-   >((a   ,b))   s=(a.abs2+b*b)**0.5;[a    /s, b/s];E=-  >p,r,a10  0.times| i|m    =-P[r,1.5+i*a*0.02];c,d=(p+
            m).rect;(c.abs-   d>2||d<-1)   &&   (T[J[0,p+   m]   ];T[J[   0,C[-   c,   d]]]);A=27.chr;$><<"%q   33.ch r+A[HA  [2J";g="NZDD  CLYJXM X;YK(   OQ'PPYZA5YTZ7M(VOBBSYVXQQ[
            SUZV(U:G[NVZ[ZS   &V[(YUU(ZT   T[   [X'X&Y%Y'   ZZ   WW[    'Z&$$[%(      '''(&[$(%($(CRGZHZI)D  IOZ;IVP  Z(SP  )[X*DRZCGJJT<<+X  I,%%  :S=[E==  RE&LEXX-'.RMY:(>>U(HU/U[
            /[RKOO0Y$1F?LZ%   &M@(2NGU3   41R   U?+6S2(NV   YV          AFVR(8FFR    YRN4W'NHI@>(EUM6H@Z   ISSMS-XL. LLV  L?RR8O[OK9B,$%Y[3Y0X  ";4 1.upto(91   )|c|g=g[2..-1].gsub(c
            .chr)g[0,2];G=g            .split   (?();Z=[C   [7   ,10],C   [13,76]]   ;U=(0..48).map[0]   *169;O=[];s r  and(0);q=20;m=40;x=0;Thr  e ad.newopen(   "/dev/dsp","w")|f|5
            00.downto(0)|k|   e=[];3   00.time   s|i|e<<(   (x   =(x+k**   1.9/9e   4+0.001)%8)>4?13  8:118);f<<e.pa  ck("C*");I=(0..48).map[0]*16  9;9.times|y|76  .times|x|"1ea8yyj
            bv4x7dzlzqjsxd8   dz4uqjf   pb66bq   7tu6lwql   6v   dbds6f6   h60xz   2iglxie44ax1nygti                                                                e5t8xpgk2oq00uzj0
            ucoq2gqc70y9fpl   fzez0d68   2syam   nhicpwfl   ot   4o9s".t   o_i(   36)[x+y*76]>0&&I[24+  y][4+x]=1;-50 0.upto(518)|k|k==1&&sleep(1) ;k==q&&(n=ran  d(11);q+=m;m-=2;spa
            wn("espeak","-s   ",(60+m*2   ).to   _s,"-ve   n+n   <7?"mn   +1":   "fn-6"",["keep",k<320||  k>360?"ruby" :"Austin","weird"]*32.chr) );a=[-k,0].m  ax**1.9/1e4;v,z=d=N[[
            P[1,a/3],Math.   sin(Math::P   I*a)   ]];u=   N[z=   =0?     [0,-   1]:z>0?[v,z-1/z]:[-v,1/z-z  ]];n=R[d,u] ;O.replace(U.map|l|l+[]) ;F.map|f|a,  b,c=f;D[R[S[b,a],S[c,a]
            ],d]>0&&f.size   .times|i|a,   b=f[i]       ,f[i-1]       ;L[J[k,   C[D[n,a],D[u,a]]],J[k,C[D[n,  b],D[u,b]] ]];k>0&&(b=Math.sin([k ,210].min*  Math::PI/15)/36;a=P[1,b*2
            ];y=(30-[k,30]   .min)/10.0-2.8;E[C[-1.2,y]*a.conj,(2-k/10%2)*0.06,-1];E[C[-1.2,y]*a.conj,0.45,-1]  ;E[C[-1.2 ,y+1.1]*a,2.75,b-0.3 ];E[C[-0.  6,y+0.5]*a,1.85,b-0.25]);k>
            =90&&k<210&&2.times|i|G[(k-90)*2+i].scan(/.   /)|c|Z[i]+=[C[1,  0],C[0,2],C[-1,0],C[0,-2]][c.ord%4];  i=k/10%7 *8+92;U[Z[0].imag/ 2][Z[0].  real,2]=U[Z[1].imag/2][Z[1].r
            eal,2]=[i,i];r=8;L[J[k,C[-1.2,-2.8]*a.conj],Z[0]];L[J[k,C[1.2,-  2.8]*a],Z[1]];r=0;j=0;k==400&&15.time  s|y|65. times|x|"7gtzwhx 13bfmrr  9tsr8y0d007qlmygnh47axi9g9v609t
            cxjuvla0k6y1r  96drdisqmf  pao4        11n6  e661  l      3zyktb  qkp4i33eecq7i2utfm2n0bhrviijbr51nwcuhm  5ufx3t 79a9whf01e3a8k zzepid  45ro83n9r07kxxeht1pycrqo72".to_i(
            36)[x+y*65]>0  &&U[  21+y]  [1  4+x]=8  8;s=  O.ma  p|l|i=  0;j+=  1;l.map|n|(i+=1)>2&&i<83&&k>260&&k<420&  &((k- j/6)%80>60|| k>320  &&k<400&&I[j-1][i]>0)&&n=88;a=("%c^
            _@****"%32)[n  %8];  n>7?"  %c         [%dm%  s%c[  0m"%[27,        30+n/8,a,27]:a*"";$><<A+"[H"+(0..47).map  |i|k +i>517?$s[ i].c  homp.gsub(32.chr)27.chr+"[44m"+$&+27.
            chr+"[0m":s[i]  *10  .chr;  sl  eep(0.02  );  puts  *'');%  q%q.;e  val$   s=%qeval(%w$s=("%q.;eval$s=%q$s"+'.  gsu b(/27.ch r<<  92<<91[0-9]+m/,"")').lines;C=->x,yCompl
            ex(x,y);P=->r,aC   om      plex         .pola  r(r  ,a*Ma  th::PI);  S=-   >((a,b),(c,d))[a-c,b-d];D=->((a,b),(c  ,d ))(a*c .c  onj).real+b*d;R=->((a,b),(c,d))e,f=a.rect
            ;g,h=c.rect;[C[f*d-b*h,b*g-e*d],e*h-f*g];a=[]  ;b=  [];6.t  imes|i  |a<<[P[1,i/3.0],1];b<<[P[2,(i+0.5)/3.0],0];F=[  a ];6. t  imes|j|F<<[a[i=j-1],b[i],a[j]]<<[a[j],b[i],
            b[j]]<<[b[i],[0,-2],b[j]];J=->k,vk=[k/500.0,0].min+2.5;(v/(        k+0.5)+C[k,k/2])*48;r=0;T=->px,y=p.rect;z=O[y/2];   x>   =0&&y>=0&&z&&z[x]&&z[x]|=(y%2>1?2:1)|r;L=->p,
            qs=(p+q)/2;(p-q).abs<1?(T[s];q):L[L[p,s],q];N=->((a,b))s=(a.abs2+b*b)**0.5;[a/s,b/s];E=->p,r,a100.times|i|m=-P[r,1.5+i    *a*0.02];c,d=(p+m).rect;(c.abs-d>2||d<-1)&&(T[J
            [0,p+m]];T[J[0,C[-c,d]]]);A=27.chr;$><<"%q33.chr+A[HA[2J";g="NZDDCLYJXMX;YK(OQ'PPYZA5YTZ7M(VOBBSYVXQQ[SUZV(U:G[NVZ[ZS&V[(YUU(ZTT[[X'X&Y%Y'ZZWW['Z&$$[%('''(&[$(%($(CRGZHZ
            I)DIOZ;IVPZ(SP)[X*DRZCGJJT<<+XI,%%:S=[E==RE&LEXX-'.RMY:(>>U(HU/U[/[RKOO0Y$1F?LZ%&M@(2NGU341RU?+6S2(NVYVAFVR(8FFRYRN4W'NHI@>(EUM6H@Z}}.gsub(/#{27.chr<<92<<91}[0-9]+m/,"")
      • mjcohen 4 months ago

        Looks ok to me - can't see anything wrong.

  • rkagerer 4 months ago

    Wikipedia's specific radiation-hardened example looks like goobly-gook to me, could you explain it in principle? Also does it only compensate for removed characters, or also for "bit flips" i.e. a changed character?

    • lifthrasiir 4 months ago

      The basic principle of radiation-hardened programs is to have as many safety nets as possible. The particular example in Wikipedia does this by, at the topmost layer for example, having two same string literals and `eval`-ing the longer literal (which would then use that literal to complete the quine). Since this `eval` call can be disrupted instead, it first checks whether two literals are same and calls a dedicated code path:

          /#{eval eval if eval==instance_eval}}/.i rescue##/
      
      This code path is carefully designed so that it always remains a valid Ruby code in any case and will execute the following line only when `eval` was not called. This kind of principle drives the entire source code.

      > also for "bit flips" i.e. a changed character?

      No. You can trivially make it invalid by changing the first character (`e`) to, say, `%` or 0xE5. It will be generally impossible to make such program for many languages with fixed classes of code characters.

codethief 4 months ago

> the right conceptual basis for build systems: ”build is the first stage of execution”

I have long been thinking the same. And also: "Running tests is the first stage of deploying in production" etc.

In other words: There is often a whole dependency graph between various stages (install toolchain, install 3rd-party dependencies, do code generation, compile, link/bundle, test, run, …) and each of those stages should ideally be a pure function mapping inputs to outputs. In my experience, we often don't do a good job making this graph explicit, nor do we usually implement build steps as pure functions or think of build systems as another piece of software which needs to be subject to the same quality criteria that we apply to any other line of code we write. As a result, developer experience ends up suffering.

Large JavaScript projects are particularly bad in this regard. Dependencies, auto-generated code and build output live right alongside source code, essentially making them global state from the point of view of the build system. The package.json contains dozens of "run" commands which more often than not are an arcane mix of bash scripts invoking JS code. Even worse, those commands typically need to be run in juuust the right order because they all operate on the same global state. There is no notion of one command depending on another. No effort put into isolating build tasks from each other and making them pure. No caching of intermediate results. Pipelines take ages even though they wouldn't have to. Developers get confused because a coworker introduced a new npm command yesterday which now needs be to run before anything else. Ugghhh.

  • haberman 4 months ago

    "Build is the first stage of execution" resonates with me too.

    It follows that build systems should be written in terms of real, encapsulated APIs that separate interface from implementation.

    Make-like build systems are like programming in assembly language: there is no structure that isolates one build step from another. Make offers no way of creating a "local variable" -- an intermediate file that is hidden from any other build step. The filesystem is like memory: as long as you can figure out the filename (address) somehow, you can read and write whatever you want.

    Even if you can declare that one build step depends on another, there's usually nothing that prevents a step from reading (or even writing!) files from other build steps that it did not declare a dependency on.

    What you want is a build system that lets you define real APIs, with truly private state. For example, cc_library() in Bazel (cxx_library() in Buck2) lets you define a C++ library in terms of the srcs/hdrs/etc, but you don't know how it will be implemented. Other rules and targets can consume the outputs of cc_library(), but only the files that are publicly exposed in the returned providers.

    This model brings true encapsulation to build systems, which makes it much more feasible to change implementation details without breaking users.

    • necovek 4 months ago

      It's basically impossible to program with no side effects in any general purpose language, and providing a build system language likely requires side-effect rich APIs too (to write files, call external tools, etc).

      Which is to say that you can perfectly well implement a "clean" build setup with make, and provide an "interface" for parts of the system to tie into. Files and directories are as good an input/output interface as any other, and you can do some simple safeguards with permissions on directories and files.

      To create a local, intermediate file with make, you create a file in TMPDIR (eg with mktemp or tempfile) and remove it at the end, setting the permissions the way you want for any nested steps not to be able to access it.

  • kaba0 4 months ago

    I think one build tool that has the fundamental abstraction well thought out is mill (written in Scala). Every task is just an ordinary function written in plain Scala, outputting something. Other functions can use this output by simply calling that function. In many cases the output will be simply a target directory, that is cached when none of the dependencies have changed.

    • codethief 4 months ago

      Too bad that mill is JVM-specific. :\

LorenDB 4 months ago

In D, this is a fully-supported used case. The DMD compiler provides an executable called rdmd that can be used as the shebang executable at the top of any D file, and the shebang itself is also codified as valid D syntax.

lboc 4 months ago

Seems to be replicating mainframe JCL and in-stream data sets. Processing instructions and input are combined in a single file. Used all the time for compiling, running utilities etc.

I'm guessing that this (IBM) example is setting the delimeter to '@@' to avoid problems with the comment - JCL also understands the '/*' sequence. I've not seen it used with other languages (Cobol etc.)

    //jobname    JOB   acctno,name...
    //COMPILE    EXEC  PGM=CCNDRVR,
    // PARM='/SEARCH(''CEE.SCEEH.+'') NOOPT SO OBJ'
    //STEPLIB    DD    DSNAME=CEE.SCEERUN,DISP=SHR
    //           DD    DSNAME=CEE.SCEERUN2,DISP=SHR
    //           DD    DSNAME=CBC.SCCNCMP,DISP=SHR
    //SYSLIN     DD    DSNAME=MYID.MYPROG.OBJ(MEMBER),DISP=SHR
    //SYSPRINT   DD    SYSOUT=*
    //SYSIN      DD    DATA,DLM=@@
      #include <stdio.h>
      ⋮
      int main(void)
      {
      /*  comment   */
      ⋮
      }
    @@
    //SYSUT1     DD    DSN=...
    ⋮
    //*
https://en.wikipedia.org/wiki/Job_Control_Language#In-stream...*
necovek 4 months ago

The way I see it, this is something a true built-from-source system could do with their packaging system to enable no-effort code changes for any system utility and true trust in you running what you have source for (other than backdoored hardware).

Debian is pretty far off from this vision (if we also want performant execution), but I wonder how do the Gentoo, ArchLinux and Nix fare in this regard? Is this something that could be viably built with their current packaging formats?

  • antoinealb 4 months ago

    In arch at least it is reasonnably easy to download the source for a package, modify it locally, build it and install it. Not sure if that's what you are asking for?

    • Brian_K_White 4 months ago

      I think no matter how easy a seperate source package is, it's still a seperate thing, and not the same (not as good) as the source being a built in part of the package.

      freebsd/gentoo ports comes close where if you pretend that pkg doesn't exist, or at least imagine a world where it's only used for the absolute minimum necessary bootstrap, then ports is probably the closest.

      The source is still actually a seperate thing even then so I think even ports with no pkg usage is still not really there.

      Imagine the package itself being the source, the one and only form of the package is the source. If it builds an executable, that executable is actually just an automatically generated throw-away artifact that you don't care about and don't save or distribute. Maybe most normal users don't even know where the compiled bin really lives, buried in some /var tree or something, or maybe even in a kind of kernel level db. All the user ever overtly interacts with is actually the self-building source package. When you want to copy it or delete it etc, that's the only thing you touch and everything else is just automatically managed cache.

      Then it's not merely easy to get the source and modify it, it's simply THE package in the first place. If you can even run a thing, then you automatically and indellibly also have the full source to that thing. That would be pretty huge I think.

      It would probably result in slow installs and updates like gentoo or freebsd ports, but only if we only imagine switching to this today as the only variable changed, out of context, without also imagining the last 40 years of tooling and os development optimizing pain points to make whatever we do most go faster, if we had decided to package apps this way all along.

      • necovek 4 months ago

        Indeed, but I also don't see why this couldn't extend to the kernel too: it would self-compile upon boot.

        There is an obvious bootstrapping issue (there has to be a compiler for any language you want to compile, including the one your compiler is in), but it's certainly interesting food for thought.

      • Brian_K_White 4 months ago

        BTW I don't mean to imply that it's a good idea necessarily, just that it is an interesting idea.

o11c 4 months ago

Sometimes it's useful to do something like:

  /*usr/bin/env echo 'Hello World!' #*/
Even if, for some reason, there are multiple `usr` folders, the use of `env` means it will eventually call the executable.

As for getting rid of the shebang - swapping the ! with a / means that the line and character counts don't change so you get meaningful error messages.

  • rramadass 4 months ago

    Ah; the /* at the beginning is expanded by the shell to a proper path while the trailing #*/ becomes a shell comment.

hippich 4 months ago

Somewhat adjacent- I recently discovered https://github.com/rofl0r/rcb2 - it can take it quite far without using make file. And similarly to OP - it allows to keep relevant build info right in the source code. (Rcb2 is great at prototype stage, but obviously at some point makefiles are worth spending time on)

slippy 4 months ago

Didn't this exist conceptually anyway as the C shell (csh) where the scripting language was "closer to" C?

https://en.wikipedia.org/wiki/C_shell

It seems like you are on your way to making the C++ shell.

  • 082349872349872 4 months ago

    Although the substantial functionality overlap between shells and programming languages has caused many to attempt to combine them, someone a while back (Yossi Kreinin?) had a reason that's probably not what we want: a shell should be optimised for quickly doing specific things, and programming languages should be optimised for concisely doing general things. In particular, this suggests that bare words in shells should default to being literals, and variable substitution will take extra characters, while bare words in programming languages should default to being variables, and literal values will take extra characters.

trollied 4 months ago

You might all enjoy The International Obfuscated C Code Contest https://www.ioccc.org/

https://www.ioccc.org/years.html

  • lifthrasiir 4 months ago

    IOCCC is almost irrelevant here, but one particular winning entry is relevant: 2000/tomx [1]. Nowadays IOCCC is hosted in GitHub Pages and it is hard to look at the verbatim source, so the following is the entire source code:

        #include <stdio.h>
        #define  true
        
        true /*:all
        
        CC=cc
        PROG=tomx
        
        false :
            make -f $0 $1
            exit 0
        
        all: $(PROG)
        
        %:%.c
            $(CC) $< -o $@
        
        clean:
            rm $(PROG)
        
        .PHONY: /* true clean */
            int main() {return!printf("Hello, world\n");}
    
    (All indentations here are actually tabs.)

    [1] https://www.ioccc.org/2000/tomx.c

    • rramadass 4 months ago

      This compiles as both "./tomx.c" and "make -f tomx.c" to produce the executable "tomx"; but am not fully clear how?

      • lifthrasiir 4 months ago

        As a shell script, anything starting with `#`, `true<WS>` and `false<WS>` are meant to be ignored and variable declarations are compatible with Make. The next lines to be executed are therefore `make -f $0 $1` and `exit 0`, as you would expect. Shell scripts are parsed linewise so subsequent lines are ignored.

        As a Makefile, again `#` lines are ignored and variable declarations are compatible but lines starting with `true<WS>` and `false<WS>` have to be a valid construct, so they are made into rules. Make doesn't rely much on the rule's recipe (you can do way more weird things by setting `SHELL` to non-shells by the way!), so indented lines are essentially ignored unless the rule is triggered. Next lines are usual stuffs for Makefile. As `true` (and anything follows, see the next paragraph) would be the first and thus default rule target, it has to trigger `all` manually and ensure that `all` is always executed in this way by setting the final `.PHONY` rule.

        As a C source code, `#` lines are now preprocessor directives and used to hide the first `true` token from the compiler. As an identifier followed by a colon wouldn't be a valid C code at the top level, `true` should be followed by a comment which extends to the second-to-last line. (There is no technical reason for `*/` to be a requisite here I believe.) The final line is an usual indented C code, which is a part of `.PHONY` which recipe is ignored. Since Make will interpret `/*` as path patterns, they have to be also handled by `.PHONY`.

        • rramadass 4 months ago

          Nice. I worked out most of it; the C source part was easy and the Makefile was mostly comprehensible(except for the "true"/"false" rules) but the shell script part was what stumped me; now i see the sneaky whitespace in "false :".

          A true IOCCC winner.

          One tip for folks trying to figure out this (and other multi-modal) code is to use your editor's syntax highlight feature based on filetype. So for example in vim renaming this file to ".sh" and ".mk" gives you shell script and makefile structures.

  • hoseja 4 months ago

    Is the linked example a quine or something else? It's a quine, right?

    • mananaysiempre 4 months ago

      TFA? No, it’s a C / Bourne shell polyglot where the shell part compiles and runs the C part. I’ve also used this technique when I needed to post self-contained examples (e.g. to mailing lists), but I don’t know if people actually appreciated it.

dorianmariefr 4 months ago

    "$0".bin: -c: line 0: unexpected EOF while looking for matching `''
    "$0".bin: -c: line 1: syntax error: unexpected end of file
z4ziggy 4 months ago

//bin/env gcc $0 -g -o ${0%.} && ./${0%.} ; exit

thats the one i've been using. feel free to adopt and change.