Special sentences creating new notations for literal values.
§1. The ability to create new ways to write literal values is one of the best
features of Inform, but it is complex to parse, and has around 30 problem
messages associated with it. The :literals set of test cases may be useful
when tweaking the code below.
The first point to note is that "X specifies Y" sentences have two different uses, but that both are covered below. First, they create literal patterns, and the syntax for that can be quite involved:
1 tonne (in metric units, in tonnes, singular) specifies a mass scaled up by 1000.
Second, they can gives dimensional instructions about kinds:
A length times a length specifies an area.
This is a slightly unhappy ambiguity, but the potential for confusion is low. Nobody who defines a literal pattern with the word "times" in can expect good results anyway, given that "times" will usually be interpreted as multiplication when Inform eventually parses such a literal.
§2. We create new literal patterns during pass 1, which imposes two timing constraints:
- The specification sentence must come after the sentence creating the kind of value being specified; but
- It must come before any sentences using constants written in this notation.
In practice both constraints seem to be accepted by users as reasonable, and this causes no trouble.
int LPRequests::specifies_SMF(int task, parse_node *V, wording *NPs) { wording SW = (NPs)?(NPs[0]):EMPTY_WORDING; wording OW = (NPs)?(NPs[1]):EMPTY_WORDING; switch (task) { case ACCEPT_SMFT: /* "10'23 specifies a running time." */ if (<np-alternative-list>(SW)) { parse_node *S = <<rp>>; if (<np-unparsed>(OW)) { parse_node *O = <<rp>>; V->next = S; V->next->next = O; return TRUE; } } break; case PASS_1_SMFT: LPRequests::new_list(V->next, V->next->next, NULL); break; } return FALSE; }
§3. The code in this section parses sentences which set up new literal patterns, puts the (quite complicated) result into a lp_specification object, and sends that to Literal Patterns (in values).
define SINGULAR_LPN 1
define PLURAL_LPN 2
define ABANDON_LPN 4 /* for error recovery only */
typedef struct lp_specification { struct wording notation_wording; /* the actual notation, e.g., "2 tonnes" */ struct kind *kind_specified; /* what kind this sentence specifies */ int scaled_dir; /* one of theLP_SCALED_*constants above */ int scale_factor; double scale_factor_as_double; struct parse_node *equivalent_value; double equivalent_value_as_double; struct parse_node *offset_value; double offset_value_as_double; int number_base; int uses_real_arithmetic; int notation_options; /* a bitmap of the*_LPNvalues */ struct literal_pattern_name *notation_groups; /* "in metric units", and so on */ struct parse_node *part_np_list; } lp_specification; lp_specification glps; void LPRequests::initialise(lp_specification *lps) { lps->scaled_dir = LP_SCALED_UP; lps->scale_factor = 1; lps->scale_factor_as_double = 1.0; lps->equivalent_value_as_double = 0.0; lps->equivalent_value = NULL; lps->offset_value_as_double = 0.0; lps->offset_value = NULL; lps->number_base = 10; lps->uses_real_arithmetic = FALSE; lps->notation_options = 0; lps->notation_groups = NULL; lps->notation_wording = EMPTY_WORDING; lps->part_np_list = NULL; }
- The structure lp_specification is private to this section.
§4. One can define LPs with a list of alternatives, of which the first is the "primary alternative" and said to be the "owner" of the rest. For instance:
1 tonne (in metric units, in tonnes, singular) or 2 tonnes (in metric units, in tonnes, plural) specifies a mass scale_factor up by 1000.
Here "1 tonne" is the primary alternative, and it owns "2 tonnes".
literal_pattern *LPRequests::new_list(parse_node *p, parse_node *q, literal_pattern *m) { if (Node::get_type(p) == AND_NT) { m = LPRequests::new_list(p->down, q, m); return LPRequests::new_list(p->down->next, q, m); } LPRequests::initialise(&glps); Parse the object noun phrase of the specifies sentence4.2; Parse the subject noun phrase of the specifies sentence4.1; return LiteralPatterns::new_literal_specification_inner(&glps, p, q, m); }
§4.1. Parse the subject noun phrase of the specifies sentence4.1 =
<specifies-sentence-subject>(Node::get_text(p)); glps.notation_wording = GET_RW(<specifies-sentence-subject>, 1); glps.notation_options = <<r>>; glps.notation_groups = <<rp>>; if (glps.notation_options & ABANDON_LPN) return m;
- This code is used in §4.
§5. The following grammar is used to parse the new literal patterns defined in a "specifies" sentence.
define PARTS_LPC 1
define SCALING_LPC 2
define OFFSET_LPC 3
define EQUIVALENT_LPC 4
§6. Formally, the subject noun phrase of a "specifies" sentence must be a list of alternatives each of which matches the following:
<specifies-sentence-subject> ::= ... ( {<lp-group-list>} ) | ==> { pass 1 } <k-kind-articled> times <k-kind-articled> | ==> Store left and right kinds6.1 <s-type-expression> times <s-type-expression> | ==> Issue PM_MultiplyingNonKOVs problem6.3 <k-kind-articled> minus <k-kind-articled> | ==> Subtract left and right kinds6.2 <s-type-expression> minus <s-type-expression> | ==> Issue PM_MultiplyingNonKOVs problem6.3 ... ==> { 0, NULL } <lp-group-list> ::= <lp-group> <lp-group-tail> | ==> { R[1] | R[2], LPRequests::compose(RP[1], RP[2]) } <lp-group> ==> { pass 1 } <lp-group-tail> ::= , and <lp-group-list> | ==> { pass 1 } ,/and <lp-group-list> ==> { pass 1 } <lp-group> ::= singular | ==> { SINGULAR_LPN, NULL } plural | ==> { PLURAL_LPN, NULL } <lp-group-name> | ==> { 0, LiteralPatterns::new_lpn(EMPTY_WORDING, RP[1]) } in ...... | ==> { 0, LiteralPatterns::new_lpn(W, NULL) } ...... ==> Issue PM_BadLPNameOption problem6.4
- This is Preform grammar, not regular C code.
§6.1. Store left and right kinds6.1 =
Kinds::Dimensions::dim_set_multiplication(RP[1], RP[2], glps.kind_specified); ==> { ABANDON_LPN, - };
- This code is used in §6.
§6.2. Subtract left and right kinds6.2 =
Kinds::Dimensions::dim_set_subtraction(RP[1], RP[2], glps.kind_specified); ==> { ABANDON_LPN, - };
- This code is used in §6.
§6.3. Issue PM_MultiplyingNonKOVs problem6.3 =
if (preform_lookahead_mode == FALSE) StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_MultiplyingNonKOVs), "only kinds of value can be multiplied or subtracted here", "and only in a sentence like 'A length times a length specifies an area.'"); ==> { ABANDON_LPN, NULL }
- This code is used in §6 (twice).
§6.4. Issue PM_BadLPNameOption problem6.4 =
if (preform_lookahead_mode == FALSE) { Problems::quote_source(1, current_sentence); Problems::quote_wording(2, W); StandardProblems::handmade_problem(Task::syntax_tree(), _p_(PM_BadLPNameOption)); Problems::issue_problem_segment( "In the specification %1, I was expecting that '%2' would be an optional " "note about one of the notations: it should have been one of 'singular', " "'plural' or 'in ...'."); Problems::issue_problem_end(); } ==> { ABANDON_LPN, NULL }
- This code is used in §6.
§4.2. Parse the object noun phrase of the specifies sentence4.2 =
glps.offset_value = NULL; <specifies-sentence-object>(Node::get_text(q)); switch (<<r>>) { case PARTS_LPC: glps.part_np_list = <<rp>>; break; case SCALING_LPC: break; } if (glps.kind_specified == NULL) return m;
- This code is used in §4.
§7. The object noun phrase of a "specifies" sentence is required to match the following grammar. Note that the tails are mutually exclusive; you can't set both scaling and an equivalent, for instance.
<specifies-sentence-object> ::= <kind-specified-with-base> <lp-specification-tail> | ==> { pass 2 } <kind-specified-with-base> ==> { 0, NULL } <kind-specified-with-base> ::= <k-kind-articled> in <number-base> | ==> { 0, NULL }; glps.number_base = R[2]; glps.kind_specified = RP[1]; <k-kind-articled> | ==> { 0, NULL }; glps.kind_specified = RP[1]; ... ==> Issue PM_LPNotKOV problem7.6 <number-base> ::= binary | ==> { 2, NULL } octal | ==> { 8, NULL } decimal | ==> { 10, NULL } hexadecimal | ==> { 16, NULL } base <cardinal-number> ==> { R[1], NULL } <lp-specification-tail> ::= with parts <lp-part-list> | ==> { PARTS_LPC, RP[1] } <scaling-instruction> | ==> { SCALING_LPC, - } <scaling-instruction> offset by <s-literal> | ==> Scaling together with offset7.1 offset by <s-literal> | ==> Offset alone7.2 equivalent to <s-literal> ==> Equivalency7.3 <scaling-instruction> ::= scaled up by <scaling-amount> | ==> { SCALING_LPC, - }; glps.scaled_dir = LP_SCALED_UP scaled down by <scaling-amount> | ==> { SCALING_LPC, - }; glps.scaled_dir = LP_SCALED_DOWN scaled at <scaling-amount> ==> { SCALING_LPC, - }; glps.scaled_dir = LP_SCALED_AT <scaling-amount> ::= <cardinal-number> | ==> Accept an integer scale factor7.4 <s-literal-real-number> ==> Accept a real scale factor7.5
- This is Preform grammar, not regular C code.
§7.1. Scaling together with offset7.1 =
glps.offset_value_as_double = LiteralPatterns::get_latest_real(); glps.offset_value = RP[2]; ==> { SCALING_LPC, - };
- This code is used in §7.
glps.offset_value_as_double = LiteralPatterns::get_latest_real(); glps.offset_value = RP[1]; ==> { OFFSET_LPC, - };
- This code is used in §7.
glps.equivalent_value_as_double = LiteralPatterns::get_latest_real(); glps.equivalent_value = RP[1]; ==> { EQUIVALENT_LPC, - };
- This code is used in §7.
§7.4. Accept an integer scale factor7.4 =
glps.scale_factor = R[1]; glps.scale_factor_as_double = (double) R[1]; ==> { -, - };
- This code is used in §7.
§7.5. Accept a real scale factor7.5 =
glps.scale_factor = 1; glps.scale_factor_as_double = LiteralPatterns::get_latest_real(); glps.uses_real_arithmetic = TRUE; ==> { -, - };
- This code is used in §7.
§7.6. Issue PM_LPNotKOV problem7.6 =
if (preform_lookahead_mode == FALSE) StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_LPNotKOV), "you can only specify ways to write kinds of value", "as created with sentences like 'A weight is a kind of value.'");
- This code is used in §7.
§8. Of the optional tails, the only tricky one is the part list, which has the following rather extensive grammar. This handles text like:
dollars and cents (optional, preamble optional)
The text is a list of part-names, each of which can optionally be followed by a bracketed list of up to three options in any order.
<lp-part-list> ::= <lp-part> , and <lp-part-list> | ==> { 0, Node::compose(RP[1], RP[2]) } <lp-part> , <lp-part-list> | ==> { 0, Node::compose(RP[1], RP[2]) } <lp-part> and <lp-part-list> | ==> { 0, Node::compose(RP[1], RP[2]) } <lp-part> ==> { 0, RP[1] } <lp-part> ::= <np-unparsed-bal> ( <lp-part-option-list> ) | ==> { 0, LPRequests::mark(RP[1], R[2], <<kind:corresponding>>, <<min_part_val>>, <<max_part_val>>, <<digits_wd_val>>, <<values_wd_val>>) } <np-unparsed-bal> ==> { 0, RP[1] } <lp-part-option-list> ::= <lp-part-option> <lp-part-option-tail> | ==> { R[1] | R[2], - } <lp-part-option> ==> { pass 1 } <lp-part-option-tail> ::= , and <lp-part-option-list> | ==> { pass 1 } ,/and <lp-part-option-list> ==> { pass 1 } <lp-part-option> ::= optional | ==> { OPTIONAL_LSO, - } preamble optional | ==> { PREAMBLE_OPTIONAL_LSO, - } with leading zeros | ==> { WITH_LEADING_ZEROS_LSO, - } without leading zeros | ==> { WITHOUT_LEADING_ZEROS_LSO, - } in <number-base> | ==> { BASE_LSO*R[1], - } <cardinal-number> to <cardinal-number> | ==> <<min_part_val>> = R[1]; <<max_part_val>> = R[2]; Apply range8.1; up to <cardinal-number> <number-base> digit/digits | ==> Apply based max digit count8.4; up to <cardinal-number> digit/digits | ==> Apply max digit count8.2; <cardinal-number> <number-base> digit/digits | ==> Apply based digit count8.5; <cardinal-number> digit/digits | ==> Apply digit count8.3; digits { <quoted-text> } | ==> <<digits_wd_val>> = Wordings::first_wn(WR[1]); ==> { DIGITS_TEXT_LSO, - } values { <quoted-text> } | ==> <<values_wd_val>> = Wordings::first_wn(WR[1]); ==> { VALUES_TEXT_LSO, - } corresponding to <k-kind> | ==> { KIND_LSO, <<kind:corresponding>> = RP[1] } corresponding to <article> <k-kind> | ==> { KIND_LSO, <<kind:corresponding>> = RP[2] } ...... ==> Issue PM_BadLPPartOption problem8.6
- This is Preform grammar, not regular C code.
if (R[1] > R[2]) StandardProblems::sentence_problem(Task::syntax_tree(), _p_(...), "the minimum value for a named part cannot be greater than the maximum", "or it would be impossible for any value of this kind to exist."); ==> { MINIMUM_LSO + MAXIMUM_LSO, - };
- This code is used in §8.
§8.2. Apply max digit count8.2 =
if (R[1] < 1) StandardProblems::sentence_problem(Task::syntax_tree(), _p_(...), "the digit count in a named part has to be at least 1", "for obvious reasons."); ==> { MAX_DIGITS_LSO*R[1], - }
- This code is used in §8.
if (R[1] < 1) StandardProblems::sentence_problem(Task::syntax_tree(), _p_(...), "the digit count in a named part has to be at least 1", "for obvious reasons."); ==> { MAX_DIGITS_LSO*R[1] + MIN_DIGITS_LSO*R[1] + WITH_LEADING_ZEROS_LSO, - };
- This code is used in §8.
§8.4. Apply based max digit count8.4 =
if (R[1] < 1) StandardProblems::sentence_problem(Task::syntax_tree(), _p_(...), "the digit count in a named part has to be at least 1", "for obvious reasons."); ==> { MAX_DIGITS_LSO*R[1] + BASE_LSO*R[2], - }
- This code is used in §8.
§8.5. Apply based digit count8.5 =
if (R[1] < 1) StandardProblems::sentence_problem(Task::syntax_tree(), _p_(...), "the digit count in a named part has to be at least 1", "for obvious reasons."); ==> { MAX_DIGITS_LSO*R[1] + MIN_DIGITS_LSO*R[1] + BASE_LSO*R[2] + WITH_LEADING_ZEROS_LSO, - };
- This code is used in §8.
§8.6. Issue PM_BadLPPartOption problem8.6 =
if (preform_lookahead_mode == FALSE) { Problems::quote_source(1, current_sentence); Problems::quote_wording(2, W); StandardProblems::handmade_problem(Task::syntax_tree(), _p_(PM_BadLPPartOption)); Problems::issue_problem_segment( "In the specification %1, I was expecting that '%2' would be an optional " "note about one of the parts: it should have been one of 'optional', " "'preamble optional' or 'without leading zeros'."); Problems::issue_problem_end(); } ==> { 0, - };
- This code is used in §8.
literal_pattern_name *LPRequests::compose(literal_pattern_name *A, literal_pattern_name *B) { if (A == NULL) return B; A->next_with_rp = B; return A; }
define OPTIONAL_LSO 0x00000001
define PREAMBLE_OPTIONAL_LSO 0x00000002
define WITH_LEADING_ZEROS_LSO 0x00000004
define WITHOUT_LEADING_ZEROS_LSO 0x00000008
define MAXIMUM_LSO 0x00000010
define MINIMUM_LSO 0x00000020
define DIGITS_TEXT_LSO 0x00000040
define VALUES_TEXT_LSO 0x00000080
define KIND_LSO 0x00000100
define BASE_LSO 0x00000200
define BASE_MASK_LSO 0x0000fe00
define MIN_DIGITS_LSO 0x00010000
define MIN_DIGITS_MASK_LSO 0x007f0000
define MAX_DIGITS_LSO 0x01000000
define MAX_DIGITS_MASK_LSO 0x7f000000
parse_node *LPRequests::mark(parse_node *A, int N, kind *K, int MI, int MA, int DT, int VT) { if (A) { Annotations::write_int(A, lpe_options_ANNOT, N); if (N & KIND_LSO) Node::set_corresponding_kind(A, K); if (N & MINIMUM_LSO) Annotations::write_int(A, lpe_min_ANNOT, MI); if (N & MAXIMUM_LSO) Annotations::write_int(A, lpe_max_ANNOT, MA); if (N & DIGITS_TEXT_LSO) Annotations::write_int(A, lpe_digits_ANNOT, DT); if (N & VALUES_TEXT_LSO) Annotations::write_int(A, lpe_values_ANNOT, VT); } return A; }