Answers:
Postscript程序具有独特的(?)功能,可以将自己的程序文本读取为数据。这通常用于通过image
其接收操作者的数据采集-程序作为输入,并且这个过程通常会使用currentfile
随后readline
,readstring
,或readhexstring
。但是换一种说法,image
它只是另一个循环运算符,因此任何循环都可以预读。一个示例是《绿皮书》中的行式打印机模拟器。
使用token
运算符可在文件或字符串上调用扫描程序,并以数字或空格(或其他方式:请参见其他答案)分隔名称。
PS中的一个简单的PS解释器:
{currentfile token not {exit} if dup type /arraytype ne {exec} if }loop
由于我似乎无法获得原始的二进制令牌来工作(请参阅其他答案),因此我利用“嵌入式解码”的思想来利用二进制令牌机制将代码打包为8位字符串,然后即时处理和分析字符串中的命令。
/.{
<920> % two-byte binary-encoded name template with 0x92 prefix
dup 1 4 3 roll put % insert number into string
cvx exec % and execute it
}def
/${
//. %the /. procedure body defined above
73 . %"forall" (by code number)
}def
该.
过程从堆栈中获取一个数字,并将其作为两个字节的字符串中的第二个字节插入,第一个字节是二进制令牌的前缀字节,并指定了可执行系统名称。我们使用扫描程序的规则在十六进制字符串中保存一个字节,即在十六进制字符串中奇数个半字节用额外的0个半字节填充,因此3个十六进制半字节会生成一个2字节的字符串。然后将该字符串标记为可执行,并调用该字符串以exec
调用扫描程序,生成所需的可执行系统名称,然后加载该名称并执行运算符。在$
栈上的一个字符串的每个字节执行此,使用.
过程两次,一次是作为循环体,然后执行该循环操作者forall
通过数字。
更紧凑地说,这些过程如下所示:
/.{<920>dup 1 4 3 roll put cvx exec}def/${//. 73 .}def
%123457890123456789012345678901234567890123456789012345
% 1 2 3 4 5
因此,55个字符购买二进制令牌字符串。或者,对于6个字符(如果使用空格终止,则为7个字符),则可以加载G库,(G)run
该库用于定义.
和$
上面的代码(以及其他一些代码,以扩展可到达ascii的代码的范围)。
可能是旧消息,但我才刚学到。:)
您可以使用Postscript解释器与编码过滤器和剪切和粘贴交互地进行操作。但我将展示如何使用dc
“手动”它。
因此,这是一个十六进制字符串。我们将其分成4个字节的块。
95 20 6e d8 d0 59 49 35 50 74 ba c5 08 2d
接通dc,我们将它们输入为32位(无符号)big-endian-byte-order数字。然后使用mod -off以85为基数的数字(直到为0之前,应该为5)。
0>直流 16i 95206ED8 爱 d85%n85 / 82 d85%n85 / 83 d85%n85 / 82 d85%n85 / 78 d85%n85 / 47 d85%n85 / 0
用填充最后一个块将00 00
产生(十进制),而忽略了我们填充的相同字节数。
47 78 82 83 82 66 81 72 79 83 25 72 82 25 69 2 53 30 [2 53]
加33进入ASCII和poof的可打印范围!ASCII 85。
80 111 115 116 115 99 114 105 112 116 58 105 115 58 102 35 86 63
解码为:Postscript:is:f#V?%%%糟糕!应该说“有趣”!我搞砸了。:)
将其包装在<~
...中~>
,并且Level-2 Postscript可以访问8位数据,该价格比hex便宜。
这是一个快速工具:包裹多个定义[...>>begin
以消除关键字def
(nb。[
与相同<<
)。
def def
[>>begin
所以请记住:不止三两个... 聚集在一起!;)
/a 1 def/b 2 def/c 3 def
与比较<</a 1/b 2/c 3>>begin
。我们需要更多空间来进行防御。
[/a 1/b 2/c 3>>begin
/a{pop 2 mul}def
或者\b[2 3]def
,将def
成本只有3个字符,而不是4
虽然大多数后记运营商在语法标识符(因此必须与空间(或其他方式)分隔),名称[
,]
,<<
,和>>
是自定界和扫描仪会检测到它们中间没有空间。出于同样的原因,你不能引用这些名称与通常的/literal
语法(如:/[
是两个符号:一个空的名称等同于文本()cvn cvlit
和可执行文件的名称[
等同于([)cvn cvx exec
)。
为了重新定义这些名称不能提及的名称,我们可以使用在字典中用作键时隐式转换为名称的字符串(方便!)。
此示例说明了滥用这些运算符来执行算术。
%!
([)0 def
(])1 def
(<<){add}def
(>>){mul}def
]]<<]]]<<<<>> =
%1 1 add 1 1 1 add add mul = %prints 6
而且<<
和[
(和mark
)都是同一件事。
/
结束前一个令牌,因此您不需要在其之前有空格。
如果您已经在使用<<>>begin
字典,则/?{}
每次重新定义都会有4个字符的固定开销。因此,长度为n的操作符重复N次将产生
(4 + n)-(N *(n -1))个字符计数变化。
将此公式设置为0可以得出收支平衡点的方程。由此我们可以针对每个变量求解另一个变量,得出
n =-(N -4)/(1- N)和
N =(4 + n)/(n -1)。
不,我们无法回答诸如“缩写多少次值得使用?”的问题。n = 5,所以N = 9/4。登顶吧,因为您无法有效地调用print的1/4倍。因此,3。3用途。的确,
print print print
/P{print}p p p
(当然,假设您已经支付了<<>>begin
激活定义的开销)。
当然,二进制令牌可以解决这种问题,从系统名称表中为您提供前255个2字节的名称:0x92、0x ??。二进制令牌也是自定界的,因为第一个字节的高位不在ASCII范围内,所以之前或之后不需要空格。
对于PostScript程序的最终压缩,最终的边界是二进制令牌,它使您可以完全删除长运算符名称,而不再需要使用ASCII干净程序。
因此,从压缩后的代码块开始
[/T[70{R 0 rlineto}48{}49{}43{A rotate}45{A neg rotate}91{currentdict
end[/.[currentpoint matrix currentmatrix]cvx>>begin begin}93{. setmatrix
moveto currentdict end end begin}>>/S{dup B eq{T begin exch{load exec}forall
end}{exch{load exch 1 add S}forall}ifelse 1 sub }>>begin moveto 0 S stroke
我们在PLRM的背面查找所有名称(附录F,第795-797页)
appearance
in
vim dec meaning
<92> 146 'executable system name' binary token prefix
^A 1 add
^M 13 begin
^^ 30 currentdict
' 39 currentmatrix
( 40 currentpoint
2 50 cvx
8 56 dup
9 57 end
= 61 eq !)
> 62 exch
? 63 exec
I 73 forall
U 85 ifelse
d 100 load
h 104 matrix
k 107 moveto
n 110 neg
<85> 133 rlineto
<88> 136 rotate
§ 167 stroke
© 169 sub
然后以146
(十进制)字节为前缀键入它们。vim帮助输入任意字节
然后在vim中,可以直接键入压缩文件,因此:
[/ T [70 {R 0 ^V146 ^V133} 48 {} 49 {} 43 {A ^V146 ^V136} 45 {A ^V146 ^V110 ^V146 ^V136} 91 { ^V146 ^V30 ^V146 ^V57 [ ^V/。[146 ^V40 ^V146 ^V104 ^V146 ^V39] ^V146 ^V50 >> ^V146 ^V13 ^V146 ^V13} 93 {。 ^V146 ^V156 ^V146 ^V107 ^V146 ^V30 ^V146 ^V57 ^V146 ^V57 ^V146 ^V13} >> / S { ^V146 ^V56 B ^V146 ^V61 {T ^V146 ^V13 ^V146 ^V62 { ^V146^V 100 ^V146 ^V63}^V146^V73 57} { 62^V 146^V^V146 ^V62 { ^V146 ^V100 ^V146^V
...您必须在此处输入空格以终止 ^V
-62并开始1,但您可以备份并稍后将其删除...
1 ^V146 ^V1S} ^V146 ^V73} ^V146^V 85
...必须在此处输入空格以终止 ^V
-85并开始1,但您可以备份并稍后将其删除...
1 ^V146 ^V169} >> ^V146 ^V13 ^V146^V 107
... 3位数代码的第3位数终止字节输入,因此以下 0
内容很正常,很方便...
0秒^V146^V 167
在屏幕上(在vim中)如下所示:
[/T[70{R 0<92><85>}48{}49{}43{A<92><88>}45{A<92>n<92><88>}
91{<92>^^<92>9[/.[<92>(<92>h<92>']<92>2>>
<92>^M<92>^M}93{.<92><9c><92>k<92>^^<92>9<92>9<92>^M}
>>/S{<92>8B<92>={T<92>^M<92>>{<92>d<92>?}<92>I<92>9}{<92>>
{<92>d<92>>1<92>^AS}<92>I}<92>U1<92>©}>><92>^M
<92>k0 S<92>§
如果只是为了显示图片,通常可以完全忽略这一点。Ghostscript不需要将大多数内容绘制到屏幕上showpage
。
¡ 161 showpage
[ 这实际上不起作用。Ghostscript在给我undefined
和syntaxerror
这些令牌。也许我需要启用某些模式。]
3 -1 roll
还是3 2 roll
?在我的心智模型中,前者应该更有效,因为它只需要一招。我的心理模型正确吗?
https://github.com/luser-dr00g/G
这是一个文本文件。无扩展名,以最短的语法加载它。
它允许这个203个字符的Sierpinksi Triangle程序
[48(0-1+0+1-0)49(11)43(+)45(-)/s{dup
0 eq{exch{[48{1 0 rlineto}49 1 index
43{240 rotate}45{120 rotate}>>exch
get exec}forall}{exch{load
exch 1 sub s}forall}ifelse 1 add}>>begin
9 9 moveto(0-1-1)9 s fill
改写为151字节为
3(G)run $
{A - B + A + B - A}
{B B}
{A - B - B}7{[ex{du w{(>K?\2u)$}if}fora]}rep
cvx[/A{3 0 rl}/B 1 in/-{120 rot}/+{-120 rot}>>b
100 200(k?B9)$ showp
使用缩写的系统名称功能, 1(G)run
完全消除了冗长的操作员名称的负担。操作员名称仅需足够长即可将其与其他名称区分开。
所以
add
变成 ad
mul
变成 mu
index
变成 i
使用PLRM附录F用于操作员名称的标准表。
即使未选择缩写名称,操作符字符串的功能仍然可用。裸库通过简单地添加来选择“基础级别”(G)run
而无需其他修饰。
基本级别包括一个新函数.
,该函数接受运算符的整数代码(与上述相同的附录F)并执行它。
新函数$
遍历一个字符串并调用.
每个字符串。因此,ascii代码直接通过数字选择运算符。
@
通过将空格字符(Ascii 0x20)视为0,可以使用新功能深入附录F中表格的底部。
通过使用新功能#
,您可以通过首先添加95(0x5F)进一步深入表中,以便将char 0x20空间视为127(0x7F),这是最后一个可打印的ASCII字符~
126(0x7E)之后的下一个代码。
两个新功能!
使您可以使用索引/键的索引数组访问数组和/或dict的深度嵌套结构,而不是许多get
(和put
)运算符的乏味表达式。
(G)run
7个字符购买基本级别。
1(G)run
8个字符购买该AND的缩写系统名称。
3(G)run $
9个字符立即开始隐式过程块扫描源代码行,直到下一行空行,并将第一行定义为一个过程称为A
,将下一行定义为一个过程称为B
,以此类推。这应删除def
用于定义的大多数s很多东西,而无需将它们包装在字典中,甚至无需明确为其命名。