@x
else  begin print_err("I can't go on meeting you like this");
@y
else  begin print_err("I can't go on meeting you like this (");
            print(s); print_char(")");
@z

@x
@d disc_node=7 {|type| of a discretionary node}
@y
With extended hyphenation, discretionaries have an additional property,
the |hyphen_class| (which is an integer $0\leq\mathit{hyphen_class}\leq 9$).
The penalty charged for a break depends on the |hyphen_class|.

@d disc_node=7 {|type| of a discretionary node}
@d disc_node_size=3 {number of words to allocate for a discretionary}
@d hyphen_class(#)==mem[#+2].int {the class of this hyphen}
@z

@x
@p function new_disc:pointer; {creates an empty |disc_node|}
var p:pointer; {the new node}
begin p:=get_node(small_node_size); type(p):=disc_node;
replace_count(p):=0; pre_break(p):=null; post_break(p):=null;
new_disc:=p;
end;
@y
@p function new_disc(c:integer):pointer; {creates an empty |disc_node|}
var p:pointer; {the new node}
begin p:=get_node(disc_node_size); type(p):=disc_node;
replace_count(p):=0; pre_break(p):=null; post_break(p):=null;
hyphen_class(p):=c; new_disc:=p;
end;
@z

@x
disc_node: begin short_display(pre_break(p));
  short_display(post_break(p));@/
@y
disc_node: begin short_display(pre_break(p));
  if hyphen_classes_en and(hyphen_class(p)<>1) then print_int(hyphen_class(p));
  short_display(post_break(p));@/
@z

@x
    disc_node: begin flush_node_list(pre_break(p));
      flush_node_list(post_break(p));
      end;
@y
    disc_node: begin flush_node_list(pre_break(p));
      flush_node_list(post_break(p));
      free_node(p,disc_node_size); goto done;
      end;
@z

@x
disc_node: begin r:=get_node(small_node_size);
  pre_break(r):=copy_node_list(pre_break(p));
  post_break(r):=copy_node_list(post_break(p));
  end;
@y
disc_node: begin r:=new_disc(hyphen_class(p));
  pre_break(r):=copy_node_list(pre_break(p));
  post_break(r):=copy_node_list(post_break(p));
  end;
@z

@x
@d hyph_data=99 {hyphenation data ( \.{\\hyphenation}, \.{\\patterns} )}
@y
@d hyph_data=99 {hyphenation data ( \.{\\hyphenation}, \.{\\patterns},
                                    \.{\\hyphenpenalties} )}
@z

@x
  if m=hmode then if nest[p].pg_field <> @'40600000 then
    begin print(" (language"); print_int(nest[p].pg_field mod @'200000);
    print(":hyphenmin"); print_int(nest[p].pg_field div @'20000000);
    print_char(","); print_int((nest[p].pg_field div @'200000) mod @'100);
    print_char(")");
    end;
@y
  if m=hmode then if nest[p].pg_field <> @'40601000 then begin
    print(" (language"); print_int(nest[p].pg_field mod @'400);
    print(":hyphenmin"); print_int(nest[p].pg_field div @'20000000);
    print_char(","); print_int((nest[p].pg_field div @'200000) mod @'100);
    if hyphen_classes_en then begin
      print(":hyphenclasses");
      print_int((nest[p].pg_field div @'400) mod @'20);
    end;
    print_char(")");
  end;
@z

@x
@d hyphen_penalty_code=3 {penalty for break after discretionary hyphen}
@d ex_hyphen_penalty_code=4 {penalty for break after explicit hyphen}
@y
@d hyphen_classes_code=3 {number of hyphenation classes}
@d ex_hyphen_class_code=4 {hyphenation class of automatically
                            inserted empty discretionaries}
@z

@x
{hyphen classes int_pars go here}
@y
@d hyphen_classes_state_code=58
@d hyphen_penalty_base=59 {10 penalties for breaks after discretionary hyphens,
                              numbered from 0 to 9}
@z

@x
@d hyphen_penalty==int_par(hyphen_penalty_code)
@d ex_hyphen_penalty==int_par(ex_hyphen_penalty_code)
@y
@d hyphen_classes_state==int_par(hyphen_classes_state_code)
@d hyphen_classes_en==(hyphen_classes_state>0)
@d hyphen_penalties(#)==int_par(hyphen_penalty_base+#)
@d default_hyphen_class=1
@d hyphen_penalty==hyphen_penalties(default_hyphen_class)
@d ex_hyphen_penalty==hyphen_penalties(ex_hyphen_class)
@z

@x
hyphen_penalty_code:print_esc("hyphenpenalty");
ex_hyphen_penalty_code:print_esc("exhyphenpenalty");
@y
hyphen_classes_code:print_esc("hyphenclasses");
ex_hyphen_class_code:print_esc("exhyphenclass");
@z

@x
error_context_lines_code:print_esc("errorcontextlines");
@y
error_context_lines_code:print_esc("errorcontextlines");
hyphen_classes_state_code:print_esc("hyphenclassesstate");
@z

@x
primitive("hyphenpenalty",assign_int,int_base+hyphen_penalty_code);@/
@!@:hyphen_penalty_}{\.{\\hyphenpenalty} primitive@>
primitive("exhyphenpenalty",assign_int,int_base+ex_hyphen_penalty_code);@/
@!@:ex_hyphen_penalty_}{\.{\\exhyphenpenalty} primitive@>
@y
primitive("hyphenclasses",assign_int,int_base+hyphen_classes_code);@/
@!@:hyphen_classes_}{\.{\\hyphenclasses} primitive@>
primitive("exhyphenclass",assign_int,int_base+ex_hyphen_class_code);@/
@!@:ex_hyphen_class_}{\.{\\exhyphenclass} primitive@>
@z

@x
primitive("errorcontextlines",assign_int,int_base+error_context_lines_code);@/
@!@:error_context_lines_}{\.{\\errorcontextlines} primitive@>
@y
primitive("errorcontextlines",assign_int,int_base+error_context_lines_code);@/
@!@:error_context_lines_}{\.{\\errorcontextlines} primitive@>
primitive("hyphenpenalty",assign_int,int_base+hyphen_penalty_base+1);@/
@!@:hyphen_penalty_}{\.{\\hyphenpenalty} primitive@>
primitive("exhyphenpenalty",assign_int,int_base+hyphen_penalty_base
                                               +ex_hyphen_class);@/
@!@:ex_hyphen_penalty_}{\.{\\exhyphenpenalty} primitive@>
primitive("hyphenclassesstate",assign_int,int_base+hyphen_classes_state_code);@/
@!@:hyphen_classes_state_}{\.{\\hyphenclassesstate} primitive@>
@z

@x
escape_char:="\"; end_line_char:=carriage_return;
@y
escape_char:="\"; end_line_char:=carriage_return;
hyphen_classes_state:=0;
@z

@x
@ Here is a procedure that displays the contents of |eqtb[n]|
symbolically.

@p@t\4@>@<Declare the procedure called |print_cmd_chr|@>@;@/
@y
@ Here is a procedure that displays the contents of |eqtb[n]|
symbolically.

@p@t\4@>@<Declare the procedure called |print_cmd_chr|@>@;@/
function is_enabled(@!b:boolean;@!j:quarterword;@!k:halfword):boolean;
begin
 if not b then begin
   print_err("Improper "); print_cmd_chr(j,k);
   help1("Sorry, this optional feature has been disabled."); error;
 end;
 is_enabled:=b;
end;
@z

@x
init_cur_lang:=prev_graf mod @'200000;
@y
init_cur_lang:=prev_graf mod @'400;
init_hyf_class:=(prev_graf div @'400) mod @'20;
@z

@x
@ The following code knows that discretionary texts contain
only character nodes, kern nodes, box nodes, rule nodes, and ligature nodes.

@<Try to break after a discretionary fragment...@>=
begin s:=pre_break(cur_p); disc_width:=0;
if s=null then try_break(ex_hyphen_penalty,hyphenated)
else  begin repeat @<Add the width of node |s| to |disc_width|@>;
    s:=link(s);
  until s=null;
  act_width:=act_width+disc_width;
  try_break(hyphen_penalty,hyphenated);
  act_width:=act_width-disc_width;
  end;
@y
@ The following code knows that discretionary texts contain
only character nodes, kern nodes, box nodes, rule nodes, and ligature nodes.
If |ex_hyphen_penalty=0|, we might encounter automatically inserted empty
discretionary nodes of class~0. We never break at these discretionaries,
nevertheless they are necessary to differentiate the output of
\.{\\showhyphens} for automatically inserted hyphens of classes 0 and~1.

@<Try to break after a discretionary fragment...@>=
begin if hyphen_class(cur_p)>0 then begin
  s:=pre_break(cur_p); disc_width:=0;
  while s<>null do begin
    @<Add the width of node |s| to |disc_width|@>;
    s:=link(s);
  end;
  act_width:=act_width+disc_width;
  try_break(hyphen_penalties(hyphen_class(cur_p)),hyphenated);
  act_width:=act_width-disc_width;
  end;
@z

@x
begin t:=replace_count(q);
@y
begin t:=replace_count(q); hyphen_class(q):=1;
@z

@x
cur_lang:=init_cur_lang; l_hyf:=init_l_hyf; r_hyf:=init_r_hyf;
@y
cur_lang:=init_cur_lang; l_hyf:=init_l_hyf; r_hyf:=init_r_hyf;
hyf_class:=init_hyf_class;
@z

@x
@!hyf_bchar:halfword; {boundary character after $c_n$}
@y
@!hyf_bchar:halfword; {boundary character after $c_n$}
@!hyf_class,@!init_hyf_class:integer;
@z

@x
@!hyf:array [0..64] of 0..9; {odd values indicate discretionary hyphens}
@y
@!hyf:array [0..64] of small_number;
@z

@x
@<If no hyphens were found, |return|@>=
for j:=l_hyf to hn-r_hyf do if odd(hyf[j]) then goto found1;
return;
found1:
@y
@<If no hyphens were found, |return|@>=
for j:=l_hyf to hn-r_hyf do if hyf[j]>0 then goto found1;
return;
found1:
@z

@x
@d set_cur_r==begin if j<n then cur_r:=qi(hu[j+1])@+else cur_r:=bchar;
    if odd(hyf[j]) then cur_rh:=hchar@+else cur_rh:=non_char;
@y
@d set_cur_r==begin if j<n then cur_r:=qi(hu[j+1])@+else cur_r:=bchar;
    if hyf[j]>0 then cur_rh:=hchar@+else cur_rh:=non_char;
@z

@x
    else begin if hchar<non_char then if odd(hyf[j]) then
@y
    else begin if hchar<non_char then if hyf[j]>0 then
@z

@x
  while link(s)>null do s:=link(s);
  if odd(hyf[j-1]) then
@y
  while link(s)>null do s:=link(s);
  if hyf[j-1]>0 then
@z

@x
repeat r:=get_node(small_node_size);
@y
repeat r:=new_disc(hyf[hyphen_passed]);
@z

@x
hyphen_passed:=j-1; link(hold_head):=null;
until not odd(hyf[j-1])
@y
hyphen_passed:=j-1; link(hold_head):=null;
until hyf[j-1]=0
@z

@x
found: for j:=0 to l_hyf-1 do hyf[j]:=0;
for j:=0 to r_hyf-1 do hyf[hn-j]:=0
@y
for j:=l_hyf to hn-r_hyf do hyf[j]:=hyf[j] mod hyf_class;
found: for j:=0 to l_hyf-1 do hyf[j]:=0;
for j:=0 to r_hyf-1 do hyf[hn-j]:=0
@z

@x
The words in the table point to lists in |mem| that specify hyphen positions
in their |info| fields. The list for $c_1\ldots c_n$ contains the number |k| if
the word $c_1\ldots c_n$ has a discretionary hyphen between $c_k$ and
$c_{k+1}$.
@y
The words in the table point to lists in |mem| that specify hyphen positions
and classes in their |type| and |subtype| fields. The list for $c_1\ldots c_n$
contains the pair |k|, |l| if the word $c_1\ldots c_n$ has a discretionary
hyphen of class |l| between $c_k$ and $c_{k+1}$.

@d hyphen_exception_pos==type
@d hyphen_exception_class==subtype
@z

@x
while s<>null do
  begin hyf[info(s)]:=1; s:=link(s);
  end
@y
while s<>null do
  begin hyf[hyphen_exception_pos(s)]:=hyphen_exception_class(s); s:=link(s);
  end
@z

@x
var n:0..64; {length of current word; not always a |small_number|}
@y
var n:0..64; {length of current word; not always a |small_number|}
 at_hyphen:boolean;
@z

@x
@ @<Enter as many...@>=
n:=0; p:=null;
@y
@ @<Enter as many...@>=
n:=0; p:=null; at_hyphen:=false;
@z

@x
    if cur_cmd=right_brace then return;
    n:=0; p:=null;
@y
    if cur_cmd=right_brace then return;
    n:=0; p:=null; at_hyphen:=false;
@z

@x
if cur_chr="-" then @<Append the value |n| to list |p|@>
@y
if cur_chr="-" then begin
  at_hyphen:=true;
  @<Append the value |n| to list |p|@>;
end else if hyphen_classes_en and at_hyphen and
            ("0"<=cur_chr)and(cur_chr<="9") then begin
 hyphen_exception_class(p):=cur_chr-"0";
 at_hyphen:=false;
end
@z

@x
begin if n<63 then
  begin q:=get_avail; link(q):=p; info(q):=n; p:=q;
  end;
end
@y
begin if n<63 then
  begin q:=get_avail; link(q):=p;
    hyphen_exception_pos(q):=n;
    hyphen_exception_class(q):=1;
    p:=q;
  end;
end
@z

@x
@!digit_sensed:boolean; {should the next digit be treated as a letter?}
@y
@!digits_sensed:integer; {should the next digit be treated as a letter?}
@z

@x
k:=0; hyf[0]:=0; digit_sensed:=false;
loop@+  begin get_x_token;
@y
k:=0; hyf[0]:=0; digits_sensed:=0;
loop@+  begin get_x_token;
@z

@x
    if cur_cmd=right_brace then goto done;
    k:=0; hyf[0]:=0; digit_sensed:=false;
@y
    if cur_cmd=right_brace then goto done;
    k:=0; hyf[0]:=0; digits_sensed:=0;
@z

@x
if digit_sensed or(cur_chr<"0")or(cur_chr>"9") then
@y
if (cur_chr<"0")or(cur_chr>"9")or
   ((not hyphen_classes_en)and(digits_sensed>=1))or(digits_sensed>=2) then
@z

@x
    begin incr(k); hc[k]:=cur_chr; hyf[k]:=0; digit_sensed:=false;
@y
    begin incr(k); hc[k]:=cur_chr; hyf[k]:=0; digits_sensed:=0;
@z

@x
else if k<63 then
  begin hyf[k]:=cur_chr-"0"; digit_sensed:=true;
  end
@y
else if k<63 then begin
  if 10*hyf[k]+cur_chr-"0"<=63 then begin
    hyf[k]:=10*hyf[k]+cur_chr-"0"; incr(digits_sensed);
  end else begin
    print_err("Bad "); print_esc("patterns");
@.Bad \\patterns@>
    help1("(See Appendix H.)"); error;
  end;
end
@z

@x
    if mode>0 then tail_append(new_disc);
@y
    if mode>0 then tail_append(new_disc(ex_hyphen_class));
@z

@x
else norm_min:=h;
end;
@y
else norm_min:=h;
end;
@#
function hyphen_classes:small_number;
var h:integer;
begin
  if not hyphen_classes_en then hyphen_classes:=2
  else begin
    h:=int_par(hyphen_classes_code);
    if h<2 then hyphen_classes:=2@+else if h>10 then hyphen_classes:=10@+
    else hyphen_classes:=h;
  end;
end;

function ex_hyphen_class:small_number;
var h:integer;
begin
  if not hyphen_classes_en then ex_hyphen_class:=2
  else begin
    h:=int_par(ex_hyphen_class_code);
    if h<0 then ex_hyphen_class:=0@+else if h>9 then ex_hyphen_class:=9@+
    else ex_hyphen_class:=h;
  end;
end;
@z

@x
prev_graf:=(norm_min(left_hyphen_min)*@'100+norm_min(right_hyphen_min))
             *@'200000+cur_lang;
@y
prev_graf:=(norm_min(left_hyphen_min)*@'100+norm_min(right_hyphen_min))
             *@'200000+hyphen_classes*@'400+cur_lang;
@z

@x
primitive("discretionary",discretionary,0);
@!@:discretionary_}{\.{\\discretionary} primitive@>
@y
primitive("discretionary",discretionary,0);
@!@:discretionary_}{\.{\\discretionary} primitive@>
primitive("gendiscretionary",discretionary,2);
@!@:gendiscretionary_}{\.{\\gendiscretionary} primitive@>
@z

@x
discretionary: if chr_code=1 then
  print_esc("-")@+else print_esc("discretionary");
@y
discretionary: if chr_code=1 then
  print_esc("-")@+else if chr_code=0 then print_esc("discretionary")
  else print_esc("gendiscretionary");
@z

@x
@ The space factor does not change when we append a discretionary node,
but it starts out as 1000 in the subsidiary lists.

@<Declare act...@>=
procedure append_discretionary;
var c:integer; {hyphen character}
begin tail_append(new_disc);
if cur_chr=1 then
  begin c:=hyphen_char[cur_font];
  if c>=0 then if c<256 then pre_break(tail):=new_character(cur_font,c);
  end
@y
@ The space factor does not change when we append a discretionary node,
but it starts out as 1000 in the subsidiary lists. We temporarily assign
hyphen class 0 to discretionaries generated by one of the original
primitives. This is necessary to simulate \TeX's behaviour on discretionaries
with empty pre-break text. They charge the \.{\\exhyphenpenalty}, so we
have to assign them to hyphen class |ex_hyphen_class|. Using
\.{\\gendiscretionary}, one can generate discretionaries with empty pre-break
text in other hyphen classes.

@<Declare act...@>=
procedure append_discretionary;
var c:integer; {hyphen character}
    class:integer; {hyphen class}
begin
  class:=default_hyphen_class;
  if cur_chr<2 then class:=0
  else if is_enabled(hyphen_classes_en,cur_cmd,cur_chr) then begin
    scan_int;
    if (1<=cur_val)and(cur_val<=9) then class:=cur_val
    else begin
      print_err("No such "); print_esc("hyphen class");
@.No such \\hyphenpenalties@>
      help2("Only numbers 1..9 are valid here.")@/
           ("Proceed; I'll take 1 instead.");
      error;
    end;
  end;
  tail_append(new_disc(class));
  if cur_chr=1 then begin
    c:=hyphen_char[cur_font];
    if c>=0 then if c<256 then pre_break(tail):=new_character(cur_font,c);
    if pre_break(tail)=null then hyphen_class(tail):=ex_hyphen_class
    else hyphen_class(tail):=default_hyphen_class;
  end
@z

@x
@ The three discretionary lists are constructed somewhat as if they were
hboxes. A~subroutine called |build_discretionary| handles the transitions.
(This is sort of fun.)
@y
@ The three discretionary lists are constructed somewhat as if they were
hboxes. A~subroutine called |build_discretionary| handles the transitions.
(This is sort of fun.)

When we enter this routine, |tail| is a discretionary node whose hyphen class
is zero, if it comes from \.{\\discretionary}. In that case we decide about
the proper hyphen class after reading the pre-break text.
@z

@x
0:pre_break(tail):=p;
@y
0:begin pre_break(tail):=p;
 if hyphen_class(tail)=0 then
   if (p=null) then hyphen_class(tail):=ex_hyphen_class
   else hyphen_class(tail):=default_hyphen_class;
end;
@z

@x
primitive("patterns",hyph_data,1);
@!@:patterns_}{\.{\\patterns} primitive@>
@y
primitive("patterns",hyph_data,1);
@!@:patterns_}{\.{\\patterns} primitive@>
primitive("hyphenpenalties",hyph_data,2);
@!@:hyphenpenalties_}{\.{\\hyphenpenalties} primitive@>
@z

@x
hyph_data: if chr_code=1 then print_esc("patterns")
  else print_esc("hyphenation");
@y
hyph_data: if chr_code=1 then print_esc("patterns")
  else if chr_code=0 then print_esc("hyphenation")
  else print_esc("hyphenpenalties");
@z

@x
  else  begin new_hyph_exceptions; goto done;
    end;
@y
  else if cur_chr=0 then begin
    new_hyph_exceptions;
    goto done;
  end else if is_enabled(hyphen_classes_en,cur_cmd,cur_chr) then begin
    scan_int; p:=cur_val;
    scan_optional_equals; scan_int;
    if (1<=p)and(p<=9) then begin
      word_define(int_base+hyphen_penalty_base+p,cur_val);
    end else begin
      print_err("No such "); print_esc("hyphenpenalties");
@.No such \\hyphenpenalties@>
      help2("Only numbers 1..9 are valid here.")@/
      ("Proceed; I'll ignore the assignment I just read.");
     error;
    end;
  end;
@z

@x
@d what_lang(#)==link(#+1) {language number, in the range |0..255|}
@d what_lhm(#)==type(#+1) {minimum left fragment, in the range |1..63|}
@d what_rhm(#)==subtype(#+1) {minimum right fragment, in the range |1..63|}
@y
@d what_lhm(#)==mem[#+1].qqqq.b0 {minimum left fragment, in the range |1..63|}
@d what_rhm(#)==mem[#+1].qqqq.b1 {minimum right fragment, in the range |1..63|}
@d what_lang(#)==mem[#+1].qqqq.b2 {language number, in the range |0..255|}
@d what_hyf(#)==mem[#+1].qqqq.b3 {number of hyphen classes, in the range |2..10|}
@z

@x
  print_int(what_lhm(p)); print_char(",");
  print_int(what_rhm(p)); print_char(")");
@y
  print_int(what_lhm(p)); print_char(",");
  print_int(what_rhm(p)); print_char(")");
  if hyphen_classes_en then begin
    print("(hyphen classes "); print_int(what_hyf(p));
    print_char(")");
  end;
@z

@x
@ @d adv_past(#)==@+if subtype(#)=language_node then
    begin cur_lang:=what_lang(#); l_hyf:=what_lhm(#); r_hyf:=what_rhm(#);@+end
@y
@ @d adv_past(#)==@+if subtype(#)=language_node then
    begin cur_lang:=what_lang(#); l_hyf:=what_lhm(#); r_hyf:=what_rhm(#);
    hyf_class:=what_hyf(#);@+end
@z

@x
if l<>clang then
  begin new_whatsit(language_node,small_node_size);
  what_lang(tail):=l; clang:=l;@/
  what_lhm(tail):=norm_min(left_hyphen_min);
  what_rhm(tail):=norm_min(right_hyphen_min);
@y
if l<>clang then
  begin new_whatsit(language_node,small_node_size);
  what_lang(tail):=l; clang:=l;@/
  what_lhm(tail):=norm_min(left_hyphen_min);
  what_rhm(tail):=norm_min(right_hyphen_min);
  what_hyf(tail):=hyphen_classes;
@z

@x
  what_lang(tail):=clang;
  what_lhm(tail):=norm_min(left_hyphen_min);
  what_rhm(tail):=norm_min(right_hyphen_min);
@y
  what_lang(tail):=clang;
  what_lhm(tail):=norm_min(left_hyphen_min);
  what_rhm(tail):=norm_min(right_hyphen_min);
  what_hyf(tail):=hyphen_classes;
@z