aboutsummaryrefslogtreecommitdiffstats
path: root/examples/linebreak.aup
blob: f9735c5416dfb21cbc8240fe9cc55ea7bc8111fe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# Line breaking (UAX #14, Unicode 16.0): each boundary of a line of code points decided by the first rule, in the
# standard's order, that matches it (as Vega's text/linebreak.c). The properties come in as data, ranges of code points
# loaded once: each its class after LB1, whether an initial or a final quotation mark (Pi, Pf), East Asian (width F, W
# or H), and pictographic and unassigned (Extended_Pictographic and Cn). A line's boundaries, the ends included, are 0
# (none), 1 (allowed) or 2 (mandatory).
record Range {
  start = 0
  class = 0
  pi = false
  pf = false
  east = false
  pictographic = false
}
record Point {
  code = 0
}
record Text {                                    # each position's: its code, class, range, and what the rules look back at
  codes: list of number
  cls: list of number
  eff: list of number                            # its unit's class: a mark takes its base's (LB9), a lone mark is AL (LB10)
  joined: list of bool                           # a mark LB9 joins to the character before
  start: list of number                          # where its unit starts
  nonspace: list of number                       # where the last unit up to it that is not a space starts, or -1
  numeric: list of bool                          # its unit ends NU (NU | SY | IS)*
  ri: list of number                             # how many regional indicators end at its unit
  at: list of number                             # its range
}
state ranges: list of Range                      # by start, from 0
state breaks: bytes                              # the last line's boundaries
view boundaries = breaks
input load = message { rows: list of Range }
input line = message { points: list of Point }

fn BK = 0; fn CR = 1; fn LF = 2; fn NL = 3; fn SP = 4; fn ZW = 5; fn ZWJ = 6; fn CM = 7; fn WJ = 8; fn GL = 9
fn BA = 10; fn BB = 11; fn B2 = 12; fn HY = 13; fn CB = 14; fn CL = 15; fn CP = 16; fn EX = 17; fn IN = 18; fn NS = 19
fn OP = 20; fn QU = 21; fn IS = 22; fn NU = 23; fn PO = 24; fn PR = 25; fn SY = 26; fn AK = 27; fn AL = 28; fn AP = 29
fn AS = 30; fn EB = 31; fn EM = 32; fn H2 = 33; fn H3 = 34; fn HL = 35; fn ID = 36; fn JL = 37; fn JV = 38; fn JT = 39
fn RI = 40; fn VF = 41; fn VI = 42
fn NONE = 0; fn ALLOWED = 1; fn MANDATORY = 2
fn end(c) = c == BK || c == CR || c == LF || c == NL

on load {
  for r in load.rows { ranges.append(Range { ..r }) }
}

# The range holding code point c (ranges by start: the last whose start is c or before).
fn range(c) {
  let low, high = 0, ranges.count
  while high - low > 1 {
    let middle = (low + high) // 2
    if ranges[middle].start <= c { low = middle } else { high = middle }
  }
  return low
}

fn next_unit(t, i) {
  let j = i + 1
  while j < t.codes.count && t.joined[j] { j = j + 1 }
  return j
}

fn aksara(t, u) = t.eff[u] == AK || t.eff[u] == AS || t.codes[u] == 9676   # U+25CC
fn pi(t, k) = ranges[t.at[k]].pi
fn pf(t, k) = ranges[t.at[k]].pf
fn east(t, k) = ranges[t.at[k]].east

fn decide(t, i) {
  let n = t.codes.count
  let a, b, A, B = t.cls[i - 1], t.cls[i], t.eff[i - 1], t.eff[i]
  let unit, before = t.start[i - 1], t.nonspace[i - 1]
  let base = t.codes[unit]
  if a == BK { return MANDATORY }                                                     # LB4
  if a == CR && b == LF { return NONE }                                               # LB5
  if a == CR || a == LF || a == NL { return MANDATORY }
  if end(b) { return NONE }                                                           # LB6
  if b == SP || b == ZW { return NONE }                                               # LB7
  if before >= 0 && t.eff[before] == ZW { return ALLOWED }                            # LB8
  if a == ZWJ { return NONE }                                                         # LB8a
  if t.joined[i] { return NONE }                                                      # LB9
  if A == WJ || B == WJ { return NONE }                                               # LB11
  if A == GL { return NONE }                                                          # LB12
  if B == GL && A != SP && A != BA && A != HY { return NONE }                         # LB12a
  if B == CL || B == CP || B == EX || B == SY { return NONE }                         # LB13
  if before >= 0 && t.eff[before] == OP { return NONE }                               # LB14
  if before >= 0 && t.eff[before] == QU && pi(t, before) {                            # LB15a
    if before == 0 { return NONE }
    let q = t.eff[before - 1]
    if end(q) || q == OP || q == QU || q == GL || q == SP || q == ZW { return NONE }
  }
  if B == QU && pf(t, i) {                                                            # LB15b
    let j = next_unit(t, i)
    if j == n { return NONE }
    let e = t.eff[j]
    if e == SP || e == GL || e == WJ || e == CL || e == QU || e == CP || e == EX || e == IS || e == SY || end(e) || e == ZW {
      return NONE
    }
  }
  if A == SP && B == IS {                                                             # LB15c
    let j = next_unit(t, i)
    if j < n && t.eff[j] == NU { return ALLOWED }
  }
  if B == IS { return NONE }                                                          # LB15d
  if B == NS && before >= 0 && (t.eff[before] == CL || t.eff[before] == CP) { return NONE } # LB16
  if B == B2 && before >= 0 && t.eff[before] == B2 { return NONE }                    # LB17
  if A == SP { return ALLOWED }                                                       # LB18
  if B == QU && !pi(t, i) { return NONE }                                             # LB19
  if A == QU && !pf(t, unit) { return NONE }
  if B == QU {                                                                        # LB19a
    let j = next_unit(t, i)
    if !east(t, unit) || j == n || !east(t, j) { return NONE }
  }
  if A == QU && (!east(t, i) || unit == 0 || !east(t, t.start[unit - 1])) { return NONE }
  if B == CB || A == CB { return ALLOWED }                                            # LB20
  if (A == HY || base == 8208) && B == AL {                                           # LB20a (U+2010)
    let w = if unit > 0 { t.eff[unit - 1] } else { SP }
    if end(w) || w == SP || w == ZW || w == CB || w == GL { return NONE }
  }
  if B == BA || B == HY || B == NS || A == BB { return NONE }                         # LB21
  if (A == HY || (A == BA && !east(t, unit))) && unit > 0 && t.eff[unit - 1] == HL && B != HL { return NONE } # LB21a
  if A == SY && B == HL { return NONE }                                               # LB21b
  if B == IN { return NONE }                                                          # LB22
  if ((A == AL || A == HL) && B == NU) || (A == NU && (B == AL || B == HL)) { return NONE } # LB23
  if (A == PR && (B == ID || B == EB || B == EM)) || ((A == ID || A == EB || A == EM) && B == PO) { return NONE } # LB23a
  if ((A == PR || A == PO) && (B == AL || B == HL)) || ((A == AL || A == HL) && (B == PR || B == PO)) { return NONE } # LB24
  if (B == PO || B == PR) && (t.numeric[i - 1] || ((A == CL || A == CP) && unit > 0 && t.numeric[unit - 1])) { return NONE } # LB25
  if A == PO || A == PR {
    if B == NU { return NONE }
    if B == OP {
      let j = next_unit(t, i)
      if j < n && t.eff[j] == IS { j = next_unit(t, j) }
      if j < n && t.eff[j] == NU { return NONE }
    }
  }
  if (A == HY || A == IS || t.numeric[i - 1]) && B == NU { return NONE }
  if A == JL && (B == JL || B == JV || B == H2 || B == H3) { return NONE }            # LB26
  if ((A == JV || A == H2) && (B == JV || B == JT)) || ((A == JT || A == H3) && B == JT) { return NONE }
  let jamo = A == JL || A == JV || A == JT || A == H2 || A == H3                       # LB27
  if (jamo && B == PO) || (A == PR && (B == JL || B == JV || B == JT || B == H2 || B == H3)) { return NONE }
  if (A == AL || A == HL) && (B == AL || B == HL) { return NONE }                     # LB28
  if A == AP && aksara(t, i) { return NONE }                                          # LB28a
  if aksara(t, unit) && (B == VF || B == VI) { return NONE }
  if A == VI && unit > 0 && aksara(t, t.start[unit - 1]) && (B == AK || t.codes[i] == 9676) { return NONE }
  if aksara(t, unit) && aksara(t, i) {
    let j = next_unit(t, i)
    if j < n && t.eff[j] == VF { return NONE }
  }
  if A == IS && (B == AL || B == HL) { return NONE }                                  # LB29
  if (A == AL || A == HL || A == NU) && B == OP && !east(t, i) { return NONE }        # LB30
  if A == CP && !east(t, unit) && (B == AL || B == HL || B == NU) { return NONE }
  if A == RI && B == RI && t.ri[i - 1] % 2 == 1 { return NONE }                       # LB30a
  if B == EM && (A == EB || ranges[t.at[unit]].pictographic) { return NONE }          # LB30b
  return ALLOWED                                                                      # LB31
}

on line {
  let codes: list of number
  let cls: list of number
  let eff: list of number
  let start: list of number
  let nonspace: list of number
  let ri: list of number
  let at: list of number
  let joined: list of bool
  let numeric: list of bool
  for p in line.points {
    let i = codes.count
    let r = range(p.code)
    let c = ranges[r].class
    let mark = c == CM || c == ZWJ
    let previous = if i > 0 { eff[i - 1] } else { SP }
    let joins = mark && i > 0 && !end(previous) && previous != SP && previous != ZW   # LB9, LB10
    let e = if joins { previous } else if mark { AL } else { c }
    codes.append(p.code)
    cls.append(c)
    at.append(r)
    joined.append(joins)
    eff.append(e)
    start.append(if joins { start[i - 1] } else { i })
    nonspace.append(if e != SP { start[i] } else if i > 0 { nonspace[i - 1] } else { -1 })
    numeric.append(if joins { numeric[i - 1] } else { e == NU || ((e == SY || e == IS) && i > 0 && numeric[i - 1]) })
    ri.append(if joins { ri[i - 1] } else if e == RI { (if i > 0 { ri[i - 1] } else { 0 }) + 1 } else { 0 })
  }
  let t = Text { codes: codes, cls: cls, eff: eff, joined: joined, start: start, nonspace: nonspace, numeric: numeric, ri: ri,
                 at: at }
  let out: list of i64 = [0]                                                          # LB2
  for i in 1 .. codes.count { out.append(i64(decide(t, i))) }
  if codes.count > 0 { out.append(2) }                                                # LB3
  breaks = bytes(out)
}