From b13e6477726cc111af4c233d7718f982f3fd948e Mon Sep 17 00:00:00 2001 From: Pauan Date: Tue, 18 Dec 2012 11:09:39 -0800 Subject: [PATCH] . --- NULAN.macros | 2 +- NULAN.parse.js | 513 ++++++++++++++++++---------------- notes/Customizable syntax.rst | 186 +++++++++--- 3 files changed, 413 insertions(+), 288 deletions(-) diff --git a/NULAN.macros b/NULAN.macros index 32f6a7c..4f1d7a1 100644 --- a/NULAN.macros +++ b/NULAN.macros @@ -153,7 +153,7 @@ $mac $syntax-helper -> n f # TODO 'w/var v = ,f | u.priority <= i - | u.action <= v + | u.parse <= v | $syntax-rule s u $syntax-helper $syntax-infix diff --git a/NULAN.parse.js b/NULAN.parse.js index 9f7b4f0..ea714a7 100644 --- a/NULAN.parse.js +++ b/NULAN.parse.js @@ -130,7 +130,7 @@ var NULAN = (function (n) { return { delimiter: b, priority: i, - action: function (l, s, r) { + parse: function (l, s, r) { var x = l[l.length - 1] , y = r[0] @@ -144,7 +144,7 @@ var NULAN = (function (n) { delimiter: b, priority: i, order: "right", - action: function (l, s, r) { + parse: function (l, s, r) { var y = r[0] return l.concat([[s, y]], r.slice(1)) } @@ -154,7 +154,7 @@ var NULAN = (function (n) { function inert(start, end) { t[end] = { delimiter: true, - action: function (l, s, r) { + parse: function (l, s, r) { throw new n.Error(s, "missing starting " + start) } } @@ -164,169 +164,250 @@ var NULAN = (function (n) { return x.length === 1 ? x[0] : x } + // TODO: make them into proper infix, so they behave correctly when only given a left or right side - var t = { - // TODO: do these need to be priority 120? - "(": { - priority: 110, - delimiter: true, - endAt: ")", - action: function (l, s, r) { - l.push(unwrap(r[0])) - return l.concat(r.slice(1)) - } - }, + var t = {} + + // TODO: do these need to be priority 120? + t["("] = { + priority: 110, + delimiter: true, + endAt: ")", + parse: function (l, s, r) { + l.push(unwrap(r[0])) + return l.concat(r.slice(1)) + } + } - "{": { - priority: 110, - delimiter: true, - endAt: "}", - action: function (l, s, r) { - r[0].unshift(n.box("list")) + t["{"] = { + priority: 110, + delimiter: true, + endAt: "}", + parse: function (l, s, r) { + r[0].unshift(n.box("list")) + l.push(r[0]) + return l.concat(r.slice(1)) + } + } + + t["["] = { + priority: 110, + delimiter: true, + endAt: "]", + parse: function (l, s, r) { + if (s.whitespace) { + r[0].unshift(n.box("dict")) l.push(r[0]) - return l.concat(r.slice(1)) + } else { + var x = l[l.length - 1] + l = l.slice(0, -1) + l.push([n.box("."), x, unwrap(r[0])]) } - }, + return l.concat(r.slice(1)) + } + } - "[": { - priority: 110, - delimiter: true, - endAt: "]", - action: function (l, s, r) { - if (s.whitespace) { - r[0].unshift(n.box("dict")) - l.push(r[0]) - } else { - var x = l[l.length - 1] - l = l.slice(0, -1) - l.push([n.box("."), x, unwrap(r[0])]) - } - return l.concat(r.slice(1)) - } - }, + t[";"] = { + priority: 100, + delimiter: true, + parse: function (l, s, r) { + l.push([l.pop()]) + l.push.apply(l, r) + return l + } + } - ";": { - priority: 100, - delimiter: true, - action: function (l, s, r) { - l = [l] - l.push.apply(l, r) - return l - } - }, + t[":"] = { + priority: 100, + delimiter: true, + separator: true, + parse: function (l, s, r) { + l.push(r[0]) + l.push.apply(l, r.slice(1)) + return l + } + } - ":": { - priority: 100, - delimiter: true, - separator: true, - action: function (l, s, r) { - l.push(r[0]) - l.push.apply(l, r.slice(1)) - return l - } - }, - - ".": infix(100, true, function (l, s, r) { - if (l instanceof n.Wrapper && - r instanceof n.Wrapper && - typeof l.value === "number" && - typeof r.value === "number") { - var x = (l.value + "." + r.value) - x = enrich(new n.Wrapper(+x), l) - x.length = l.length + r.length + 1 - return x - } else if (r instanceof n.Symbol) { - return [s, l, r.value] - } else { - return [s, l, r] - } - }), + t["."] = infix(100, true, function (l, s, r) { + if (l instanceof n.Wrapper && + r instanceof n.Wrapper && + typeof l.value === "number" && + typeof r.value === "number") { + var x = (l.value + "." + r.value) + x = enrich(new n.Wrapper(+x), l) + x.length = l.length + r.length + 1 + return x + } else if (r instanceof n.Symbol) { + return [s, l, r.value] + } else { + return [s, l, r] + } + }) - // TODO - ",": unary(90, true), - "@": unary(90, true), - //"~": unary(90, false), + // TODO + t[","] = unary(90, true) + t["@"] = unary(90, true) + //t["~"] = unary(90, false), - /*"*": infix(80), - "/": infix(80), + /*t["*"] = infix(80), + t["/"] = infix(80), - "+": infix(70), - "-": infix(70), + t["+"] = infix(70), + t["-"] = infix(70), - "<": infix(60), - "=<": infix(60), - ">": infix(60), - ">=": infix(60),*/ + t["<"] = infix(60), + t["=<"] = infix(60), + t[">"] = infix(60), + t[">="] = infix(60),*/ - //"==": infix(50), - //"~=": infix(50), + //t["=="] = infix(50), + //t["~="] = infix(50), - /*"&&": infix(40), + /*t["&&"] = infix(40), - "||": infix(30),*/ + t["||"] = infix(30),*/ - "'": { - priority: 10, - whitespace: true, - delimiter: true, - separator: true, - action: function (l, s, r) { - l.push([s, unwrap(r[0])]) - l.push.apply(l, r.slice(1)) - return l - } - }, + t["'"] = { + priority: 10, + whitespace: true, + delimiter: true, + separator: true, + parse: function (l, s, r) { + l.push([s, unwrap(r[0])]) + l.push.apply(l, r.slice(1)) + return l + } + } - "->": { - priority: 10, - order: "right", - action: function (l, s, r) { - var args = r.slice(0, -1) - l.push([s, args, r[r.length - 1]]) - return l - } - }, + t["->"] = { + priority: 10, + order: "right", + parse: function (l, s, r) { + var args = r.slice(0, -1) + l.push([s, args, r[r.length - 1]]) + return l + } + } - "=": { - priority: 10, - separator: true, - action: function (l, s, r) { - var x = l[l.length - 1] - l = l.slice(0, -1) - l.push([s, x, unwrap(r[0])]) - l.push.apply(l, r.slice(1)) - return l - } - }, + t["="] = { + priority: 10, + separator: true, + parse: function (l, s, r) { + var x = l[l.length - 1] + l = l.slice(0, -1) + l.push([s, x, unwrap(r[0])]) + l.push.apply(l, r.slice(1)) + return l + } + } - "<=": { - order: "right", - action: function (l, s, r) { - return [s, unwrap(l), unwrap(r)] - } - }, + t["<="] = { + order: "right", + parse: function (l, s, r) { + return [s, unwrap(l), unwrap(r)] + } + } + + t["|"] = { + separator: true, + vertical: true, + parse: function (l, s, r) { + l.push([s].concat(r[0].map(unwrap))) + l.push.apply(l, r.slice(1)) + return l + } + } - "|": { - //delimiter: true, - action: function (l, s, r) { - return l.concat(r) + // TODO: add in whitespace, comments, and strings to "Customizable syntax.rst" + t[" "] = t["\n"] = { + delimiter: true, + whitespace: true, + tokenize: function (c, o, push) { + o.read() + } + } + + function tokenizeComment(o) { + var s = store(o) + // TODO: a teensy bit hacky + --s.column + s.length = 2 + while (true) { + if (!o.has()) { + throw new n.Error(s, "missing ending |#") + } + o.read() + if (o.peek() === "|") { + o.read() + if (o.peek() === "#") { + break + } + } else if (o.peek() === "#") { + o.read() + if (o.peek() === "|") { + tokenizeComment(o) + } } - }, + } + } - "\n": { delimiter: true }, - " ": { delimiter: true }, - "#": { delimiter: true }, + t["#"] = { + delimiter: true, + whitespace: true, + tokenize: function (c, o, push) { + o.read() + if (o.peek() === "|") { + tokenizeComment(o) + o.read() + } else { + while (o.has() && o.peek() !== "\n") { + o.read() + } + } + } + } - /*"|": { - order: "right", - priority: 100, - action: function (l, s, r) { - var y = parseLine(r) - l.push([s].concat(y[0])) - l.push.apply(l, y[1]) - return l + t["\""] = { + delimiter: true, + tokenize: function (q, o, push) { + var r = [] + , c + o.read() + while (o.peek() !== q) { + if (o.has()) { + c = o.peek() + if (c === "\\") { + o.read() + c = o.read() + if (c === "n") { + r.push("\n") + } else if (c === "t") { + r.push("\t") + } else if (c === "\"" || c === "@" || c === "\\") { + r.push(c) + } else { + // TODO: a little hacky + o.length = 2 + o.column -= 2 + throw new n.Error(o, "expected \\n \\t \\\" \\@ \\\\ but got \\" + c) + } + // TODO + } else if (c === "@") { + o.read() + console.log(tokenize(o)) + } else { + r.push(o.read()) + } + } else { + // TODO + s.length = 1 + throw new n.Error(s, "missing ending \"") + } } - },*/ + o.read() + r = r.join("") + push(new n.Wrapper(r)) + } } inert("(", ")") @@ -368,6 +449,7 @@ var NULAN = (function (n) { function tokenizeNumOrSym(o) { var s = store(o) , r = [] + , b while (o.has() && /\d/.test(o.peek())) { r.push(o.read()) } @@ -378,96 +460,22 @@ var NULAN = (function (n) { r.push(o.read()) } r = r.join("") - return enrich(new n.Symbol(r), s, o) - } - } - - function tokenizeString(o) { - var s = store(o) - , q = o.peek() - , r = [] - , c - o.read() - while (o.peek() !== q) { - if (o.has()) { - c = o.peek() - if (c === "\\") { - o.read() - c = o.read() - if (c === "n") { - r.push("\n") - } else if (c === "t") { - r.push("\t") - } else if (c === "\"" || c === "@" || c === "\\") { - r.push(c) - } else { - // TODO: a little hacky - o.length = 2 - o.column -= 2 - throw new n.Error(o, "expected \\n \\t \\\" \\@ \\\\ but got \\" + c) - } - // TODO - } else if (c === "@") { - o.read() - console.log(tokenize(o, true)) - } else { - r.push(o.read()) - } - } else { - s.length = 1 - throw new n.Error(s, "missing ending \"") - } + b = t[r] + r = enrich(new n.Symbol(r), s, o) + // TODO make this work + //r.whitespace = white + //white = (b ? !!b.whitespace : false) + return r } - o.read() - r = r.join("") - return enrich(new n.Wrapper(r), s, o) } - function tokenizeComment(o) { - var s = store(o) - // TODO: a teensy bit hacky - --s.column - s.length = 2 - while (true) { - if (!o.has()) { - throw new n.Error(s, "missing ending |#") - } - o.read() - if (o.peek() === "|") { - o.read() - if (o.peek() === "#") { - break - } - } else if (o.peek() === "#") { - o.read() - if (o.peek() === "|") { - tokenizeComment(o) - } - } - } - } - - function tokenize(o, once) { + function tokenize(o) { var c, s, r = [] var white = true while (o.has()) { c = o.peek() - if (c === " " || c === "\n") { - white = true - o.read() - } else if (c === "#") { - o.read() - if (o.peek() === "|") { - white = true - tokenizeComment(o) - o.read() - } else { - while (o.has() && o.peek() !== "\n") { - o.read() - } - } /*} else if (c === "-") { s = store(o) o.read() @@ -484,26 +492,29 @@ var NULAN = (function (n) { } else { r.push(enrich(t["u-"], s, o)) }*/ - } else if (c === "\"") { - white = false - r.push(tokenizeString(o)) - if (once) { - break - } + // TODO: multi-character tokenize and delimiter + if (t[c] && t[c].tokenize) { + // TODO: should probably pass in store somehow + s = store(o) + t[c].tokenize(c, o, function (x) { + r.push(enrich(x, s, o)) + }) + + white = !!t[c].whitespace } else if (t[c] && t[c].delimiter) { + // TODO: some small code duplication with tokenizeNumOrSym s = store(o) o.read() s = enrich(new n.Symbol(c), s, o) s.whitespace = white - white = !!t[c].whitespace r.push(s) + + white = !!t[c].whitespace } else { - // TODO: should use the whitespace property of the token - white = false - r.push(tokenizeNumOrSym(o)) - if (once) { - break - } + s = tokenizeNumOrSym(o) + s.whitespace = white // TODO: remove this + r.push(s) + white = false // TODO: remove this } } @@ -539,7 +550,7 @@ var NULAN = (function (n) { if (l.length === 0 && r.length === 0) { return [x] } else { - l = y.action(l, x, r) + l = y.parse(l, x, r) } } else { break @@ -552,6 +563,10 @@ var NULAN = (function (n) { return x instanceof n.Symbol && t[x.value] && t[x.value].separator } + function isVertical(x) { + return x instanceof n.Symbol && t[x.value] && t[x.value].vertical + } + function isEndAt(x) { return x instanceof n.Symbol && t[x.value] && t[x.value].endAt } @@ -595,7 +610,7 @@ var NULAN = (function (n) { /* syntax-rule ( braces ")" - action -> l s r + parse -> l s r `,@l ,r */ @@ -612,20 +627,24 @@ var NULAN = (function (n) { function indent(o, x) { var a = [] , y + , b , r while (o.has()) { y = o.peek() if (y.line === x.line) { - // TODO: figure out a way to generalize this - if (isSym(y, "|") && y.column === x.column) { + b = isSeparator(y) + if (isVertical(y)) { r = [] - while (o.has() && isSym(o.peek(), "|") && o.peek().column === y.column) { - o.read() - r.push(unwrap(indent(o, o.peek()))) + while (o.has() && isSym(o.peek(), y.value) && o.peek().column === y.column) { + if (b) { + o.read() + r.push(indent(o, o.peek())) + } else { + r.push.apply(r, indent(o, o.read())) + } } - r.unshift(y) // TODO: this needs to insert a box - a.push(r) - } else if (isSeparator(y)) { + a.push(y, r) + } else if (b) { o.read() a.push(y, indent(o, o.peek())) } else { diff --git a/notes/Customizable syntax.rst b/notes/Customizable syntax.rst index 1e55484..148fd12 100644 --- a/notes/Customizable syntax.rst +++ b/notes/Customizable syntax.rst @@ -11,7 +11,7 @@ To demonstrate how the system works, let's parse this Nulan program:: | if (n.left < r.left || n.right > r.right) p.scroll-left <= n.left - r.width / 2 -There are four phases to Nulan's syntax parsing: +There are three phases to Nulan's syntax parsing: 1) Tokenization. This phase splits a string into tokens. The end result is a flat 1-dimensional list of numbers, symbols, and strings:: @@ -19,13 +19,7 @@ There are four phases to Nulan's syntax parsing: The list has no structure to it, but Nulan keeps track of the line and column where each token was found. This will be very important later on. -2) The ``|`` token is handled specially. If it occurs at the start of the line, it will take all the lines that start with ``|`` at the same indentation and put them into a list:: - - def scroll-into-view -> n p w/var n = n . get-bounding-client-rect ; r = p . get-bounding-client-rect ; - {| {if ( n . top < r . top || n . bottom > r . bottom ) p . scroll-top <= n . top - r . height / 2} - {if ( n . left < r . left || n . right > r . right ) p . scroll-left <= n . left - r . width / 2}} - -3) Nulan uses significant whitespace, and has very simple rules for how to handle it: +2) Nulan uses significant whitespace, and has very simple rules for how to handle it: 1) Everything on the same line is put into a list:: @@ -58,12 +52,12 @@ There are four phases to Nulan's syntax parsing: {def scroll-into-view -> n p {w/var n = n . get-bounding-client-rect ; {r = p . get-bounding-client-rect ;} - {| {if ( n . top < r . top || n . bottom > r . bottom ) - {p . scroll-top <= n . top - r . height / 2}} - {if ( n . left < r . left || n . right > r . right ) - {p . scroll-left <= n . left - r . width / 2}}}}} + {| if ( n . top < r . top || n . bottom > r . bottom ) + {p . scroll-top <= n . top - r . height / 2}} + {| if ( n . left < r . left || n . right > r . right ) + {p . scroll-left <= n . left - r . width / 2}}}} -4) Now we have a structured program, with lists nested within lists. But we're not done yet. There's a bunch of symbols like ``=``, ``.``, and ``<`` that have special meaning, but they haven't been parsed yet. +3) Now we have a structured program, with lists nested within lists. But we're not done yet. There's a bunch of symbols like ``=``, ``.``, and ``<`` that have special meaning, but they haven't been parsed yet. Nulan has an object called ``syntax-rules`` which contains information on how to parse the remaining syntax. To create new syntax, you can use the ``$syntax-rule`` macro:: @@ -115,6 +109,37 @@ There are four phases to Nulan's syntax parsing: corge} yes} + * If ``vertical`` is true, the parser will scan vertically for the same symbol and will mush it into a single list:: + + $syntax-rule "^" [ + vertical %t + ] + + What this means is that this... + + :: + + foo ^ 1 2 3 + ^ 4 5 6 + ^ 7 8 9 + bar qux + + ...will be parsed into this:: + + {foo ^ {1 2 3 + 4 5 6 + 7 8 9} + {bar qux}} + + You will usually want to use ``separator`` at the same time, in which case it would be parsed like this:: + + {foo ^ {{1 2 3} + {4 5 6} + {7 8 9}} + {bar qux}} + + This is used for the ``|`` syntax. + * If ``endAt`` exists, it should be a string. The parser will search for a symbol that matches the string and will put everything between it and the original symbol into a list:: $syntax-rule "^" [ @@ -141,10 +166,42 @@ There are four phases to Nulan's syntax parsing: Left-associative (the default) means that ``foo ^ bar ^ qux`` is parsed as ``{{foo ^ bar} ^ qux}`` and right-associative means that it's parsed as ``{foo ^ {bar ^ qux}}`` - * The ``action`` property is a function that accepts three arguments: a list of everything to the left of the symbol, the symbol, and a list of everything to the right of the symbol:: + * The ``tokenize`` property is a function:: $syntax-rule "^" [ - action -> l s r + tokenize -> s o push + ... + ] + + When the tokenizer encounters "^" it will call the ``tokenize`` function with three arguments: + + 1) The first argument is the string "^" + + 2) The second argument is an iterator that contains all the characters remaining after the "^" character. Because this is handled by the tokenizer, it's just raw characters, there's no structure yet. It has the following methods: + + * ``has`` returns true if the iterator has any items remaining, otherwise false + * ``peek`` returns the next character in the iterator, but doesn't consume anything + * ``read`` returns the next character in the iterator, and consumes it + + 3) The third argument is a function that you can call to return a result. Here's an example of a rule that when given the string "^foo" will return "bar":: + + $syntax-rule "^" [ + tokenize -> s o push + if o.peek; == "f" + | o.read; + | if o.peek; == "o" + | o.read; + | if o.peek; == "o" + | o.read; + | push "bar" + ] + + The tokenize function is used for parsing whitespace, comments, and strings. + + * The ``parse`` property is a function that accepts three arguments: a list of everything to the left of the symbol, the symbol, and a list of everything to the right of the symbol:: + + $syntax-rule "^" [ + parse -> l s r ... ] @@ -152,12 +209,12 @@ There are four phases to Nulan's syntax parsing: foo bar ^ qux corge - When Nulan encounters ``^``, it will pass the arguments ``{foo bar}``, ``^``, and ``{qux corge}`` to the ``action`` function. Whatever the function returns is used as the final result. + When Nulan encounters ``^``, it will pass the arguments ``{foo bar}``, ``^``, and ``{qux corge}`` to the ``parse`` function. Whatever the function returns is used as the final result. - A typical infix operator is easy to define, it simply takes the last argument of the left list and the first argument of the right list and mushes them together:: + A typical infix operator is easy to define, it simply takes the last element of the left list and the first element of the right list and mushes them together:: $syntax-rule "^" [ - action -> {@l x} s {y @r} + parse -> {@l x} s {y @r} ',@l (s x y) ,@r ] @@ -168,7 +225,7 @@ There are four phases to Nulan's syntax parsing: Using the same system, unary is also easy:: $syntax-rule "^" [ - action -> l s {y @r} + parse -> l s {y @r} ',@l (s y) ,@r ] @@ -185,7 +242,7 @@ There are four phases to Nulan's syntax parsing: $syntax-rule "->" [ order "right" - action -> l s {@args body} + parse -> l s {@args body} ',@l (s args body) ] @@ -199,7 +256,7 @@ There are four phases to Nulan's syntax parsing: $syntax-rule "<=" [ order "right" - action -> l s r + parse -> l s r 's ,(unwrap l) ,(unwrap r) ] @@ -213,7 +270,7 @@ There are four phases to Nulan's syntax parsing: priority 110 delimiter %t endAt ")" - action -> l s {x @r} + parse -> l s {x @r} ',@l ,(unwrap x) ,@r ] @@ -221,7 +278,7 @@ There are four phases to Nulan's syntax parsing: priority 110 delimiter %t endAt "}" - action -> l s {x @r} + parse -> l s {x @r} ',@l (list ,@x) ,@r ] @@ -229,7 +286,7 @@ There are four phases to Nulan's syntax parsing: priority 110 delimiter %t endAt "]" - action -> {@l x} s {y @r} + parse -> {@l x} s {y @r} if s.whitespace ',@l x (dict ,@y) ,@r ',@l (. x ,(unwrap y)) ,@r @@ -238,22 +295,22 @@ There are four phases to Nulan's syntax parsing: $syntax-rule ";" [ priority 100 delimiter %t - action -> l s r - 'l ,@r + parse -> {@l x} s r + ',@l (x) ,@r ] $syntax-rule ":" [ priority 100 delimiter %t separator %t - action -> l s {x @r} + parse -> l s {x @r} ',@l x ,@r ] $syntax-rule "." [ priority 100 delimiter %t - action -> {@l x} s {y @r} + parse -> {@l x} s {y @r} if (num? x) && (num? y) ',@l ,(num: x + "." + y) ,@r if (sym? y) @@ -289,28 +346,28 @@ There are four phases to Nulan's syntax parsing: whitespace %t delimiter %t separator %t - action -> l s {x @r} + parse -> l s {x @r} ',@l (s ,(unwrap x)) ,@r ] $syntax-rule "->" [ priority 10 order "right" - action -> l s {@args body} + parse -> l s {@args body} ',@l (s args body) ] $syntax-rule "=" [ priority 10 separator %t - action -> {@l x} s {y @r} + parse -> {@l x} s {y @r} ',@l (s x ,(unwrap y)) ,@r ] $syntax-rule "<=" [ priority 0 order "right" - action -> l s r + parse -> l s r 's ,(unwrap l) ,(unwrap r) ] @@ -319,18 +376,67 @@ There are four phases to Nulan's syntax parsing: {def scroll-into-view -> n p {w/var n = n . get-bounding-client-rect ; {r = p . get-bounding-client-rect ;} - {| {if ( n . top < r . top || n . bottom > r . bottom ) - {p . scroll-top <= n . top - r . height / 2}} - {if ( n . left < r . left || n . right > r . right ) - {p . scroll-left <= n . left - r . width / 2}}}}} + {| if ( n . top < r . top || n . bottom > r . bottom ) + {p . scroll-top <= n . top - r . height / 2}} + {| if ( n . left < r . left || n . right > r . right ) + {p . scroll-left <= n . left - r . width / 2}}}} + + Let's use the built-in syntax to parse this. Firstly, let's run the ``parse`` function for ``->``:: + + {def scroll-into-view + {-> {n p} + {w/var n = n . get-bounding-client-rect ; + {r = p . get-bounding-client-rect ;} + {| if ( n . top < r . top || n . bottom > r . bottom ) + {p . scroll-top <= n . top - r . height / 2}} + {| if ( n . left < r . left || n . right > r . right ) + {p . scroll-left <= n . left - r . width / 2}}}}} + + Now the function for ``=``... + + :: + + {def scroll-into-view + {-> {n p} + {w/var {= n {n . get-bounding-client-rect ;}} + {r = p . get-bounding-client-rect ;} + {| if ( n . top < r . top || n . bottom > r . bottom ) + {p . scroll-top <= n . top - r . height / 2}} + {| if ( n . left < r . left || n . right > r . right ) + {p . scroll-left <= n . left - r . width / 2}}}}} + + Now the function for ``.``... + + :: + + {def scroll-into-view + {-> {n p} + {w/var {= n {{. n get-bounding-client-rect} ;}} + {r = p . get-bounding-client-rect ;} + {| if ( n . top < r . top || n . bottom > r . bottom ) + {p . scroll-top <= n . top - r . height / 2}} + {| if ( n . left < r . left || n . right > r . right ) + {p . scroll-left <= n . left - r . width / 2}}}}} + + Now the function for ``;``... + + :: + + {def scroll-into-view + {-> {n p} + {w/var {= n {{. n get-bounding-client-rect}}} + {r = p . get-bounding-client-rect ;} + {| if ( n . top < r . top || n . bottom > r . bottom ) + {p . scroll-top <= n . top - r . height / 2}} + {| if ( n . left < r . left || n . right > r . right ) + {p . scroll-left <= n . left - r . width / 2}}}}} - Let's use the built-in syntax to parse this:: + And such forth and so on. After all the syntax rule functions have been run, the end result is this:: {def scroll-into-view {-> {n p} - {w/var - {= n {{. n get-bounding-client-rect}}} - {= r {{. p get-bounding-client-rect}}} + {w/var {= n {{. n get-bounding-client-rect}}} + {= r {{. p get-bounding-client-rect}}} {| {if {|| {< {. n top} {. r top}} {> {. n bottom} {. r bottom}}} {<= {. p scroll-top} {- {. n top} {/ {. r height} 2}}}} {if {|| {< {. n left} {. r left}} {> {. n right} {. r right}}}