下面是我创建的合金模型,用于展开iCalendar文档中的行。我想知道是否有更好的 - 更简单的模型?
以下是iCalendar和展开的简要说明,其次是我的Alloy模型。
iCalendar 是一种计算机文件格式,允许Internet用户通过以这种格式共享或发送文件,将会议请求和任务发送给其他Internet用户。 Wikipedia
iCalendar文件由一系列行组成。每行不能超过75个字符(实际上是75个八位字节,但这与此讨论无关)。如果一条线超过该长度,则该线必须“折叠”到下一条线上。一行开头的空格字符表示它是前一行的延续。
从折叠表示移动到单行表示的过程称为"展开"。展开是通过删除紧跟在后面的CRLF和空格字符来完成的...解析内容行时,必须首先展开折叠的行。 RFC5545
以下是iCalendar文件示例:
BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
BEGIN:VEVENT
DESCRIPTION;ALTREP="cid:part1.0001@example.org":The Fall'98 Wild
Wizards Conference - - Las Vegas\, NV\, USA
END:VEVENT
END:VCALENDAR
DESCRIPTION
属性的值会折叠到下一行,如Wizards Conference ...
以下是iCalendar文档中展开行的Alloy模型。
这是我在这个问题中发现的关键抽象:文档,行,空格,数据和展开。具体来说,有一个包含行的文档。每行都有数据。如果一行以空格开头,则它是一个延续行,必须与前一行合并。必须重复应用展开操作到文档。生成的文档必须没有折叠线。
一组有序的文档表示展开过程中文档的状态:
open util/ordering[Document]
该文档包含一组行。每行都有下一行(当然除了最后一行)。每行都有数据。展开后,一行将有其数据加上下一行的数据。因此,一条线具有一组数据。显然,其中一条线是第一条。
sig Document {
lines: Line -> lone Line,
content: Line -> set Data,
firstLine: Line
} {
firstLine in lines.Line
// No Line maps to firstLine, i.e., firstLine is the first line
no line: Line | lines[line] = firstLine
// All lines are reachable from firstLine
(lines.Line + lines[Line] - firstLine) in firstLine.^lines
// The lines are sequential, i.e., lines are acyclic
no ^lines & iden
// No space at start of first line
no firstLine.spaceAtStart
}
一条线的起点可能有一个空格。
sig Line {
spaceAtStart: lone Space
}
空格由单例集表示。数据是一组值。
one sig Space {}
sig Data {}
每次调用展开操作时,它都会在文档中展开一行。
展开操作的示例:假设文档包含以下行序列:
Line0, Line1, Line2
假设Line1在开头有一个空格。这意味着Line0在Line1上继续;展开将两条线合并为一条。展开后,该文件有:
Line0', Line2
其中Line0'包含Line0加Line1的数据。
pred unfold (doc, doc': Document, line: doc.lines) {
// precondition: “line” has a next line (i.e., it's not the last line)
some line[Line]
// precondition: the next line starts with a space
some line[Line].spaceAtStart
let line1 = line.Line, line2 = line[Line], line3 = doc.lines[line2] {
// Merge line1 and line2. Set line3 to follow line1.
// Add the content of line2 to line1. Delete line2 -> line3.
// Unless ... no line follows line2. Then the unfold
// results in no line following line1. Remove line1 -> line2.
some doc.lines[line2] => doc'.lines = doc.lines ++ (line1 -> line3) - (line2 -> line3)
else doc'.lines = doc.lines - (line1 -> line2)
doc'.content = doc.content ++ (line1 -> (doc.content[line1] + doc.content[line2]))
doc'.firstLine = doc.firstLine
}
}
用一些行初始化文档。为每一行提供唯一数据。制作一些需要展开的线条。
pred init (doc: Document) {
some doc.lines
let allLines = doc.lines[Line] + doc.lines.Line {
all line: allLines | let data = doc.content[line] | one data and
all otherLine: allLines - line | doc.content[otherLine] != data
some allLines.spaceAtStart
}
}
在执行跟踪的每个步骤中,如果至少有一行需要展开,则执行展开操作。否则,不对文档做任何事情(即跳过)。
fact traces {
init [first]
all doc: Document - last | let doc' = doc.next {
// If spaces exist in the document, do an unfold operation.
// Otherwise, do nothing (i.e., skip).
let allLines = doc.lines[Line] + doc.lines.Line {
some allLines.spaceAtStart => not skip [doc, doc']
some line: doc.lines | unfold [doc, doc', line] or skip [doc, doc']
}
}
}
"跳过"表示不对文档进行任何更改,即文档的下一个状态与上一个状态相同。
pred skip (doc, doc': Document) {
doc'.lines = doc.lines
doc'.content = doc.content
doc'.firstLine = doc.firstLine
}
断言:文档的最终状态没有折叠线。
assert no_lines_are_folded {
let allLines = last.lines[Line] + last.lines.Line |
no allLines.spaceAtStart
}
check no_lines_are_folded for 6
合金分析仪没有找到反例。是啊!