<html><head><link rel="stylesheet" href="resource://content-accessible/plaintext.css"><style media="screen" id="__markdown-viewer__md_css">/*
* Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
@media (prefers-color-scheme: dark) {
:root {
--color-prettylights-syntax-comment: #8b949e;
--color-prettylights-syntax-constant: #79c0ff;
--color-prettylights-syntax-entity: #d2a8ff;
--color-prettylights-syntax-storage-modifier-import: #c9d1d9;
--color-prettylights-syntax-entity-tag: #7ee787;
--color-prettylights-syntax-keyword: #ff7b72;
--color-prettylights-syntax-string: #a5d6ff;
--color-prettylights-syntax-variable: #ffa657;
--color-prettylights-syntax-brackethighlighter-unmatched: #f85149;
--color-prettylights-syntax-invalid-illegal-text: #f0f6fc;
--color-prettylights-syntax-invalid-illegal-bg: #8e1519;
--color-prettylights-syntax-carriage-return-text: #f0f6fc;
--color-prettylights-syntax-carriage-return-bg: #b62324;
--color-prettylights-syntax-string-regexp: #7ee787;
--color-prettylights-syntax-markup-list: #f2cc60;
--color-prettylights-syntax-markup-heading: #1f6feb;
--color-prettylights-syntax-markup-italic: #c9d1d9;
--color-prettylights-syntax-markup-bold: #c9d1d9;
--color-prettylights-syntax-markup-deleted-text: #ffdcd7;
--color-prettylights-syntax-markup-deleted-bg: #67060c;
--color-prettylights-syntax-markup-inserted-text: #aff5b4;
--color-prettylights-syntax-markup-inserted-bg: #033a16;
--color-prettylights-syntax-markup-changed-text: #ffdfb6;
--color-prettylights-syntax-markup-changed-bg: #5a1e02;
--color-prettylights-syntax-markup-ignored-text: #c9d1d9;
--color-prettylights-syntax-markup-ignored-bg: #1158c7;
--color-prettylights-syntax-meta-diff-range: #d2a8ff;
--color-prettylights-syntax-brackethighlighter-angle: #8b949e;
--color-prettylights-syntax-sublimelinter-gutter-mark: #484f58;
--color-prettylights-syntax-constant-other-reference-link: #a5d6ff;
--color-fg-default: #c9d1d9;
--color-fg-muted: #8b949e;
--color-fg-subtle: #484f58;
--color-canvas-default: #0d1117;
--color-canvas-subtle: #161b22;
--color-border-default: #30363d;
--color-border-muted: #21262d;
--color-neutral-muted: rgba(110,118,129,0.4);
--color-accent-fg: #58a6ff;
--color-accent-emphasis: #1f6feb;
--color-danger-fg: #f85149;
}
}
@media (prefers-color-scheme: light) {
:root {
--color-prettylights-syntax-comment: #6e7781;
--color-prettylights-syntax-constant: #0550ae;
--color-prettylights-syntax-entity: #8250df;
--color-prettylights-syntax-storage-modifier-import: #24292f;
--color-prettylights-syntax-entity-tag: #116329;
--color-prettylights-syntax-keyword: #cf222e;
--color-prettylights-syntax-string: #0a3069;
--color-prettylights-syntax-variable: #953800;
--color-prettylights-syntax-brackethighlighter-unmatched: #82071e;
--color-prettylights-syntax-invalid-illegal-text: #f6f8fa;
--color-prettylights-syntax-invalid-illegal-bg: #82071e;
--color-prettylights-syntax-carriage-return-text: #f6f8fa;
--color-prettylights-syntax-carriage-return-bg: #cf222e;
--color-prettylights-syntax-string-regexp: #116329;
--color-prettylights-syntax-markup-list: #3b2300;
--color-prettylights-syntax-markup-heading: #0550ae;
--color-prettylights-syntax-markup-italic: #24292f;
--color-prettylights-syntax-markup-bold: #24292f;
--color-prettylights-syntax-markup-deleted-text: #82071e;
--color-prettylights-syntax-markup-deleted-bg: #FFEBE9;
--color-prettylights-syntax-markup-inserted-text: #116329;
--color-prettylights-syntax-markup-inserted-bg: #dafbe1;
--color-prettylights-syntax-markup-changed-text: #953800;
--color-prettylights-syntax-markup-changed-bg: #ffd8b5;
--color-prettylights-syntax-markup-ignored-text: #eaeef2;
--color-prettylights-syntax-markup-ignored-bg: #0550ae;
--color-prettylights-syntax-meta-diff-range: #8250df;
--color-prettylights-syntax-brackethighlighter-angle: #57606a;
--color-prettylights-syntax-sublimelinter-gutter-mark: #8c959f;
--color-prettylights-syntax-constant-other-reference-link: #0a3069;
--color-fg-default: #24292f;
--color-fg-muted: #57606a;
--color-fg-subtle: #6e7781;
--color-canvas-default: #ffffff;
--color-canvas-subtle: #f6f8fa;
--color-border-default: #d0d7de;
--color-border-muted: hsla(210,18%,87%,1);
--color-neutral-muted: rgba(175,184,193,0.2);
--color-accent-fg: #0969da;
--color-accent-emphasis: #0969da;
--color-danger-fg: #cf222e;
}
}
:root {
color: var(--color-fg-default);
background-color: var(--color-canvas-default);
}
.markdownroot {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
margin: 0;
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";
font-size: 16px;
line-height: 1.5;
word-wrap: break-word;
}
.markdownroot .octicon {
display: inline-block;
fill: currentColor;
vertical-align: text-bottom;
}
.markdownroot h1:hover .anchor .octicon-link:before,
.markdownroot h2:hover .anchor .octicon-link:before,
.markdownroot h3:hover .anchor .octicon-link:before,
.markdownroot h4:hover .anchor .octicon-link:before,
.markdownroot h5:hover .anchor .octicon-link:before,
.markdownroot h6:hover .anchor .octicon-link:before {
width: 16px;
height: 16px;
content: ' ';
display: inline-block;
background-color: currentColor;
-webkit-mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' aria-hidden='true'><path fill-rule='evenodd' d='M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z'></path></svg>");
mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' aria-hidden='true'><path fill-rule='evenodd' d='M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z'></path></svg>");
}
.markdownroot details,
.markdownroot figcaption,
.markdownroot figure {
display: block;
}
.markdownroot summary {
display: list-item;
}
.markdownroot a {
background-color: transparent;
color: var(--color-accent-fg);
text-decoration: none;
}
.markdownroot a:active,
.markdownroot a:hover {
outline-width: 0;
}
.markdownroot abbr[title] {
border-bottom: none;
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
.markdownroot b,
.markdownroot strong {
font-weight: 600;
}
.markdownroot dfn {
font-style: italic;
}
.markdownroot h1 {
margin: .67em 0;
font-weight: 600;
padding-bottom: .3em;
font-size: 2em;
border-bottom: 1px solid var(--color-border-muted);
}
.markdownroot mark {
background-color: #ff0;
color: var(--color-text-primary);
}
.markdownroot small {
font-size: 90%;
}
.markdownroot sub,
.markdownroot sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
.markdownroot sub {
bottom: -0.25em;
}
.markdownroot sup {
top: -0.5em;
}
.markdownroot img {
border-style: none;
max-width: 100%;
box-sizing: content-box;
background-color: var(--color-canvas-default);
}
.markdownroot code,
.markdownroot kbd,
.markdownroot pre,
.markdownroot samp {
font-family: monospace,monospace;
font-size: 1em;
}
.markdownroot figure {
margin: 1em 40px;
}
.markdownroot hr {
box-sizing: content-box;
overflow: hidden;
background: transparent;
border-bottom: 1px solid var(--color-border-muted);
height: .25em;
padding: 0;
margin: 24px 0;
background-color: var(--color-border-default);
border: 0;
}
.markdownroot html [type=button],
.markdownroot [type=reset],
.markdownroot [type=submit] {
-webkit-appearance: button;
}
.markdownroot [type=button]::-moz-focus-inner,
.markdownroot [type=reset]::-moz-focus-inner,
.markdownroot [type=submit]::-moz-focus-inner {
border-style: none;
padding: 0;
}
.markdownroot [type=button]:-moz-focusring,
.markdownroot [type=reset]:-moz-focusring,
.markdownroot [type=submit]:-moz-focusring {
outline: 1px dotted ButtonText;
}
.markdownroot [type=checkbox],
.markdownroot [type=radio] {
box-sizing: border-box;
padding: 0;
}
.markdownroot [type=number]::-webkit-inner-spin-button,
.markdownroot [type=number]::-webkit-outer-spin-button {
height: auto;
}
.markdownroot [type=search] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
.markdownroot [type=search]::-webkit-search-cancel-button,
.markdownroot [type=search]::-webkit-search-decoration {
-webkit-appearance: none;
}
.markdownroot ::-webkit-input-placeholder {
color: inherit;
opacity: .54;
}
.markdownroot ::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit;
}
.markdownroot a:hover {
text-decoration: underline;
}
.markdownroot hr::before {
display: table;
content: "";
}
.markdownroot hr::after {
display: table;
clear: both;
content: "";
}
.markdownroot table {
border-spacing: 0;
border-collapse: collapse;
display: block;
width: max-content;
max-width: 100%;
overflow: auto;
}
.markdownroot td,
.markdownroot th {
padding: 0;
}
.markdownroot details summary {
cursor: pointer;
}
.markdownroot details:not([open])>*:not(summary) {
display: none !important;
}
.markdownroot kbd {
display: inline-block;
padding: 3px 5px;
font: 11px ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;
line-height: 10px;
color: var(--color-fg-default);
vertical-align: middle;
background-color: var(--color-canvas-subtle);
border: solid 1px var(--color-neutral-muted);
border-bottom-color: var(--color-neutral-muted);
border-radius: 6px;
box-shadow: inset 0 -1px 0 var(--color-neutral-muted);
}
.markdownroot h1,
.markdownroot h2,
.markdownroot h3,
.markdownroot h4,
.markdownroot h5,
.markdownroot h6 {
margin-top: 24px;
margin-bottom: 16px;
font-weight: 600;
line-height: 1.25;
}
.markdownroot h2 {
font-weight: 600;
padding-bottom: .3em;
font-size: 1.5em;
border-bottom: 1px solid var(--color-border-muted);
}
.markdownroot h3 {
font-weight: 600;
font-size: 1.25em;
}
.markdownroot h4 {
font-weight: 600;
font-size: 1em;
}
.markdownroot h5 {
font-weight: 600;
font-size: .875em;
}
.markdownroot h6 {
font-weight: 600;
font-size: .85em;
color: var(--color-fg-muted);
}
.markdownroot p {
margin-top: 0;
margin-bottom: 10px;
}
.markdownroot blockquote {
margin: 0;
padding: 0 1em;
color: var(--color-fg-muted);
border-left: .25em solid var(--color-border-default);
}
.markdownroot ul,
.markdownroot ol {
margin-top: 0;
margin-bottom: 0;
padding-left: 2em;
}
.markdownroot ol ol,
.markdownroot ul ol {
list-style-type: lower-roman;
}
.markdownroot ul ul ol,
.markdownroot ul ol ol,
.markdownroot ol ul ol,
.markdownroot ol ol ol {
list-style-type: lower-alpha;
}
.markdownroot dd {
margin-left: 0;
}
.markdownroot tt,
.markdownroot code {
font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;
font-size: 12px;
}
.markdownroot pre {
margin-top: 0;
margin-bottom: 0;
font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;
font-size: 12px;
word-wrap: normal;
}
.markdownroot :-ms-input-placeholder {
color: var(--color-fg-subtle);
opacity: 1;
}
.markdownroot ::-ms-input-placeholder {
color: var(--color-fg-subtle);
opacity: 1;
}
.markdownroot ::placeholder {
color: var(--color-fg-subtle);
opacity: 1;
}
.markdownroot .pl-c {
color: var(--color-prettylights-syntax-comment);
}
.markdownroot .pl-c1,
.markdownroot .pl-s .pl-v {
color: var(--color-prettylights-syntax-constant);
}
.markdownroot .pl-e,
.markdownroot .pl-en {
color: var(--color-prettylights-syntax-entity);
}
.markdownroot .pl-smi,
.markdownroot .pl-s .pl-s1 {
color: var(--color-prettylights-syntax-storage-modifier-import);
}
.markdownroot .pl-ent {
color: var(--color-prettylights-syntax-entity-tag);
}
.markdownroot .pl-k {
color: var(--color-prettylights-syntax-keyword);
}
.markdownroot .pl-s,
.markdownroot .pl-pds,
.markdownroot .pl-s .pl-pse .pl-s1,
.markdownroot .pl-sr,
.markdownroot .pl-sr .pl-cce,
.markdownroot .pl-sr .pl-sre,
.markdownroot .pl-sr .pl-sra {
color: var(--color-prettylights-syntax-string);
}
.markdownroot .pl-v,
.markdownroot .pl-smw {
color: var(--color-prettylights-syntax-variable);
}
.markdownroot .pl-bu {
color: var(--color-prettylights-syntax-brackethighlighter-unmatched);
}
.markdownroot .pl-ii {
color: var(--color-prettylights-syntax-invalid-illegal-text);
background-color: var(--color-prettylights-syntax-invalid-illegal-bg);
}
.markdownroot .pl-c2 {
color: var(--color-prettylights-syntax-carriage-return-text);
background-color: var(--color-prettylights-syntax-carriage-return-bg);
}
.markdownroot .pl-sr .pl-cce {
font-weight: bold;
color: var(--color-prettylights-syntax-string-regexp);
}
.markdownroot .pl-ml {
color: var(--color-prettylights-syntax-markup-list);
}
.markdownroot .pl-mh,
.markdownroot .pl-mh .pl-en,
.markdownroot .pl-ms {
font-weight: bold;
color: var(--color-prettylights-syntax-markup-heading);
}
.markdownroot .pl-mi {
font-style: italic;
color: var(--color-prettylights-syntax-markup-italic);
}
.markdownroot .pl-mb {
font-weight: bold;
color: var(--color-prettylights-syntax-markup-bold);
}
.markdownroot .pl-md {
color: var(--color-prettylights-syntax-markup-deleted-text);
background-color: var(--color-prettylights-syntax-markup-deleted-bg);
}
.markdownroot .pl-mi1 {
color: var(--color-prettylights-syntax-markup-inserted-text);
background-color: var(--color-prettylights-syntax-markup-inserted-bg);
}
.markdownroot .pl-mc {
color: var(--color-prettylights-syntax-markup-changed-text);
background-color: var(--color-prettylights-syntax-markup-changed-bg);
}
.markdownroot .pl-mi2 {
color: var(--color-prettylights-syntax-markup-ignored-text);
background-color: var(--color-prettylights-syntax-markup-ignored-bg);
}
.markdownroot .pl-mdr {
font-weight: bold;
color: var(--color-prettylights-syntax-meta-diff-range);
}
.markdownroot .pl-ba {
color: var(--color-prettylights-syntax-brackethighlighter-angle);
}
.markdownroot .pl-sg {
color: var(--color-prettylights-syntax-sublimelinter-gutter-mark);
}
.markdownroot .pl-corl {
text-decoration: underline;
color: var(--color-prettylights-syntax-constant-other-reference-link);
}
.markdownroot [data-catalyst] {
display: block;
}
.markdownroot g-emoji {
font-family: "Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";
font-size: 1em;
font-style: normal !important;
font-weight: 400;
line-height: 1;
vertical-align: -0.075em;
}
.markdownroot g-emoji img {
width: 1em;
height: 1em;
}
.markdownroot::before {
display: table;
content: "";
}
.markdownroot::after {
display: table;
clear: both;
content: "";
}
.markdownroot>*:first-child {
margin-top: 0 !important;
}
.markdownroot>*:last-child {
margin-bottom: 0 !important;
}
.markdownroot a:not([href]) {
color: inherit;
text-decoration: none;
}
.markdownroot .absent {
color: var(--color-danger-fg);
}
.markdownroot .anchor {
float: left;
padding-right: 4px;
margin-left: -20px;
line-height: 1;
}
.markdownroot .anchor:focus {
outline: none;
}
.markdownroot p,
.markdownroot blockquote,
.markdownroot ul,
.markdownroot ol,
.markdownroot dl,
.markdownroot table,
.markdownroot pre,
.markdownroot details {
margin-top: 0;
margin-bottom: 16px;
}
.markdownroot blockquote>:first-child {
margin-top: 0;
}
.markdownroot blockquote>:last-child {
margin-bottom: 0;
}
.markdownroot sup>a::before {
content: "[";
}
.markdownroot sup>a::after {
content: "]";
}
.markdownroot h1 .octicon-link,
.markdownroot h2 .octicon-link,
.markdownroot h3 .octicon-link,
.markdownroot h4 .octicon-link,
.markdownroot h5 .octicon-link,
.markdownroot h6 .octicon-link {
color: var(--color-fg-default);
vertical-align: middle;
visibility: hidden;
}
.markdownroot h1:hover .anchor,
.markdownroot h2:hover .anchor,
.markdownroot h3:hover .anchor,
.markdownroot h4:hover .anchor,
.markdownroot h5:hover .anchor,
.markdownroot h6:hover .anchor {
text-decoration: none;
}
.markdownroot h1:hover .anchor .octicon-link,
.markdownroot h2:hover .anchor .octicon-link,
.markdownroot h3:hover .anchor .octicon-link,
.markdownroot h4:hover .anchor .octicon-link,
.markdownroot h5:hover .anchor .octicon-link,
.markdownroot h6:hover .anchor .octicon-link {
visibility: visible;
}
.markdownroot h1 tt,
.markdownroot h1 code,
.markdownroot h2 tt,
.markdownroot h2 code,
.markdownroot h3 tt,
.markdownroot h3 code,
.markdownroot h4 tt,
.markdownroot h4 code,
.markdownroot h5 tt,
.markdownroot h5 code,
.markdownroot h6 tt,
.markdownroot h6 code {
padding: 0 .2em;
font-size: inherit;
}
.markdownroot ul.no-list,
.markdownroot ol.no-list {
padding: 0;
list-style-type: none;
}
.markdownroot ol[type="1"] {
list-style-type: decimal;
}
.markdownroot ol[type=a] {
list-style-type: lower-alpha;
}
.markdownroot ol[type=i] {
list-style-type: lower-roman;
}
.markdownroot div>ol:not([type]) {
list-style-type: decimal;
}
.markdownroot ul ul,
.markdownroot ul ol,
.markdownroot ol ol,
.markdownroot ol ul {
margin-top: 0;
margin-bottom: 0;
}
.markdownroot li>p {
margin-top: 16px;
}
.markdownroot li+li {
margin-top: .25em;
}
.markdownroot dl {
padding: 0;
}
.markdownroot dl dt {
padding: 0;
margin-top: 16px;
font-size: 1em;
font-style: italic;
font-weight: 600;
}
.markdownroot dl dd {
padding: 0 16px;
margin-bottom: 16px;
}
.markdownroot table th {
font-weight: 600;
}
.markdownroot table th,
.markdownroot table td {
padding: 6px 13px;
border: 1px solid var(--color-border-default);
}
.markdownroot table tr {
background-color: var(--color-canvas-default);
border-top: 1px solid var(--color-border-muted);
}
.markdownroot table tr:nth-child(2n) {
background-color: var(--color-canvas-subtle);
}
.markdownroot table img {
background-color: transparent;
}
.markdownroot img[align=right] {
padding-left: 20px;
}
.markdownroot img[align=left] {
padding-right: 20px;
}
.markdownroot .emoji {
max-width: none;
vertical-align: text-top;
background-color: transparent;
}
.markdownroot span.frame {
display: block;
overflow: hidden;
}
.markdownroot span.frame>span {
display: block;
float: left;
width: auto;
padding: 7px;
margin: 13px 0 0;
overflow: hidden;
border: 1px solid var(--color-border-default);
}
.markdownroot span.frame span img {
display: block;
float: left;
}
.markdownroot span.frame span span {
display: block;
padding: 5px 0 0;
clear: both;
color: var(--color-fg-default);
}
.markdownroot span.align-center {
display: block;
overflow: hidden;
clear: both;
}
.markdownroot span.align-center>span {
display: block;
margin: 13px auto 0;
overflow: hidden;
text-align: center;
}
.markdownroot span.align-center span img {
margin: 0 auto;
text-align: center;
}
.markdownroot span.align-right {
display: block;
overflow: hidden;
clear: both;
}
.markdownroot span.align-right>span {
display: block;
margin: 13px 0 0;
overflow: hidden;
text-align: right;
}
.markdownroot span.align-right span img {
margin: 0;
text-align: right;
}
.markdownroot span.float-left {
display: block;
float: left;
margin-right: 13px;
overflow: hidden;
}
.markdownroot span.float-left span {
margin: 13px 0 0;
}
.markdownroot span.float-right {
display: block;
float: right;
margin-left: 13px;
overflow: hidden;
}
.markdownroot span.float-right>span {
display: block;
margin: 13px auto 0;
overflow: hidden;
text-align: right;
}
.markdownroot code,
.markdownroot tt {
padding: .2em .4em;
margin: 0;
font-size: 85%;
background-color: var(--color-neutral-muted);
border-radius: 6px;
}
.markdownroot code br,
.markdownroot tt br {
display: none;
}
.markdownroot del code {
text-decoration: inherit;
}
.markdownroot pre code {
font-size: 100%;
}
.markdownroot pre>code {
padding: 0;
margin: 0;
word-break: normal;
white-space: pre;
background: transparent;
border: 0;
}
.markdownroot .highlight {
margin-bottom: 16px;
}
.markdownroot .highlight pre {
margin-bottom: 0;
word-break: normal;
}
.markdownroot .highlight pre,
.markdownroot pre {
padding: 16px;
overflow: auto;
font-size: 85%;
line-height: 1.45;
background-color: var(--color-canvas-subtle);
border-radius: 6px;
}
.markdownroot pre code,
.markdownroot pre tt {
display: inline;
max-width: auto;
padding: 0;
margin: 0;
overflow: visible;
line-height: inherit;
word-wrap: normal;
background-color: transparent;
border: 0;
}
.markdownroot .csv-data td,
.markdownroot .csv-data th {
padding: 5px;
overflow: hidden;
font-size: 12px;
line-height: 1;
text-align: left;
white-space: nowrap;
}
.markdownroot .csv-data .blob-num {
padding: 10px 8px 9px;
text-align: right;
background: var(--color-canvas-default);
border: 0;
}
.markdownroot .csv-data tr {
border-top: 0;
}
.markdownroot .csv-data th {
font-weight: 600;
background: var(--color-canvas-subtle);
border-top: 0;
}
.markdownroot .footnotes {
font-size: 12px;
color: var(--color-fg-muted);
border-top: 1px solid var(--color-border-default);
}
.markdownroot .footnotes ol {
padding-left: 16px;
}
.markdownroot .footnotes li {
position: relative;
}
.markdownroot .footnotes li:target::before {
position: absolute;
top: -8px;
right: -8px;
bottom: -8px;
left: -24px;
pointer-events: none;
content: "";
border: 2px solid var(--color-accent-emphasis);
border-radius: 6px;
}
.markdownroot .footnotes li:target {
color: var(--color-fg-default);
}
.markdownroot .footnotes .data-footnote-backref g-emoji {
font-family: monospace;
}
.markdownroot [hidden] {
display: none !important;
}
.markdownroot ::-webkit-calendar-picker-indicator {
filter: invert(50%);
}
</style><style media="print" id="__markdown-viewer__md_print_css">:root {
background: white;
color: black;
font: 8pt serif;
line-height: 1.5;
vertical-align: baseline;
}
body, .markdownRoot {
max-width: 100%;
margin: 0;
padding: 0;
}
* {
margin: 0;
padding: 0;
}
p {
margin: 0 0 .3rem 0;
}
ul, ol {
padding-left: 2em
}
a {
color: #666;
text-decoration: underline;
}
a[href]:after {
content: ' (' attr(href) ')';
font-size: 1rem;
}
img {
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
padding: 0 0 .2em 0;
}
h1, h2, h3, h4, h5, h6, dt {
/* break-after: avoid; */
break-inside: avoid;
}
/* Hack to fix break-after: https://stackoverflow.com/a/53742871 */
h1:after, h2:after, h3:after, h4:after, h5:after, h6:after, dt:after {
content: "";
display: block;
/* .2em of padding + 3em for 2 full lines */
height: 3.2em;
margin-bottom: -3.2em;
}
blockquote, pre, li, dt, dd {
break-inside: avoid;
}
</style><style id="__markdown-viewer__hljs_css">
@media (prefers-color-scheme: light) { .hljs-comment,.hljs-quote{color:#696969}.hljs-deletion,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#d91e18}.hljs-built_in,.hljs-builtin-name,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-type{color:#aa5d00}.hljs-attribute{color:#aa5d00}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:green}.hljs-section,.hljs-title{color:#007faa}.hljs-keyword,.hljs-selector-tag{color:#7928a1}.hljs{display:block;overflow-x:auto;background:#fefefe;color:#545454;padding:.5em}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}@media screen and (-ms-high-contrast:active){.hljs-addition,.hljs-attribute,.hljs-built_in,.hljs-builtin-name,.hljs-bullet,.hljs-comment,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-quote,.hljs-string,.hljs-symbol,.hljs-type{color:highlight}.hljs-keyword,.hljs-selector-tag{font-weight:700}} }
@media (prefers-color-scheme: dark) { .hljs-comment,.hljs-quote{color:#d4d0ab}.hljs-deletion,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#ffa07a}.hljs-built_in,.hljs-builtin-name,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-type{color:#f5ab35}.hljs-attribute{color:gold}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:#abe338}.hljs-section,.hljs-title{color:#00e0e0}.hljs-keyword,.hljs-selector-tag{color:#dcc6e0}.hljs{display:block;overflow-x:auto;background:#2b2b2b;color:#f8f8f2;padding:.5em}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}@media screen and (-ms-high-contrast:active){.hljs-addition,.hljs-attribute,.hljs-built_in,.hljs-builtin-name,.hljs-bullet,.hljs-comment,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-quote,.hljs-string,.hljs-symbol,.hljs-type{color:highlight}.hljs-keyword,.hljs-selector-tag{font-weight:700}} }</style><style id="__markdown-viewer__katex_css">@font-face{font-family:KaTeX_AMS;font-style:normal;font-weight:400}@font-face{font-family:KaTeX_Caligraphic;font-style:normal;font-weight:700}@font-face{font-family:KaTeX_Caligraphic;font-style:normal;font-weight:400}@font-face{font-family:KaTeX_Fraktur;font-style:normal;font-weight:700}@font-face{font-family:KaTeX_Fraktur;font-style:normal;font-weight:400}@font-face{font-family:KaTeX_Main;font-style:normal;font-weight:700}@font-face{font-family:KaTeX_Main;font-style:italic;font-weight:700}@font-face{font-family:KaTeX_Main;font-style:italic;font-weight:400}@font-face{font-family:KaTeX_Main;font-style:normal;font-weight:400}@font-face{font-family:KaTeX_Math;font-style:italic;font-weight:700}@font-face{font-family:KaTeX_Math;font-style:italic;font-weight:400}@font-face{font-family:"KaTeX_SansSerif";font-style:normal;font-weight:700}@font-face{font-family:"KaTeX_SansSerif";font-style:italic;font-weight:400}@font-face{font-family:"KaTeX_SansSerif";font-style:normal;font-weight:400}@font-face{font-family:KaTeX_Script;font-style:normal;font-weight:400}@font-face{font-family:KaTeX_Size1;font-style:normal;font-weight:400}@font-face{font-family:KaTeX_Size2;font-style:normal;font-weight:400}@font-face{font-family:KaTeX_Size3;font-style:normal;font-weight:400}@font-face{font-family:KaTeX_Size4;font-style:normal;font-weight:400}@font-face{font-family:KaTeX_Typewriter;font-style:normal;font-weight:400}.katex{text-rendering:auto;font:normal 1.21em KaTeX_Main,Times New Roman,serif;line-height:1.2;text-indent:0}.katex *{-ms-high-contrast-adjust:none!important;border-color:currentColor}.katex .katex-version:after{content:"0.13.13"}.katex .katex-mathml{clip:rect(1px,1px,1px,1px);border:0;height:1px;overflow:hidden;padding:0;position:absolute;width:1px}.katex .katex-html>.newline{display:block}.katex .base{position:relative;white-space:nowrap;width:-webkit-min-content;width:-moz-min-content;width:min-content}.katex .base,.katex .strut{display:inline-block}.katex .textbf{font-weight:700}.katex .textit{font-style:italic}.katex .textrm{font-family:KaTeX_Main}.katex .textsf{font-family:KaTeX_SansSerif}.katex .texttt{font-family:KaTeX_Typewriter}.katex .mathnormal{font-family:KaTeX_Math;font-style:italic}.katex .mathit{font-family:KaTeX_Main;font-style:italic}.katex .mathrm{font-style:normal}.katex .mathbf{font-family:KaTeX_Main;font-weight:700}.katex .boldsymbol{font-family:KaTeX_Math;font-style:italic;font-weight:700}.katex .amsrm,.katex .mathbb,.katex .textbb{font-family:KaTeX_AMS}.katex .mathcal{font-family:KaTeX_Caligraphic}.katex .mathfrak,.katex .textfrak{font-family:KaTeX_Fraktur}.katex .mathtt{font-family:KaTeX_Typewriter}.katex .mathscr,.katex .textscr{font-family:KaTeX_Script}.katex .mathsf,.katex .textsf{font-family:KaTeX_SansSerif}.katex .mathboldsf,.katex .textboldsf{font-family:KaTeX_SansSerif;font-weight:700}.katex .mathitsf,.katex .textitsf{font-family:KaTeX_SansSerif;font-style:italic}.katex .mainrm{font-family:KaTeX_Main;font-style:normal}.katex .vlist-t{border-collapse:collapse;display:inline-table;table-layout:fixed}.katex .vlist-r{display:table-row}.katex .vlist{display:table-cell;position:relative;vertical-align:bottom}.katex .vlist>span{display:block;height:0;position:relative}.katex .vlist>span>span{display:inline-block}.katex .vlist>span>.pstrut{overflow:hidden;width:0}.katex .vlist-t2{margin-right:-2px}.katex .vlist-s{display:table-cell;font-size:1px;min-width:2px;vertical-align:bottom;width:2px}.katex .vbox{align-items:baseline;display:inline-flex;flex-direction:column}.katex .hbox{width:100%}.katex .hbox,.katex .thinbox{display:inline-flex;flex-direction:row}.katex .thinbox{max-width:0;width:0}.katex .msupsub{text-align:left}.katex .mfrac>span>span{text-align:center}.katex .mfrac .frac-line{border-bottom-style:solid;display:inline-block;width:100%}.katex .hdashline,.katex .hline,.katex .mfrac .frac-line,.katex .overline .overline-line,.katex .rule,.katex .underline .underline-line{min-height:1px}.katex .mspace{display:inline-block}.katex .clap,.katex .llap,.katex .rlap{position:relative;width:0}.katex .clap>.inner,.katex .llap>.inner,.katex .rlap>.inner{position:absolute}.katex .clap>.fix,.katex .llap>.fix,.katex .rlap>.fix{display:inline-block}.katex .llap>.inner{right:0}.katex .clap>.inner,.katex .rlap>.inner{left:0}.katex .clap>.inner>span{margin-left:-50%;margin-right:50%}.katex .rule{border:0 solid;display:inline-block;position:relative}.katex .hline,.katex .overline .overline-line,.katex .underline .underline-line{border-bottom-style:solid;display:inline-block;width:100%}.katex .hdashline{border-bottom-style:dashed;display:inline-block;width:100%}.katex .sqrt>.root{margin-left:.27777778em;margin-right:-.55555556em}.katex .fontsize-ensurer.reset-size1.size1,.katex .sizing.reset-size1.size1{font-size:1em}.katex .fontsize-ensurer.reset-size1.size2,.katex .sizing.reset-size1.size2{font-size:1.2em}.katex .fontsize-ensurer.reset-size1.size3,.katex .sizing.reset-size1.size3{font-size:1.4em}.katex .fontsize-ensurer.reset-size1.size4,.katex .sizing.reset-size1.size4{font-size:1.6em}.katex .fontsize-ensurer.reset-size1.size5,.katex .sizing.reset-size1.size5{font-size:1.8em}.katex .fontsize-ensurer.reset-size1.size6,.katex .sizing.reset-size1.size6{font-size:2em}.katex .fontsize-ensurer.reset-size1.size7,.katex .sizing.reset-size1.size7{font-size:2.4em}.katex .fontsize-ensurer.reset-size1.size8,.katex .sizing.reset-size1.size8{font-size:2.88em}.katex .fontsize-ensurer.reset-size1.size9,.katex .sizing.reset-size1.size9{font-size:3.456em}.katex .fontsize-ensurer.reset-size1.size10,.katex .sizing.reset-size1.size10{font-size:4.148em}.katex .fontsize-ensurer.reset-size1.size11,.katex .sizing.reset-size1.size11{font-size:4.976em}.katex .fontsize-ensurer.reset-size2.size1,.katex .sizing.reset-size2.size1{font-size:.83333333em}.katex .fontsize-ensurer.reset-size2.size2,.katex .sizing.reset-size2.size2{font-size:1em}.katex .fontsize-ensurer.reset-size2.size3,.katex .sizing.reset-size2.size3{font-size:1.16666667em}.katex .fontsize-ensurer.reset-size2.size4,.katex .sizing.reset-size2.size4{font-size:1.33333333em}.katex .fontsize-ensurer.reset-size2.size5,.katex .sizing.reset-size2.size5{font-size:1.5em}.katex .fontsize-ensurer.reset-size2.size6,.katex .sizing.reset-size2.size6{font-size:1.66666667em}.katex .fontsize-ensurer.reset-size2.size7,.katex .sizing.reset-size2.size7{font-size:2em}.katex .fontsize-ensurer.reset-size2.size8,.katex .sizing.reset-size2.size8{font-size:2.4em}.katex .fontsize-ensurer.reset-size2.size9,.katex .sizing.reset-size2.size9{font-size:2.88em}.katex .fontsize-ensurer.reset-size2.size10,.katex .sizing.reset-size2.size10{font-size:3.45666667em}.katex .fontsize-ensurer.reset-size2.size11,.katex .sizing.reset-size2.size11{font-size:4.14666667em}.katex .fontsize-ensurer.reset-size3.size1,.katex .sizing.reset-size3.size1{font-size:.71428571em}.katex .fontsize-ensurer.reset-size3.size2,.katex .sizing.reset-size3.size2{font-size:.85714286em}.katex .fontsize-ensurer.reset-size3.size3,.katex .sizing.reset-size3.size3{font-size:1em}.katex .fontsize-ensurer.reset-size3.size4,.katex .sizing.reset-size3.size4{font-size:1.14285714em}.katex .fontsize-ensurer.reset-size3.size5,.katex .sizing.reset-size3.size5{font-size:1.28571429em}.katex .fontsize-ensurer.reset-size3.size6,.katex .sizing.reset-size3.size6{font-size:1.42857143em}.katex .fontsize-ensurer.reset-size3.size7,.katex .sizing.reset-size3.size7{font-size:1.71428571em}.katex .fontsize-ensurer.reset-size3.size8,.katex .sizing.reset-size3.size8{font-size:2.05714286em}.katex .fontsize-ensurer.reset-size3.size9,.katex .sizing.reset-size3.size9{font-size:2.46857143em}.katex .fontsize-ensurer.reset-size3.size10,.katex .sizing.reset-size3.size10{font-size:2.96285714em}.katex .fontsize-ensurer.reset-size3.size11,.katex .sizing.reset-size3.size11{font-size:3.55428571em}.katex .fontsize-ensurer.reset-size4.size1,.katex .sizing.reset-size4.size1{font-size:.625em}.katex .fontsize-ensurer.reset-size4.size2,.katex .sizing.reset-size4.size2{font-size:.75em}.katex .fontsize-ensurer.reset-size4.size3,.katex .sizing.reset-size4.size3{font-size:.875em}.katex .fontsize-ensurer.reset-size4.size4,.katex .sizing.reset-size4.size4{font-size:1em}.katex .fontsize-ensurer.reset-size4.size5,.katex .sizing.reset-size4.size5{font-size:1.125em}.katex .fontsize-ensurer.reset-size4.size6,.katex .sizing.reset-size4.size6{font-size:1.25em}.katex .fontsize-ensurer.reset-size4.size7,.katex .sizing.reset-size4.size7{font-size:1.5em}.katex .fontsize-ensurer.reset-size4.size8,.katex .sizing.reset-size4.size8{font-size:1.8em}.katex .fontsize-ensurer.reset-size4.size9,.katex .sizing.reset-size4.size9{font-size:2.16em}.katex .fontsize-ensurer.reset-size4.size10,.katex .sizing.reset-size4.size10{font-size:2.5925em}.katex .fontsize-ensurer.reset-size4.size11,.katex .sizing.reset-size4.size11{font-size:3.11em}.katex .fontsize-ensurer.reset-size5.size1,.katex .sizing.reset-size5.size1{font-size:.55555556em}.katex .fontsize-ensurer.reset-size5.size2,.katex .sizing.reset-size5.size2{font-size:.66666667em}.katex .fontsize-ensurer.reset-size5.size3,.katex .sizing.reset-size5.size3{font-size:.77777778em}.katex .fontsize-ensurer.reset-size5.size4,.katex .sizing.reset-size5.size4{font-size:.88888889em}.katex .fontsize-ensurer.reset-size5.size5,.katex .sizing.reset-size5.size5{font-size:1em}.katex .fontsize-ensurer.reset-size5.size6,.katex .sizing.reset-size5.size6{font-size:1.11111111em}.katex .fontsize-ensurer.reset-size5.size7,.katex .sizing.reset-size5.size7{font-size:1.33333333em}.katex .fontsize-ensurer.reset-size5.size8,.katex .sizing.reset-size5.size8{font-size:1.6em}.katex .fontsize-ensurer.reset-size5.size9,.katex .sizing.reset-size5.size9{font-size:1.92em}.katex .fontsize-ensurer.reset-size5.size10,.katex .sizing.reset-size5.size10{font-size:2.30444444em}.katex .fontsize-ensurer.reset-size5.size11,.katex .sizing.reset-size5.size11{font-size:2.76444444em}.katex .fontsize-ensurer.reset-size6.size1,.katex .sizing.reset-size6.size1{font-size:.5em}.katex .fontsize-ensurer.reset-size6.size2,.katex .sizing.reset-size6.size2{font-size:.6em}.katex .fontsize-ensurer.reset-size6.size3,.katex .sizing.reset-size6.size3{font-size:.7em}.katex .fontsize-ensurer.reset-size6.size4,.katex .sizing.reset-size6.size4{font-size:.8em}.katex .fontsize-ensurer.reset-size6.size5,.katex .sizing.reset-size6.size5{font-size:.9em}.katex .fontsize-ensurer.reset-size6.size6,.katex .sizing.reset-size6.size6{font-size:1em}.katex .fontsize-ensurer.reset-size6.size7,.katex .sizing.reset-size6.size7{font-size:1.2em}.katex .fontsize-ensurer.reset-size6.size8,.katex .sizing.reset-size6.size8{font-size:1.44em}.katex .fontsize-ensurer.reset-size6.size9,.katex .sizing.reset-size6.size9{font-size:1.728em}.katex .fontsize-ensurer.reset-size6.size10,.katex .sizing.reset-size6.size10{font-size:2.074em}.katex .fontsize-ensurer.reset-size6.size11,.katex .sizing.reset-size6.size11{font-size:2.488em}.katex .fontsize-ensurer.reset-size7.size1,.katex .sizing.reset-size7.size1{font-size:.41666667em}.katex .fontsize-ensurer.reset-size7.size2,.katex .sizing.reset-size7.size2{font-size:.5em}.katex .fontsize-ensurer.reset-size7.size3,.katex .sizing.reset-size7.size3{font-size:.58333333em}.katex .fontsize-ensurer.reset-size7.size4,.katex .sizing.reset-size7.size4{font-size:.66666667em}.katex .fontsize-ensurer.reset-size7.size5,.katex .sizing.reset-size7.size5{font-size:.75em}.katex .fontsize-ensurer.reset-size7.size6,.katex .sizing.reset-size7.size6{font-size:.83333333em}.katex .fontsize-ensurer.reset-size7.size7,.katex .sizing.reset-size7.size7{font-size:1em}.katex .fontsize-ensurer.reset-size7.size8,.katex .sizing.reset-size7.size8{font-size:1.2em}.katex .fontsize-ensurer.reset-size7.size9,.katex .sizing.reset-size7.size9{font-size:1.44em}.katex .fontsize-ensurer.reset-size7.size10,.katex .sizing.reset-size7.size10{font-size:1.72833333em}.katex .fontsize-ensurer.reset-size7.size11,.katex .sizing.reset-size7.size11{font-size:2.07333333em}.katex .fontsize-ensurer.reset-size8.size1,.katex .sizing.reset-size8.size1{font-size:.34722222em}.katex .fontsize-ensurer.reset-size8.size2,.katex .sizing.reset-size8.size2{font-size:.41666667em}.katex .fontsize-ensurer.reset-size8.size3,.katex .sizing.reset-size8.size3{font-size:.48611111em}.katex .fontsize-ensurer.reset-size8.size4,.katex .sizing.reset-size8.size4{font-size:.55555556em}.katex .fontsize-ensurer.reset-size8.size5,.katex .sizing.reset-size8.size5{font-size:.625em}.katex .fontsize-ensurer.reset-size8.size6,.katex .sizing.reset-size8.size6{font-size:.69444444em}.katex .fontsize-ensurer.reset-size8.size7,.katex .sizing.reset-size8.size7{font-size:.83333333em}.katex .fontsize-ensurer.reset-size8.size8,.katex .sizing.reset-size8.size8{font-size:1em}.katex .fontsize-ensurer.reset-size8.size9,.katex .sizing.reset-size8.size9{font-size:1.2em}.katex .fontsize-ensurer.reset-size8.size10,.katex .sizing.reset-size8.size10{font-size:1.44027778em}.katex .fontsize-ensurer.reset-size8.size11,.katex .sizing.reset-size8.size11{font-size:1.72777778em}.katex .fontsize-ensurer.reset-size9.size1,.katex .sizing.reset-size9.size1{font-size:.28935185em}.katex .fontsize-ensurer.reset-size9.size2,.katex .sizing.reset-size9.size2{font-size:.34722222em}.katex .fontsize-ensurer.reset-size9.size3,.katex .sizing.reset-size9.size3{font-size:.40509259em}.katex .fontsize-ensurer.reset-size9.size4,.katex .sizing.reset-size9.size4{font-size:.46296296em}.katex .fontsize-ensurer.reset-size9.size5,.katex .sizing.reset-size9.size5{font-size:.52083333em}.katex .fontsize-ensurer.reset-size9.size6,.katex .sizing.reset-size9.size6{font-size:.5787037em}.katex .fontsize-ensurer.reset-size9.size7,.katex .sizing.reset-size9.size7{font-size:.69444444em}.katex .fontsize-ensurer.reset-size9.size8,.katex .sizing.reset-size9.size8{font-size:.83333333em}.katex .fontsize-ensurer.reset-size9.size9,.katex .sizing.reset-size9.size9{font-size:1em}.katex .fontsize-ensurer.reset-size9.size10,.katex .sizing.reset-size9.size10{font-size:1.20023148em}.katex .fontsize-ensurer.reset-size9.size11,.katex .sizing.reset-size9.size11{font-size:1.43981481em}.katex .fontsize-ensurer.reset-size10.size1,.katex .sizing.reset-size10.size1{font-size:.24108004em}.katex .fontsize-ensurer.reset-size10.size2,.katex .sizing.reset-size10.size2{font-size:.28929605em}.katex .fontsize-ensurer.reset-size10.size3,.katex .sizing.reset-size10.size3{font-size:.33751205em}.katex .fontsize-ensurer.reset-size10.size4,.katex .sizing.reset-size10.size4{font-size:.38572806em}.katex .fontsize-ensurer.reset-size10.size5,.katex .sizing.reset-size10.size5{font-size:.43394407em}.katex .fontsize-ensurer.reset-size10.size6,.katex .sizing.reset-size10.size6{font-size:.48216008em}.katex .fontsize-ensurer.reset-size10.size7,.katex .sizing.reset-size10.size7{font-size:.57859209em}.katex .fontsize-ensurer.reset-size10.size8,.katex .sizing.reset-size10.size8{font-size:.69431051em}.katex .fontsize-ensurer.reset-size10.size9,.katex .sizing.reset-size10.size9{font-size:.83317261em}.katex .fontsize-ensurer.reset-size10.size10,.katex .sizing.reset-size10.size10{font-size:1em}.katex .fontsize-ensurer.reset-size10.size11,.katex .sizing.reset-size10.size11{font-size:1.19961427em}.katex .fontsize-ensurer.reset-size11.size1,.katex .sizing.reset-size11.size1{font-size:.20096463em}.katex .fontsize-ensurer.reset-size11.size2,.katex .sizing.reset-size11.size2{font-size:.24115756em}.katex .fontsize-ensurer.reset-size11.size3,.katex .sizing.reset-size11.size3{font-size:.28135048em}.katex .fontsize-ensurer.reset-size11.size4,.katex .sizing.reset-size11.size4{font-size:.32154341em}.katex .fontsize-ensurer.reset-size11.size5,.katex .sizing.reset-size11.size5{font-size:.36173633em}.katex .fontsize-ensurer.reset-size11.size6,.katex .sizing.reset-size11.size6{font-size:.40192926em}.katex .fontsize-ensurer.reset-size11.size7,.katex .sizing.reset-size11.size7{font-size:.48231511em}.katex .fontsize-ensurer.reset-size11.size8,.katex .sizing.reset-size11.size8{font-size:.57877814em}.katex .fontsize-ensurer.reset-size11.size9,.katex .sizing.reset-size11.size9{font-size:.69453376em}.katex .fontsize-ensurer.reset-size11.size10,.katex .sizing.reset-size11.size10{font-size:.83360129em}.katex .fontsize-ensurer.reset-size11.size11,.katex .sizing.reset-size11.size11{font-size:1em}.katex .delimsizing.size1{font-family:KaTeX_Size1}.katex .delimsizing.size2{font-family:KaTeX_Size2}.katex .delimsizing.size3{font-family:KaTeX_Size3}.katex .delimsizing.size4{font-family:KaTeX_Size4}.katex .delimsizing.mult .delim-size1>span{font-family:KaTeX_Size1}.katex .delimsizing.mult .delim-size4>span{font-family:KaTeX_Size4}.katex .nulldelimiter{display:inline-block;width:.12em}.katex .delimcenter,.katex .op-symbol{position:relative}.katex .op-symbol.small-op{font-family:KaTeX_Size1}.katex .op-symbol.large-op{font-family:KaTeX_Size2}.katex .accent>.vlist-t,.katex .op-limits>.vlist-t{text-align:center}.katex .accent .accent-body{position:relative}.katex .accent .accent-body:not(.accent-full){width:0}.katex .overlay{display:block}.katex .mtable .vertical-separator{display:inline-block;min-width:1px}.katex .mtable .arraycolsep{display:inline-block}.katex .mtable .col-align-c>.vlist-t{text-align:center}.katex .mtable .col-align-l>.vlist-t{text-align:left}.katex .mtable .col-align-r>.vlist-t{text-align:right}.katex .svg-align{text-align:left}.katex svg{fill:currentColor;stroke:currentColor;fill-rule:nonzero;fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:block;height:inherit;position:absolute;width:100%}.katex svg path{stroke:none}.katex img{border-style:none;max-height:none;max-width:none;min-height:0;min-width:0}.katex .stretchy{display:block;overflow:hidden;position:relative;width:100%}.katex .stretchy:after,.katex .stretchy:before{content:""}.katex .hide-tail{overflow:hidden;position:relative;width:100%}.katex .halfarrow-left{left:0;overflow:hidden;position:absolute;width:50.2%}.katex .halfarrow-right{overflow:hidden;position:absolute;right:0;width:50.2%}.katex .brace-left{left:0;overflow:hidden;position:absolute;width:25.1%}.katex .brace-center{left:25%;overflow:hidden;position:absolute;width:50%}.katex .brace-right{overflow:hidden;position:absolute;right:0;width:25.1%}.katex .x-arrow-pad{padding:0 .5em}.katex .cd-arrow-pad{padding:0 .55556em 0 .27778em}.katex .mover,.katex .munder,.katex .x-arrow{text-align:center}.katex .boxpad{padding:0 .3em}.katex .fbox,.katex .fcolorbox{border:.04em solid;box-sizing:border-box}.katex .cancel-pad{padding:0 .2em}.katex .cancel-lap{margin-left:-.2em;margin-right:-.2em}.katex .sout{border-bottom-style:solid;border-bottom-width:.08em}.katex .angl{border-right:.049em solid;border-top:.049em solid;box-sizing:border-box;margin-right:.03889em}.katex .anglpad{padding:0 .03889em}.katex .eqn-num:before{content:"(" counter(katexEqnNo) ")";counter-increment:katexEqnNo}.katex .mml-eqn-num:before{content:"(" counter(mmlEqnNo) ")";counter-increment:mmlEqnNo}.katex .mtr-glue{width:50%}.katex .cd-vert-arrow{display:inline-block;position:relative}.katex .cd-label-left{display:inline-block;position:absolute;right:calc(50% + .3em);text-align:left}.katex .cd-label-right{display:inline-block;left:calc(50% + .3em);position:absolute;text-align:right}.katex-display{display:block;margin:1em 0;text-align:center}.katex-display>.katex{display:block;text-align:center;white-space:nowrap}.katex-display>.katex>.katex-html{display:block;position:relative}.katex-display>.katex>.katex-html>.tag{position:absolute;right:0}.katex-display.leqno>.katex>.katex-html>.tag{left:0;right:auto}.katex-display.fleqn>.katex{padding-left:2em;text-align:left}body{counter-reset:katexEqnNo mmlEqnNo}
</style><style id="__markdown-viewer__texmath_css">/* style for html inside of browsers */
.katex { font-size: 1em !important; } /* align KaTeX font-size to surrounding text */
eq { display: inline-block; }
eqn { display: block}
section.eqno {
display: flex;
flex-direction: row;
align-content: space-between;
align-items: center;
}
section.eqno > eqn {
width: 100%;
margin-left: 3em;
}
section.eqno > span {
width:3em;
text-align:right;
}
</style><style id="__markdown-viewer__menu_css">/* Style for the menu, possible positions/visibility */
#__markdown-viewer__tools {
margin:0;
padding:0;
background:#555;
color:#eee;
box-shadow:0 -1px rgba(0,0,0,.5) inset;
border-radius: .5em;
max-width: 25%;
min-width: 2.8em;
min-height: 3em;
z-index: 2147483647;
position: relative;
}
#__markdown-viewer__tools.floating {
float:right;
margin: 0 0 .5em .5em;
}
#__markdown-viewer__tools.fixed {
max-height: calc(100vh - 1em);
overflow-y: auto;
position:fixed;
top:.5em;
right:1em;
}
#__markdown-viewer__tools.hidden {
display:none;
}
@media print {
#__markdown-viewer__tools {
display: none;
}
}
#__markdown-viewer__tools a[href]:after {
content: "";
}
/* Style for the menu top */
label[for=__markdown-viewer__show-tools] {
display:block;
padding:0 18px 0 12px;
line-height:3em;
background:#333;
cursor:pointer;
border-radius: .5em;
min-height: 3em;
width: .9em;
position: absolute;
right: 0;
}
input#__markdown-viewer__show-tools:not(:checked) ~ label {
margin-left: -.9em;
}
input#__markdown-viewer__show-tools:checked ~ label {
}
label[for=__markdown-viewer__show-tools]:before{
}
label[for=__markdown-viewer__show-tools]:after {
content:"";
display:inline-block;
float:right;
margin-top:1.5em;
right:5px;
width:0;
height:0;
border-style: solid;
border-color: rgba(255,255,255,.5) transparent;
border-width: 4px 4px 0 4px;
transition:border-bottom .1s, border-top .1s .1s;
}
input#__markdown-viewer__show-tools:checked ~ label:after {
border-top-width:0;
border-bottom-width:4px;
transition:border-top .1s, border-bottom .1s .1s;
}
/* hide the input that tracks the menu's visibility */
input#__markdown-viewer__show-tools {
display:none;
}
/* style, and hide/show menu items based on the input being checked */
#__markdown-viewer__tools > .toggleable {
overflow:hidden;
transition-property:max-height, max-width, padding-top, padding-bottom, margin-top, margin-bottom;
transition-duration:0.5s;
}
input#__markdown-viewer__show-tools:checked ~ .toggleable {
/* maxes should be 'none' or infinite values, however those are not aniimatable, so just put something big enough. */
max-width: 2000px;
max-height: 2000px;
overflow-y: auto;
transition-timing-function:ease-in;
}
input#__markdown-viewer__show-tools:not(:checked) ~ .toggleable {
max-height:0;
max-width:0;
transition-timing-function:ease-out;
padding-top:0;
padding-bottom:0;
margin-top:0;
margin-bottom:0;
}
/* style the table of contents and its items */
#__markdown-viewer__toc {
display:block;
padding: .5em;
border:0;
}
#__markdown-viewer__toc::before {
content: "Table of Contents";
text-align: center;
display: block;
font-weight: bold;
text-decoration: underline;
}
#__markdown-viewer__toc {
margin-top: 2em;
}
#__markdown-viewer__tools.fixed input#__markdown-viewer__show-tools:checked ~ #__markdown-viewer__toc {
max-height: calc(100vh - 16em);
}
input#__markdown-viewer__show-tools:checked ~ #__markdown-viewer__toc::before {
position: absolute;
top: .5em;
left: 0;
right: 3em;
}
#__markdown-viewer__tools select {
max-width: 40%;
margin: 0 .5em;
}
#__markdown-viewer__tools select,
#__markdown-viewer__toc * {
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
}
#__markdown-viewer__tools a {
color: white;
text-decoration: none;
}
#__markdown-viewer__toc ul {
list-style: inside "• ";
padding: 0;
margin: 0;
}
#__markdown-viewer__toc ul ul {
padding-left: 1.5em;
}
/* Style the "Download Source" button at the end of the menu */
#__markdown-viewer__tools > p {
text-align:center;
padding: 0 .5em;
}
#__markdown-viewer__download {
/* appearance: button; */
-moz-appearance: button !important;
display:inline-block;
text-align: center;
text-decoration:none;
margin: .5em auto;
}
</style><style id="__markdown-viewer__custom_css"></style><meta name="viewport" content="width=device-width, initial-scale=1"><title>Special Chapter: Generating Parsers for F# using Rustlr and Fussless</title></head><body><div id="__markdown-viewer__tools" class="floating"><input type="checkbox" id="__markdown-viewer__show-tools"><label for="__markdown-viewer__show-tools"></label><div id="__markdown-viewer__toc" class="toggleable"><ul><li><a href="#special-chapter-generating-parsers-for-f-using-rustlr-and-fussless">Special Chapter: Generating Parsers for F# using Rustlr and Fussless</a><ul><li><a href="#invoking-the-parser-generator">Invoking the Parser Generator</a></li><li><a href="#grammar-format">GRAMMAR FORMAT</a><ul><li><a href="#valuetype">Valuetype</a></li><li><a href="#declaring-terminal-and-nonterminal-symbols">Declaring Terminal and Nonterminal symbols.</a></li><li><a href="#top-nonterminal">Top Nonterminal</a></li><li><a href="#grammar-production-rules">Grammar Production Rules</a></li><li><a href="#semantic-actions">SEMANTIC ACTIONS</a></li><li><a href="#error-reporting">Error Reporting</a></li><li><a href="#lbox">LBox</a></li><li><a href="#building-and-invoking-the-parser">BUILDING AND INVOKING THE PARSER</a></li><li><a href="#injection-of-top-level-code">Injection of Top Level Code</a></li><li><a href="#lexical-analyzer-directives">Lexical Analyzer Directives</a></li><li><a href="#other-lexattribute-directives">Other lexattribute directives</a></li><li><a href="#operator-precedence-and-associativity-declarations">Operator precedence and associativity declarations.</a></li><li><a href="#error-recovery">Error Recovery</a></li><li><a href="#using-regular-expression-like-operators-etc">Using Regular Expression-Like Operators +, *, ?, etc</a></li><li><a href="#a-more-advanced-example">A More Advanced Example</a></li><li><a href="#using-c">Using C#</a></li></ul></li><li><a href="#automatically-generating-the-abstract-syntax">Automatically Generating the Abstract Syntax</a></li></ul></li></ul></div><p class="toggleable">Pick a markdown and code style:<br><select id="__markdown-viewer__mdselect"><option value="sss">default</option><option value="github">github</option></select><select id="__markdown-viewer__hlselect"><option value="a11y-dark">a11y-dark</option><option value="a11y-light">a11y-light</option><option value="a11y-auto">a11y-auto</option><option value="agate">agate</option><option value="androidstudio">androidstudio</option><option value="an-old-hope">an-old-hope</option><option value="arduino-light">arduino-light</option><option value="arta">arta</option><option value="ascetic">ascetic</option><option value="atelier-cave-dark">atelier-cave-dark</option><option value="atelier-cave-light">atelier-cave-light</option><option value="atelier-cave-auto">atelier-cave-auto</option><option value="atelier-dune-dark">atelier-dune-dark</option><option value="atelier-dune-light">atelier-dune-light</option><option value="atelier-dune-auto">atelier-dune-auto</option><option value="atelier-estuary-dark">atelier-estuary-dark</option><option value="atelier-estuary-light">atelier-estuary-light</option><option value="atelier-estuary-auto">atelier-estuary-auto</option><option value="atelier-forest-dark">atelier-forest-dark</option><option value="atelier-forest-light">atelier-forest-light</option><option value="atelier-forest-auto">atelier-forest-auto</option><option value="atelier-heath-dark">atelier-heath-dark</option><option value="atelier-heath-light">atelier-heath-light</option><option value="atelier-heath-auto">atelier-heath-auto</option><option value="atelier-lakeside-dark">atelier-lakeside-dark</option><option value="atelier-lakeside-light">atelier-lakeside-light</option><option value="atelier-lakeside-auto">atelier-lakeside-auto</option><option value="atelier-plateau-dark">atelier-plateau-dark</option><option value="atelier-plateau-light">atelier-plateau-light</option><option value="atelier-plateau-auto">atelier-plateau-auto</option><option value="atelier-savanna-dark">atelier-savanna-dark</option><option value="atelier-savanna-light">atelier-savanna-light</option><option value="atelier-savanna-auto">atelier-savanna-auto</option><option value="atelier-seaside-dark">atelier-seaside-dark</option><option value="atelier-seaside-light">atelier-seaside-light</option><option value="atelier-seaside-auto">atelier-seaside-auto</option><option value="atelier-sulphurpool-dark">atelier-sulphurpool-dark</option><option value="atelier-sulphurpool-light">atelier-sulphurpool-light</option><option value="atelier-sulphurpool-auto">atelier-sulphurpool-auto</option><option value="atom-one-dark">atom-one-dark</option><option value="atom-one-dark-reasonable">atom-one-dark-reasonable</option><option value="atom-one-light">atom-one-light</option><option value="brown-paper">brown-paper</option><option value="codepen-embed">codepen-embed</option><option value="color-brewer">color-brewer</option><option value="darcula">darcula</option><option value="dark">dark</option><option value="default">default</option><option value="docco">docco</option><option value="dracula">dracula</option><option value="far">far</option><option value="foundation">foundation</option><option value="github">github</option><option value="github-gist">github-gist</option><option value="gml">gml</option><option value="googlecode">googlecode</option><option value="gradient-dark">gradient-dark</option><option value="grayscale">grayscale</option><option value="gruvbox-dark">gruvbox-dark</option><option value="gruvbox-light">gruvbox-light</option><option value="gruvbox-auto">gruvbox-auto</option><option value="hopscotch">hopscotch</option><option value="hybrid">hybrid</option><option value="idea">idea</option><option value="ir-black">ir-black</option><option value="isbl-editor-dark">isbl-editor-dark</option><option value="isbl-editor-light">isbl-editor-light</option><option value="isbl-editor-auto">isbl-editor-auto</option><option value="kimbie">kimbie</option><option value="kimbie">kimbie</option><option value="lightfair">lightfair</option><option value="lioshi">lioshi</option><option value="magula">magula</option><option value="mono-blue">mono-blue</option><option value="monokai">monokai</option><option value="monokai-sublime">monokai-sublime</option><option value="night-owl">night-owl</option><option value="nnfx">nnfx</option><option value="nnfx-dark">nnfx-dark</option><option value="nord">nord</option><option value="obsidian">obsidian</option><option value="ocean">ocean</option><option value="paraiso-dark">paraiso-dark</option><option value="paraiso-light">paraiso-light</option><option value="paraiso-auto">paraiso-auto</option><option value="pojoaque">pojoaque</option><option value="purebasic">purebasic</option><option value="qtcreator_dark">qtcreator_dark</option><option value="qtcreator_light">qtcreator_light</option><option value="qtcreator_auto">qtcreator_auto</option><option value="railscasts">railscasts</option><option value="rainbow">rainbow</option><option value="routeros">routeros</option><option value="school-book">school-book</option><option value="shades-of-purple">shades-of-purple</option><option value="solarized-dark">solarized-dark</option><option value="solarized-light">solarized-light</option><option value="solarized-auto">solarized-auto</option><option value="srcery">srcery</option><option value="sunburst">sunburst</option><option value="tomorrow">tomorrow</option><option value="tomorrow-night">tomorrow-night</option><option value="tomorrow-night-blue">tomorrow-night-blue</option><option value="tomorrow-night-bright">tomorrow-night-bright</option><option value="tomorrow-night-eighties">tomorrow-night-eighties</option><option value="vs">vs</option><option value="vs2015">vs2015</option><option value="xcode">xcode</option><option value="xt256">xt256</option><option value="zenburn">zenburn</option></select></p><p class="toggleable"><a id="__markdown-viewer__download" download="markdown.html" style="display: none;" href="blob:null/06791adb-5109-410f-8d5e-0102b173a587">Download as HTML</a></p></div><div class="markdownRoot"><h2 id="special-chapter-generating-parsers-for-f-using-rustlr-and-fussless">Special Chapter: Generating Parsers for F# using Rustlr and Fussless</h2>
<p>Rustlr can generate parsers for F#. With .Net interoperability,
other languages (C#) can also use the generated parsers with a little
adaptation, though some knowledge of F# is required. The .Net side
of this aspect of Rustlr is a system called <strong><a href="https://github.com/chuckcscccl/Fussless">Fussless</a></strong>.
This repository contains the runtime parser written in F#. The lexical
analysis aspect of Fussless uses <a href="https://github.com/zbrad/CsLex">CsLex</a>, which is written in C#.
Fussless can automatically generate a CsLex .lex file from the grammar.
Download Fussless and follow instructions in the <a href="https://github.com/chuckcscccl/Fussless">Fussless README</a>
to install the system. If you're not using the latest Mono, you may have
to re-compile absLexer.cs into a .dll, and then, using that
.dll, compile RuntimeParser.fs to a .dll.</p>
<p>At the time of this writing, there are still some features missing from
Fussless compared to the native Rust parser generator. There is
only one, simple error-recovery mechanism (<code>resynch</code>). The experimental -lrsd
option and the wildcard symbol are not currently supported
and the interactive training feature is also not available.
These limitations will gradually be resolved with future releases.</p>
<p>As of Rustlr release 4.0, the Fussless system can now automatically
generate the abstract syntax types and semantic actions from a grammar with
the <code>auto</code> option. However, the first part of this chapter will show how
to write grammars with manually defined types and actions.</p>
<h3 id="invoking-the-parser-generator">Invoking the Parser Generator</h3>
<p>To create a parser, you will first need a .grammar file. Rustlr/Fussless
has its own format for specifying grammars:</p>
<pre><code class="language-ignore"># Unambiguous LR grammar <span class="hljs-keyword">for</span> simple calculator.
valuetype <span class="hljs-type">int</span>
nonterminals E T F
terminals <span class="hljs-operator">+</span> <span class="hljs-operator">*</span>
valueterminal number <span class="hljs-operator">~</span> <span class="hljs-type">int</span> <span class="hljs-operator">~</span> Num <span class="hljs-operator">~</span> <span class="hljs-type">int</span>
lexterminal LPAREN (
lexterminal RPAREN )
topsym E
E <span class="hljs-comment">--> E:e + T:t { e + t }</span>
E <span class="hljs-comment">--> T:t { t }</span>
T <span class="hljs-comment">--> T:t * F:f { t*f }</span>
T <span class="hljs-comment">--> F:f { f }</span>
F <span class="hljs-comment">--> LPAREN E:e RPAREN { e }</span>
F <span class="hljs-comment">--> number:n { n }</span>
EOF
</code></pre>
<p>These are the contents of a Fussless grammar file, called <a href="https://cs.hofstra.edu/~cscccl/rustlr_project/fstarget/test1.grammar">test1.grammar</a>.
This classic example of LR parsing is found in virtually all compiler
textbooks. After you <strong><code>cargo install rustlr</code></strong>
you can produce a LALR(1) parser from this grammar file with:</p>
<blockquote>
<p>rustlr test1.grammar -fsharp</p>
</blockquote>
<p>The first and the only required argument to the executable is the path of the
grammar file. However, without the -fsharp option it will try to create a
parser for Rust. Other optional arguments (after the grammar path) that can be
given to the executable are:</p>
<ul>
<li><strong>-lr1</strong> : this will create a full LR(1) parser if LALR does not suffice.
The default is LALR(1), which works for most examples. A sample grammar
requiring full LR(1) can be found <strong><a href="https://cs.hofstra.edu/~cscccl/rustlr_project/nonlalr.grammar">here</a>.</strong>
Rustlr will always try to resolve shift-reduce conflicts by precedence and associativity
declarations (see later examples) and reduce-reduce conflicts by rule order.
So it will generate some kind of parser in any case. However, unless it's
understood clearly what's causing the conflict, default resolutions should
not be accepted: rewrite the grammar instead.</li>
<li><strong>-o filepath</strong> : changes the default destination of the generated parser, which is
a F# program file called test1parser.fs. This program must be compiled
with RuntimeParser.dll.</li>
<li><strong>-genlex</strong> : automatically generates a lexical scanner in the form of
a CsLex .lex file. The genlex option is
also automatically enabled by the presence of certain declarations in the
grammar file, such as <strong><code>lexterminal</code></strong> or <strong><code>valueterminal</code></strong>. A file
called <a href="https://cs.hofstra.edu/~cscccl/rustlr_project/fstarget/test1.lex">test1.lex</a> will be created. This file must be processed by <em>lex.exe</em>, which is the
CsLex executable: this in turn generates test1_lex.cs, which must be compiled
with absLexer.dll.</li>
<li><strong>-trace n</strong> : where n is a non-negative integer defining the trace level.
Level 0 prints nothing; level 1, which is the default, prints a little more
information. Each greater level will print all information in lower levels.
-trace 3 will print the states of the LR finite state machine, which could
be useful for debugging and training the parser for error message output.</li>
</ul>
<p>The generated parser will be a program
<a href="https://cs.hofstra.edu/~cscccl/rustlr_project/fstarget/test1parser.fs">test1parser.fs</a>
that contains a <strong><code>parse_with</code></strong> function. RustLr will derive the
name of the grammar (test1) from the file path, unless there is a
declaration of the form</p>
<blockquote>
<p>grammarname somename</p>
</blockquote>
<p>in the grammar spec, in which case the parser generated will be called
"somenameparser.fs".</p>
<h3 id="grammar-format">GRAMMAR FORMAT</h3>
<h4 id="valuetype">Valuetype</h4>
<p>A context free grammar is not very useful unless we associate <em>values</em>
with grammar symbols. Without these values all a parser can do is
tell us if something parsed or not. The first line in the grammar
specification should define the default type of value carried by each
grammar symbol. Not all symbols need to have values of the same
type. However, the "start symbol" or "topsym" of the grammar must
have this type, and this is the type of value that's ultimately
returned by the parser.</p>
<blockquote>
<p>valuetype int</p>
</blockquote>
<p>(alternatively <code>absyntype int</code>).
In most cases the type would be some type that defines an abstract syntax
tree, but here we will just calculate an int.</p>
<h4 id="declaring-terminal-and-nonterminal-symbols">Declaring Terminal and Nonterminal symbols.</h4>
<p>Rustlr requires that all terminal and non-terminal symbols be declared
before writing any grammar rules. Terminal symbols can be divided
into two categories: those that carry important values, and those that
do not. In this example, the only terminal symbol with imporant values
is <em>number</em>, which carries values of type <em>int</em>. Such terminals should be
declared using a <strong>valueterminal</strong> line, which has the following format</p>
<blockquote>
<pre><code> valueterminal terminal_name ~ terminal_type ~ token_name ~ fun:string->terminal_type
</code></pre>
</blockquote>
<p>The four elements of the declaration must be separated by <code>~</code>. The
terminal_name declares that this is a terminal symbol, with values of
type terminal_type. The next two fields allow for a lexical scanner
to be generated that recognizes these terminals. Fussless
automatically creates a .lex file that returns lexical tokens of type
<em>RawToken</em> (defined in absLexer.cs). Each RawToken carries a string
(token_name) that defines the type of the token and a string (token_text)
that defines the text of the token. The lexer pre-defines a category for
(unsigned) integers as token_type "Num". Thus the third argument to
<em>valueterminal</em> is the lexer token
type (not to be confused with the terminal_name, which is what the grammar
will refer to). The last component of a valueterminal declaration is a
<em>function</em> of type <em>string -> terminal_type</em>. The <em>int</em> function in F#
converts strings to integers: <em>int("32")</em> returns the integer 32. You can
also write <code>(fun x -> int x)</code>. This function will be applied to the token
text to produce the value expected by the terminal symbol.</p>
<p>The lexical scanner generated by Rustlr recognizes other token types
including "Float", "Alphanum" and "StrLit", which will be discussed in
a later section.</p>
<p>In contrast, terminals such as +, * ( and ) do not carry significant values:
they will always be assigned Unchecked.defaultof<valuetype> just as a filler.
These terminals can be defined in one of two ways.</p>
<ol>
<li>if the name of the terminal is the same as the text of the terminal,
they can be defined on a <em>terminals</em> line (multiple lines are allowed).</li>
<li>if the name of the terminal is different from the textual form, use
a <em>lexterminal</em> declaration. These are required for certain symbols that
are reserved for other uses in Rustlr, including { } | : and a few others.
The parentheses are also best not used to name terminals by themselfs.
Thus <code>lexterminal LPAREN (</code> means that we will refer to the terminal as
LPAREN in the grammar and the lexical analyzer will recognize "(" as
this type of token.</li>
</ol>
<p>Nonterminal symbols that are to have the same type as the declared valuetype
of the grammar can be defined on one <code>nonterminals</code> line. You should use only
alphanumeric names for non-terminals (Rustlr is also not guaranteed to work
with non-ascii characters). In this example all nonterminals have type int, so
one such line suffices. Otherwise declare differently typed non-terminals
using lines such as <code>nonterminal S string</code>.</p>
<h4 id="top-nonterminal">Top Nonterminal</h4>
<blockquote>
<p>topsym E</p>
</blockquote>
<p>(alternatively startsymbol E). You should designate one particular
nonterminal symbol as the top symbol. This symbol must have the same
type (for its value ) as the declared 'valuetype' so you should not
try to assign it a different type.</p>
<h4 id="grammar-production-rules">Grammar Production Rules</h4>
<p>You will get an error message if the grammar symbols are not defined
before the grammar rules. Each rule is indicated by a non-terminal
symbol followed by <code>--></code> or <code>==></code>. The symbol <code>==></code> is for rules that
span multiple lines: they must be terminated with <code><==</code>. You can specify
multiple production rules with the same left-hand side nonterminal
using | but Rustlr discourages their use.</p>
<p>The right-hand side of each rule must separate symbols with
whitespaces. For each grammar symbol such as E, you can optionally
bind a "label" such as <code>E:a</code>, The label 'a' refers to the
value associated with this occurrence of E.</p>
<p>The right-hand side of a rule may be empty, which will make the
non-terminal on the left side of <code>--></code> "nullable".</p>
<h4 id="semantic-actions">SEMANTIC ACTIONS</h4>
<p>Values for non-terminal symbols are returned by functions commonly referred
to as <em>semantic actions</em>.
Each rule can optionally end with a semantic action inside { and },
which can only follow all grammar symbols making up the right-hand
side of the production rule. This is a piece of F# code that will form
the body of the semantic action function. This code will have
access to any labels associated with the symbols defined using ":".
In a label such as <code>E:e</code>, e is a mutable variable intialized to the value
associated with E.</p>
<p>The semantic action of each rule must return a value of the type associated
with the left-hand side symbol of that rule. Generally speaking,
the semantic action of a rule <code>A --> B:b C:c D:d</code> is a function that
<code>f</code> that takes as arguments value of the types for <code>B</code>, <code>C</code> and <code>D</code>
and <code>f(b,c,d)</code> will be the value associated with <code>A</code>.</p>
<p>It is recommended that if you use multiple lines that you start the
semantic action on a new line after the openning <code>{</code>. Be reminded
that F# doesn't use braces to group code (they're used to form
records). The braces are just Rustlr syntax to separate the semantic
action from the rest of the grammar rule.</p>
<p>The semantic action code is injected verbatim into the generated parser,
thus any errors in the code will not show up until you try to compile
the parser. For security reasons it's generally not a good idea to run
programs like parser generators with systems privileges.</p>
<p>If no semantic action is given, a default one is created that just returns
a default value.</p>
<h4 id="error-reporting">Error Reporting</h4>
<p>Semantic actions always have access a parameter named 'parser'. The
functions that can be called on parser are <strong>report_error</strong> and
<strong>abort</strong>. For example, <code>parser.abort("failure")</code> or
<code>parser.report_error("problem encountered",true)</code>. The <code>report_error</code>
function takes a boolean argument that determines if line/column
numbers should be displayed. The <code>abort</code> function terminates parsing.</p>
<p>Another important function that can be called on <code>parser</code> is parser.position,
which returns a pair (line,column) that's associated with one of the symbols
on the right-hand side of a rule: the exact symbol is indicated as a 0-based
integer that's passed to parser.position. An example will make this clear:
the rule for multiplication can be replaced with</p>
<pre><code>T ==> T:t * F:f {
<span class="hljs-built_in">let</span> tf = t*f
<span class="hljs-keyword">if</span> f<>0 && (tf/f <> t) <span class="hljs-keyword">then</span>
<span class="hljs-built_in">let</span> (ln,cl) = parser.position(1)
printfn <span class="hljs-string">"Warning: arithmetic overflow line %d, column %d"</span> ln cl
t*f
} <==
</code></pre>
<p>The argument (1) passed to parser.position refers to the <code>*</code> symbol, that
is the 2nd symbol on the right-hand side. Index 0 will refer to the T and
index 2 will refer to F. (0,0) will be returned for an invalid index.</p>
<p>Note also that this rule spans multiple lines and requires ==> and <==. Also,
the injected multi-line F# code should start on a new line and be indented.</p>
<p>The three member functions on parser described above are the only ones that
should be called from semantic actions. There are other functions that would
corrupt the parser and should never be called. In general, whatever code
you write inside the braces are entirely your own responsibility.</p>
<h4 id="lbox">LBox</h4>
<p>Not all errors are parsing errors. After the AST is successfully
built, other phases usually follow that perform semantic analysis such as
type checking. Errors detected in later stages must also be reported
with line/column numbers indicating their origin. The AST therefore must
carry this information. Fussless defines a structure <em>LBox</em> that encapsulates
a value along with line and column information:</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">type</span> <span class="hljs-title">LBox</span></span><<span class="hljs-symbol">'AT</span>> =
{
value: <span class="hljs-symbol">'AT</span>;
line : int;
column: int;
}
<span class="hljs-keyword">let</span> lbox<<span class="hljs-symbol">'AT</span>> (v:<span class="hljs-symbol">'AT</span>,ln:int,cn:int) = { LBox.value =v; line=ln; column=cn; }
<span class="hljs-keyword">let</span> (|Lbox|) (b:LBox<<span class="hljs-symbol">'AT</span>>) = Lbox(b.value)
</code></pre>
<p>The structure comes with two other definitions: <em>lbox</em> is an ordinary
constructor and <em>Lbox</em> is an <em>active pattern</em>. The active pattern
allows the lexical information to be hidden: exposing
only the value within the box. ASTs can be defined using LBox as
demonstrated below:</p>
<pre><code>type expr = Val of LBox<span class="hljs-tag"><<span class="hljs-name">int</span>></span> | Plus of LBox<span class="hljs-tag"><<span class="hljs-name">expr</span>></span>*LBox<span class="hljs-tag"><<span class="hljs-name">expr</span>></span> | Times of LBox<span class="hljs-tag"><<span class="hljs-name">expr</span>></span>*LBox<span class="hljs-tag"><<span class="hljs-name">expr</span>></span> | Divide of LBox<span class="hljs-tag"><<span class="hljs-name">expr</span>></span>*LBox<span class="hljs-tag"><<span class="hljs-name">expr</span>></span>
</code></pre>
<p>The active pattern form <em>Lbox</em> allows pattern matching on these
structures without the intrusive line/column information, <em>except</em>
when we actually need them</p>
<pre><code><span class="hljs-keyword">let</span> rec <span class="hljs-built_in">eval</span> = <span class="hljs-function"><span class="hljs-keyword">function</span>
| <span class="hljs-title">Val</span>(<span class="hljs-params">Lbox(x)</span>) -> <span class="hljs-title">x</span>
| <span class="hljs-title">Plus</span>(<span class="hljs-params">Lbox(a),Lbox(b)</span>) -> (<span class="hljs-params"><span class="hljs-built_in">eval</span> a</span>) + (<span class="hljs-params"><span class="hljs-built_in">eval</span> b</span>)
| <span class="hljs-title">Times</span>(<span class="hljs-params">Lbox(a),Lbox(b)</span>) -> (<span class="hljs-params"><span class="hljs-built_in">eval</span> a</span>) * (<span class="hljs-params"><span class="hljs-built_in">eval</span> b</span>)
| <span class="hljs-title">Divide</span>(<span class="hljs-params">Lbox(a),(Lbox(b) <span class="hljs-keyword">as</span> n)</span>) ->
<span class="hljs-title">let</span> <span class="hljs-title">bv</span> = (<span class="hljs-params"><span class="hljs-built_in">eval</span> b</span>)
<span class="hljs-title">if</span> <span class="hljs-title">bv</span>=0 <span class="hljs-title">then</span>
<span class="hljs-title">raise</span>(<span class="hljs-params">Exception(sprintf <span class="hljs-string">"division by zero column %d\n"</span> n.column)</span>)
(<span class="hljs-params"><span class="hljs-built_in">eval</span> a</span>) / <span class="hljs-title">bv</span>
</span></code></pre>
<p>Fussless has built-in support for creating LBoxes. In a grammar production,
symbols on the right-hand side can be given "boxed labels". For example:</p>
<pre><code><span class="hljs-attribute">E</span> --> E:[e<span class="hljs-number">1</span>] + T:[e<span class="hljs-number">2</span>] { Plus(e<span class="hljs-number">1</span>,e<span class="hljs-number">2</span>) }
</code></pre>
<p>A boxed label such as <code>[e1]</code> instructs the parser to place the value
associated with the grammar symbol inside an LBox and to bind the variable
<code>e1</code> to it.</p>
<p>The LBox is named for its counterpart in Rust parsers created by Rustlr,
although it is not a "smart pointer".</p>
<h4 id="building-and-invoking-the-parser"><strong>BUILDING AND INVOKING THE PARSER</strong></h4>
<p>The steps for creating and calling a parser is best illustrated by the
following example (<a href="https://cs.hofstra.edu/~cscccl/rustlr_project/fstarget.test1main.fs">test1main.fs</a>).</p>
<pre><code><span class="hljs-keyword">module</span> Test1
<span class="hljs-keyword">open</span> <span class="hljs-keyword">System</span>
<span class="hljs-keyword">open</span> Fussless
<span class="hljs-keyword">open</span> Test1
let parser1 <span class="hljs-operator">=</span> make_parser(); <span class="hljs-operator">/</span><span class="hljs-operator">/</span> <span class="hljs-keyword">create</span> parser
Console.Write("Enter Expression: ");
let lexer1 <span class="hljs-operator">=</span> test1lexer<span class="hljs-operator"><</span>unit<span class="hljs-operator">></span>(Console.ReadLine()); <span class="hljs-operator">/</span><span class="hljs-operator">/</span> <span class="hljs-keyword">create</span> lexer
let <span class="hljs-keyword">result</span> <span class="hljs-operator">=</span> parse_with(parser1,lexer1); <span class="hljs-operator">/</span><span class="hljs-operator">/</span> invokes parser printfn
printfn "Result = %A" <span class="hljs-keyword">result</span>;;
</code></pre>
<p>The lexical analyzer 'test1.lex' that's
generated automatically defines the C# class 'test1lexer<E>'. The
generic type argument E defines an "shared state' between the parser
and lexer. By default, this type is unit. The class comes with two
constructors: one taking a string, as used in the above program, and
one taking a System.IO.FileStream.</p>
<p>Assuming that <a href="https://github.com/chuckcscccl/Fussless">Fussless</a> has been downloaded and that 'absLexer.dll'
and 'RuntimeParser.dll' are available, compile the test1parser.fs file with
RuntimeParser.dll and the test1_lex.cs file (produced by lex.exe) with
absLexer.dll. Note that both the generated parser and lexer are defined within
the 'Fussless' namespace and the parser is defined under the Test1 module, which
is why 'open Test1' is used: you would have to call Test1.make_parser and
Test1.parse_with otherwise.</p>
<p>Now compile the above test1main.fs program with the .dlls of the parser and
lexer. Under mono this is done with</p>
<blockquote>
<pre><code> fsharpc test1main.fs -r test1parser.dll -r test1_lex.dll
</code></pre>
</blockquote>
<p>which produces an executable. Alternatively, there is a <a href="https://github.com/chuckcscccl/Fussless/blob/main/Makefile">Makefile</a> included inside the Fussless
repository. Consult the <a href="https://github.com/chuckcscccl/Fussless/blob/main/README.md">Fussless Readme</a> for instructions.</p>
<p>The <code>parse_with</code> function must be passed instances of a parser and a lexer.
It returns an <strong>option type</strong> value of type <strong>valuetype option</strong>.</p>
<h4 id="injection-of-top-level-code">Injection of Top Level Code</h4>
<p>A line that begin with '!' will be injected verbatim into
the generated parser. Such lines will always be injected towards the beginning
of the code regardless of where they appear in the grammar. Typically, these
lines will specify additional modules to open, such as</p>
<pre><code>!<span class="hljs-built_in">open</span> System.Collections.<span class="hljs-type">Generic</span>;
</code></pre>
<h4 id="lexical-analyzer-directives">Lexical Analyzer Directives</h4>
<p>In order for Rustlr-Fussless to generate a .lex file, there must be at least
one 'lexterminal' or valueterminal' declaration in the grammar; otherwise
rustlr must be invoked with the -genlex option.</p>
<p>The generated .lex will recognize the following token types, including "Num"
that appeared in the 'test1' example</p>
<ul>
<li>
<p>Alphanum: alphanumeric sequences starting with an alphabetical letter or
_ (underscore), and followed by zero or more alphabetical or numeric characters
or _.</p>
</li>
<li>
<p>Num: unsigned base-10 integers. It is better to process negative
integers at the grammar level, lest "3-2" be recognized as two tokens
instead of three.</p>
</li>
<li>
<p>Hexnum: hexadecimal sequences starting with 0x</p>
</li>
<li>
<p>Float: unsigned floating point sequences</p>
</li>
<li>
<p>StrLit: string literals</p>
</li>
</ul>
<p>Note that the lexer will not check the returned tokens for overflow: that must
be done with the the function that you specify as the last argument to
'valueterminal'.</p>
<p>Besides the common types of tokens above, you can also define new token types
and their associated regular expressions:</p>
<blockquote>
<pre><code> lexattribute custom ULong [0-9]+UL
</code></pre>
</blockquote>
<p>This defines a new token type that will be returned along with the text that
matched the given regex. Such user-defined custom categories <strong>will override
the other categories</strong>. This means that "205UL" will now be returned as a
single RawToken with token type "ULong" instead of two tokens, a "Num" and
an "Alphanum". Multiple custom token types will be prioritized in the order
in which they appear inside the grammar.</p>
<p>Once a custom token type is defined, <strong>a valueterminal declaration is still
required</strong> to translate such tokens into terminal symbols of the grammar, such
as</p>
<pre><code>!<span class="hljs-function">let <span class="hljs-title">conv64</span> <span class="hljs-params">(x:string)</span> :uint64 =</span> System.UInt64.<span class="hljs-built_in">Parse</span>(x.<span class="hljs-built_in">Substring</span>(<span class="hljs-number">0</span>,x.Length<span class="hljs-number">-2</span>))
valueterminal U64 ~ uint64 ~ ULong ~ conv64
</code></pre>
<h4 id="other-lexattribute-directives">Other lexattribute directives</h4>
<p>As of this writing, the only other lexattribute directive available is
<code>line_comment</code>. By default, the generated lexer recognizes (and ignores)
C-style comments. The line_comment directive can be
used to change the symbol for single-line comments, such as</p>
<pre><code><span class="hljs-attribute">lexattribute</span> line_comment <span class="hljs-comment">#</span>
</code></pre>
<p>The symbol selected should be non-alphanumeric. <code>lexattribute line_comment disable</code> will disable the recognition of single-line comments.</p>
<h4 id="operator-precedence-and-associativity-declarations">Operator precedence and associativity declarations.</h4>
<p>Rustlr allows <strong>left</strong>, <strong>right</strong> and <strong>nonassoc</strong> declarations for terminal
symbols. Each such declaration must specify a positive integer defining the
precedence levels. These declarations are used to break shift-reduce conflicts
and allows the writing of some ambiguous grammars (<code>E --> E+E</code>) instead of
(<code>E --> E+T</code>). The default precedence is zero, which means no precedence has
been defined. However, these kinds of declarations should not be overused
(see below).</p>
<h4 id="error-recovery">Error Recovery</h4>
<p>An LR parser is defined by a state action (transition) table and a stack of states. The
top of the stack is the current state. A parsing error occurs when
the current state has no entry defined for the next input.
Currently, only one method of error recovery has been implemented for F# parsers.
A declaration such as</p>
<pre><code><span class="hljs-attribute">resync</span> SEMICOLON COMMA
</code></pre>
<p>designates one or more terminal symbols as <em>resynchronization points</em>. When
an error occurs, the parser will skip input tokens until it finds one of
these points. It then looks down its stack of states to find one that
has an entry for the <em>next</em> input symbol after the resynch
point, and continues parsing. If no resync point is declared, the parser
will just skip input until it finds one that has an entry defined with
respect to the current state. A natural resync point is the semicolon
that separates statements in many languages. If an error occurs, the
parser will skip past the semicolon and parse the next line.</p>
<p>There are more sophisticated error recovery techniques that could be
implemented so this is currently a minimal feature.</p>
<p>Up on the detection of any error, the <strong>parser.err_occurred</strong> flag will
be set and it's up to the user to examine this flag before deciding what
to do with the result.</p>
<h4 id="using-regular-expression-like-operators-etc">Using Regular Expression-Like Operators +, *, ?, etc</h4>
<p>Rustlr allows grammar rules to be written in the following way:</p>
<pre><code>E --> <span class="hljs-selector-tag">A</span>* <span class="hljs-selector-tag">B</span>+ C? D<,+> E<;*>
</code></pre>
<p>These regular-expression like operators serve to translate the grammar into the
following:</p>
<pre><code>E <span class="hljs-comment">--> As Bp Cq Dp Es</span>
<span class="hljs-keyword">As</span> <span class="hljs-comment">--> | As A</span>
Bp <span class="hljs-comment">--> B | Bp B</span>
Cq <span class="hljs-comment">--> | C</span>
Dp <span class="hljs-comment">--> D | Dp , D</span>
Es <span class="hljs-comment">--> | Ep</span>
Ep <span class="hljs-comment">--> E | Ep ; E</span>
</code></pre>
<p>Furthermore, the semantic values associated with A*, B+ D<,+> and E<;*>
are always of type Vec<_> (ResizeArray<_>) and type for C? is
option<_>, where _ represents the types of the respective
non-terminals. The *, + and ? operators have the same meaning as in
regular expressions. In <sym+> and <sym*>, sym must be a terminal symbol.
These operations represent sequences separated by the terminal, but not
ending in the terminal. For example:</p>
<pre><code><span class="hljs-selector-tag">function_call</span> <span class="hljs-selector-tag">--</span>> <span class="hljs-selector-tag">functional_name</span> ( expression<,*> )
</code></pre>
<p>defines function calls with zero or more comma-separated arguments.</p>
<p>These operators are available as a convenience, but they come at a price.
The introduction of new production rules to a grammar increases the chance
of non-determinism even if the grammar remains unambiguous. Rustlr does not
allow the regex-like operators to be <em>nested</em>: such expressions easily become
ambiguous. Consider <code>(a?)+</code>: a single <code>a</code> will have an infinite
number of parse trees because any number of <code>a?</code> can be empty.</p>
<h4 id="a-more-advanced-example">A More Advanced Example</h4>
<p>We consolidate the features of Fussless with a more advanced version
of an online calculator. In addition to several new terminal token
types, this grammar approaches the sophistication of a programming
language with let-expression, as in <code>let x=1 in x+(let x=3 in x+x)+x</code>
(which should evaluate to 8). Checking for the proper scoping of
variables, however, is typically not done at the parsing stage.
The language also allows a sequence of expressions separated by semicolons.
The semicolon (<code>;</code>) is also declared as the error-recovery resynch point.</p>
<p>This time, the parser will build abstract syntax trees, and we've injected
the AST discriminated union type directly into the parser, though usually
this is done separately in another module. Discriminated unions and
pattern matching definitely give F# and similar languages an advantage over
conventional languages when processing ASTs. Even Rust cannot compete as
recursive types require smart pointers (Box) that prevent deep pattern matching.</p>
<pre><code>!type expr = Val of <span class="hljs-keyword">int</span> | <span class="hljs-keyword">Var</span> of <span class="hljs-keyword">string</span> | <span class="hljs-keyword">Float</span> of <span class="hljs-keyword">float</span> | Plus of expr*expr | Times of expr*expr | Minus of expr*expr | Divide of expr*expr | Negative of expr | Letexp of <span class="hljs-keyword">string</span>*expr*expr | Equals of (expr*expr) | Uint of uint64;;
!
!let conv64 (x:<span class="hljs-keyword">string</span>) :uint64 = System.UInt64.Parse(x.Substring(<span class="hljs-number">0</span>,x.Length-<span class="hljs-number">2</span>))
valuetype Vec<expr>
<span class="hljs-comment"># Vec is defined in the Fussless namespace as an alias for ResizeArray</span>
nonterminal E expr
nonterminal ES
terminals + - * / ( ) == = ;
terminals let in
valueterminal Val ~ <span class="hljs-keyword">int</span> ~ Num ~ <span class="hljs-keyword">int</span>
valueterminal <span class="hljs-keyword">Var</span> ~ <span class="hljs-keyword">string</span> ~ Alphanum ~ (fun x -> x)
valueterminal <span class="hljs-keyword">Float</span> ~ <span class="hljs-keyword">float</span> ~ <span class="hljs-keyword">Float</span> ~ <span class="hljs-keyword">float</span>
lexattribute custom U64 [<span class="hljs-number">0</span>-<span class="hljs-number">9</span>]+UL
valueterminal Ulong ~ uint64 ~ U64 ~ conv64
lexattribute line_comment <span class="hljs-comment">#</span>
topsym ES
resync ;
left * <span class="hljs-number">500</span>
left / <span class="hljs-number">500</span>
left + <span class="hljs-number">400</span>
left - <span class="hljs-number">400</span>
nonassoc = <span class="hljs-number">200</span>
right == <span class="hljs-number">300</span>
E --> Val:m { Val(m) }
E --> <span class="hljs-keyword">Var</span>:s { <span class="hljs-keyword">Var</span>(s) }
E --> <span class="hljs-keyword">Float</span>:f { <span class="hljs-keyword">Float</span>(f) }
E --> Ulong:u { Uint(u) }
E --> let <span class="hljs-keyword">Var</span>:x = E:e in E:b {Letexp(x,e,b)}
E --> E:e1 + E:e2 { Plus(e1,e2) }
E --> E:e1 - E:e2 { Minus(e1,e2) }
E --> E:e1 * E:e2 { Times(e1,e2) }
E ==> E:e1 / E:e2 {
<span class="hljs-keyword">if</span> e2=Val(<span class="hljs-number">0</span>) then
let (ln,cl) = parser.position(<span class="hljs-number">2</span>)
printfn <span class="hljs-string">"Warning:obvious division by 0, line %d column %d"</span> ln cl
Divide(e1,e2)
} <==
E --> E:e1 == E:e2 { Equals(e1,e2) }
E(<span class="hljs-number">600</span>) --> - E:e { Negative(e) }
E --> ( E:e ) { e }
ES --> E<;+>:v ;? { v }
EOF
</code></pre>
<p>Note that the - (minus) symbol serves as both a unary and a binary operator.
As a unary operator, it should have precedence over *.
This means that using operator precedence/associativity declarations for
the symbol is not enough. "-3*5" should be parsed as (-3)*5 and not as
-(3*5): never mind that they both evaluate to -15: the point is that the
parse trees are different. Thus, a precedence level can also be assigned
to a <em>rule</em>, which is done for the rule for unary minus. Without a
particular precedence assignment, a rule is assigned the precedence of the
highest precedence symbol it finds on the right-hand side. Precedence declarations are definitely a <em>hack</em>, almost as bad as some parser generators that
claim to "work with any grammar".
They should not be overly relied on. One place where it
is useful is in disambiguating the <em>dangling else</em> problem: assign "else"
a higher precedence than "if". This will force a <em>shift</em> when an "else" is
encountered, which means that it will be associated with the nearest "if".</p>
<h4 id="using-c">Using C#</h4>
<p>Using C# is possible by virtue of .Net interoperability. The abstract syntax
structures can be defined in C# and the semantic actions to construct such
structures should generally not be difficult to call from F#. The
integration of the .dlls from the different languages may face some challenges
depending on your development platform. On Mono there where some problems
importing a .dll compiled with F# into a C# project. But these problems
can be mostly avoided by writing some minimal components of the parser in
F#.</p>
<br>
<h3 id="automatically-generating-the-abstract-syntax"><strong>Automatically Generating the Abstract Syntax</strong></h3>
<p>With Rustlr 0.4 and the latest Fussless the grammar can now generate the
abstract syntax types and semantic actions automatically. Manual overrides
are also possible. Essentially, non-terminal symbols that are on the
left-hand side of multiple productions generate discriminated unions while
those with a single production generate records. However, the AST types do
not necessarily just mirror the grammar. For
example, non-terminal symbols such as E, T and F (of the calculator grammar)
can be specified to define a single union type as opposed to individual
types. Records can be absorbed or "flattened" into other types.
Rustlr/Fussless grammars contain a sub-language that defines
how ASTs are to be created that can also be stable across small changes to
the grammar. The system has the same capabilities as described for
Rust parsers in <strong><a href="https://cs.hofstra.edu/~cscccl/rustlr_project/chapter4.html">Chapter 4</a></strong>. In fact it is simpler since there
is no need for lifetimes and smart pointers. Fussless <em>LBox</em> structures
are created in the same way as their counterparts in Rust, without the
pointer aspect.</p>
<p>Two sample grammars are available that demonstrate these abilities:</p>
<ol>
<li>
<p><a href="https://github.com/chuckcscccl/Fussless/blob/main/calcautofs.grammar">calcautofs.grammar</a>. This grammar describes another version of the calculator
grammar with <code>auto</code> types and actions and some overrides. It also demonstrates the injection of code that precedes the automatically generated actions, and
the 'flattening' feature for structs.</p>
</li>
<li>
<p><a href="https://github.com/chuckcscccl/Fussless/blob/main/fs7c.grammar">fs7c.grammar</a>. This grammar defines a simplified, typed functional
programming language that was used in a compilers class taught at Hofstra
University.</p>
</li>
</ol>
<p>To invoke the feature, replace the "valuetype" declaration with "auto"
at the top of the grammar. In addition to the parser file, an <code>_ast.fs</code>
file will be created. Code can be injected into the AST file with lines
beginning with <code>$</code>.</p>
<hr>
</div></body></html>