5

我知道在 Rebol 2 中,对于大于 ^(FF) 的代码点,您不能在字符串中使用插入符号样式转义,因为它对 Unicode 一无所知。所以这不会产生任何好处,它看起来很乱:

print {Q: What does a Zen master's {Cow} Say?  A: "^(03BC)"!}

然而代码在 Rebol 3 中工作并打印出来:

Q: What does a Zen master's {Cow} Say?  A: "μ"!

这很好,但是 R3 显然在 U+FFFF 处最大限度地发挥了它在字符串中保存字符的能力:

>> type? "^(FFFF)"
== string!

>> type? "^(010000)"
** Syntax error: invalid "string" -- {"^^(010000)"}
** Near: (line 1) type? "^(010000)"

这种情况比 Rebol 2 在遇到它不知道的代码点时的随机行为要好得多。但是,如果您知道如何进行自己的 UTF-8 编码(或者通过从磁盘加载源代码来获取字符串),那么在 Rebol 中曾经有一种用于存储字符串的解决方法。您可以将它们从单个字符组装起来。

所以 U+010000 的 UTF-8 编码是#F0908080,你之前可以说:

workaround: rejoin [#"^(F0)" #"^(90)" #"^(80)" #"^(80)"]

您会得到一个使用 UTF-8 编码的单个代码点的字符串,您可以将其以代码块的形式保存到磁盘并再次读回。R3中是否有类似的技巧?

4

2 回答 2

4

有一个使用字符串的解决方法!数据类型也是如此。在这种情况下,您不能使用 UTF-8,但可以使用 UTF-16 解决方法,如下所示:

utf-16: "^(d800)^(dc00)"

,它使用 UTF-16 代理对对 ^(10000) 代码点进行编码。一般来说,以下函数可以进行编码:

utf-16: func [
    code [integer!]
    /local low high
] [
    case [
        code < 0 [do make error! "invalid code"]
        code < 65536 [append copy "" to char! code]
        code < 1114112 [
            code: code - 65536
            low: code and 1023
            high: code - low / 1024
            append append copy "" to char! high + 55296 to char! low + 56320
        ]
        'else [do make error! "invalid code"]
    ]
]
于 2013-04-09T01:08:41.337 回答
3

是的,有一个技巧……这也是您应该在 R2 中使用的技巧。 不要使用字符串!使用二进制!如果你必须做这种事情:

好的解决方法:#{F0908080}

它可以在 Rebol2 中工作,在 Rebol3 中也可以工作。您可以保存并加载它而无需任何有趣的事情。

事实上,如果你完全关心 Unicode,永远......如果你被困在 Rebol 2 而不是 3 中,请停止使用高于 ^(7F) 的代码点进行字符串处理。我们将通过查看那个可怕的解决方法来了解原因:

糟糕的解决方法:重新加入 [#"^(F0)" #"^(90)" #"^(80)" #"^(80)"]

... “你会得到一个带有单个 UTF-8 代码点的字符串” ...

您唯一应该得到的是一个带有四个单独字符代码点的字符串,并且带有4 = length? terrible-workaround. Rebol2 坏了,因为字符串!基本上和二进制没什么区别!在引擎盖下。事实上,在 Rebol2 中,您可以在不复制的情况下来回为这两种类型命名,查找 AS-BINARY 和 AS-STRING。(这在 Rebol3 中是不可能的,因为它们本质上是不同的,所以不要附在这个特性上!)

看到这些字符串报告长度为 4 有点欺骗性,如果您转换它们,每个字符都会产生相同的值,这是一种错误的安慰to integer!。因为如果您曾经将它们写到某个文件或端口中,并且需要对其进行编码,那么您就会被咬。在 Rebol2 中注意这一点:

>> to integer! #"^(80)"
== 128

>> to binary! #"^(80)"
== #{80}

但在 R3 中,当需要二进制转换时,您有一个 UTF-8 编码:

>> to integer! #"^(80)"
== 128

>> to binary! #"^(80)"
== #{C280}

因此,当您看似正常工作的代码稍后执行不同的操作并最终以不同的方式序列化时,您会感到惊讶。实际上,如果您想知道 R2 在这方面有多“混乱”,请查看为什么您的“mu”有一个奇怪的符号。在 R2 中:

>> to binary! #"^(03BC)"
== #{BC}

它只是把“03”扔掉了。:-/

因此,如果您出于某种原因需要使用 Unicode 字符串并且无法切换到 R3,请在牛示例中尝试这样的操作:

mu-utf8: #{03BC}
utf8: rejoin [#{} {Q: What does a Zen master's {Cow} Say?  A: "} mu-utf8 {"!}]

这会给你一个二进制文件。仅将其转换为字符串以进行调试输出,并准备好看到乱码。但是,如果您被困在 Rebol2 中,这是正确的做法。

并重申答案:如果由于某种奇怪的原因需要在 Rebol3 中使用那些更高的代码点,这也是该怎么做:

utf8: rejoin [#{} {Q: What did the Mycenaean's {Cow} Say?  A: "} #{010000} {"!}]

如果我知道LINEAR B SYLLABLE B008 A是什么,我相信这将是一个非常有趣的笑话。这让我很可能会说,如果您正在做如此深奥的事情,您可能只有几个代码点被引用作为示例。您可以将大部分数据保存为字符串,直到您需要方便地将它们插入,并将结果保存在二进制系列中。


更新:如果遇到这个问题,这里有一个实用程序函数,可用于临时解决它:

safe-r2-char: charset [#"^(00)" - #"^(7F)"]
unsafe-r2-char: charset [#"^(80)" - #"^(FF)"]
hex-digit: charset [#"0" - #"9" #"A" - #"F" #"a" - #"f"]

r2-string-to-binary: func [
    str [string!] /string /unescape /unsafe
    /local result s e escape-rule unsafe-rule safe-rule rule
] [
    result: copy either string [{}] [#{}]
    escape-rule: [
        "^^(" s: 2 hex-digit e: ")" (
            append result debase/base copy/part s e 16
        )
    ]
    unsafe-rule: [
        s: unsafe-r2-char (
            append result to integer! first s
        )
    ]
    safe-rule: [
        s: safe-r2-char (append result first s)
    ]
    rule: compose/deep [
        any [
            (either unescape [[escape-rule |]] [])
            safe-rule
            (either unsafe [[| unsafe-rule]] [])
        ]
    ]
    unless parse/all str rule [
        print "Unsafe codepoints found in string! by r2-string-to-binary"
        print "See http://stackoverflow.com/questions/15077974/"
        print mold str
        throw "Bad codepoint found by r2-string-to-binary"
    ]
    result
]

如果您使用它而不是to binary!转换,您将在 Rebol2 和 Rebol3 中获得一致的行为。(它有效地实现了terrible-workaround样式字符串的解决方案。)

于 2013-02-25T22:44:30.477 回答