Created
May 2, 2012 05:26
-
-
Save gerardpaapu/2574097 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ### | |
| Accessors | |
| --------- | |
| An expression in Chitchat can be suffixed with an accessor | |
| An accessor is an accessor-operator followed by a key | |
| the three types of accessor-operators are: | |
| - pass-message, written as '.' | |
| - primitive-get, written as ':' | |
| - primitive-get-prototype, written as '::' | |
| the key is written either as an expression or a symbol | |
| - if written as a symbol, the string value of the symbol is the key | |
| - if written as an expression, it is an arbitrary chitchat expression | |
| delimited by '[' and ']' | |
| Except in a 'set' expression, an accessor is either translated to a message-pass | |
| or a get-property! call | |
| foo.bar -> (foo bar) | |
| foo.[bar] -> (foo getItem bar) | |
| foo:bar -> (get-property! foo 'bar') | |
| foo:[bar] -> (get-property! foo bar) | |
| foo::bar -> (get-property! foo:prototype 'bar') | |
| foo::[bar] -> (get-property! foo:prototype bar) | |
| get-property! is a primitive that compiles from `(get-property! foo 'bar')` | |
| to `foo['bar']` (roughly). | |
| Inside of a 'set' expression an accessor is transformed into either a | |
| primitive 'set' operation or the message 'set' passed with the appropriate= | |
| arguments. | |
| (set foo.bar baz) -> (foo set 'bar' baz) | |
| (set foo.[bar] baz) -> (foo setItem bar baz) | |
| (set foo:bar baz) -> (set-property! foo 'bar' baz) | |
| (set foo:[bar] baz) -> (set-property! foo bar baz) | |
| (set foo::bar baz) -> (set-property! foo:prototype 'bar' baz) | |
| (set foo::[bar] baz) -> (set-property! foo:prototype bar baz) | |
| set-property! is a primitive that compiles from `(set-property! foo 'bar' baz)` | |
| to `foo['bar'] = baz` (roughly). | |
| ### | |
| {Parser, SequenceIW, OR} = require './Parser' | |
| {symbolParser} = require './symbol' | |
| class DotAccessor | |
| constructor: (@root, @key) -> | |
| class PrimitiveAccessor | |
| constructor: (@root, @key) -> | |
| class PrototypeAccessor | |
| constructor: (@root, key) -> | |
| wrap = (Klass) -> | |
| (key) -> | |
| (root) -> new Klass(root, key) | |
| expressionParser = (simpleExpressionParser) -> | |
| ### | |
| given simple expression parser 's' | |
| this grammar is used over (expr := expr | expr rest) | |
| or similar because it's difficult to remove the | |
| left-side recursion without affecting the associativity | |
| exp := <s> ( <tail> )* | |
| tail := <op> <key> | |
| op := '.' | '::' | '.' | |
| key := <symbol> | '[' <exp> ']' | |
| ### | |
| simpleExpressionParser() | |
| .plus(suffixParser simpleExpressionParser) | |
| .convert (arr) -> | |
| ### | |
| then you have to unwrap the list, to restore | |
| left associativity. | |
| wrap root [] = root | |
| wrap root [x:xs] = wrap x(root) xs | |
| ### | |
| _wrap = (root, suffixes) -> | |
| unless root? and suffixes? | |
| throw new Error ('bad arguments to wrap') | |
| if suffixes.length is 0 | |
| root | |
| else | |
| [fn, rest...] = suffixes | |
| _wrap fn(root), rest | |
| [root, suffixes] = arr | |
| _wrap root, suffixes | |
| suffixParser = (expressionParser) -> | |
| OR( | |
| accessorParser('.', expressionParser).convert(wrap DotAccessor), | |
| accessorParser(':', expressionParser).convert(wrap PrimitiveAccessor), | |
| accessorParser('::', expressionParser).convert(wrap PrototypeAccessor) | |
| ).zeroOrMore() | |
| accessorParser = (operator, expressionParser) -> | |
| Parser(operator) | |
| .ignoreWhitespace() | |
| .and(keyParser expressionParser) | |
| keyParser = (simpleExpressionParser) -> | |
| expr = Parser.delay -> | |
| expressionParser(simpleExpressionParser) | |
| OR(symbolParser().convert((x) -> x.value), | |
| expr.surroundedBy('[', ']')) | |
| exports.expressionParser = expressionParser | |
| exports.DotAccessor = DotAccessor | |
| exports.PrimitiveAccessor = PrimitiveAccessor | |
| exports.PrototypeAccessor = PrototypeAccessor |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| vows = require 'vows' | |
| assert = require 'assert' | |
| {Parser} = require '../Parser' | |
| {expressionParser, DotAccessor, PrimitiveAccessor, PrototypeAccessor} = require '../accessors' | |
| simpleExpression = -> Parser.from '1' | |
| complexParser = expressionParser(simpleExpression) | |
| vows.describe('Parsing Numbers') | |
| .addBatch( | |
| 'When Parsing a simple expression': | |
| topic: -> complexParser.parse '1 1' | |
| 'Succeeds': (t) -> assert.ok t.succeeded | |
| 'With the correct value': (t) -> | |
| assert.equal t.value, '1' | |
| 'Leaving the remainder for the next parser': (t) -> | |
| assert.equal t.rest, ' 1' | |
| 'When Parsing a dot accessor w/ symbol': | |
| topic: -> complexParser.parse '1.foo 1' | |
| 'Succeeds': (t) -> assert.ok t.succeeded | |
| 'With the correct value': (t) -> | |
| assert.ok t.value instanceof DotAccessor | |
| assert.equal t.value.root, '1' | |
| assert.equal t.value.key, 'foo' | |
| 'Leaving the remainder for the next parser': (t) -> | |
| assert.equal t.rest, ' 1' | |
| 'When Parsing a dot accessor w/ expression': | |
| topic: -> | |
| complexParser.parse '1.[1] 1' | |
| 'Succeeds': (t) -> | |
| assert.ok(t and t.succeeded) | |
| 'With the correct value': (t) -> | |
| assert.ok t.value instanceof DotAccessor | |
| assert.equal t.value.root, '1' | |
| assert.equal t.value.key, '1' | |
| 'Leaving the remainder for the next parser': (t) -> | |
| assert.equal t.rest, ' 1' | |
| 'When Parsing a chained dot accessor ': | |
| topic: -> complexParser.parse '1.foo.bar 1' | |
| 'Succeeds': (t) -> assert.ok t.succeeded | |
| 'With the correct value': (t) -> | |
| assert.ok t.value instanceof DotAccessor | |
| assert.equal t.value.root.root, '1' | |
| assert.equal t.value.root.key, 'foo' | |
| assert.equal t.value.key, 'bar' | |
| 'Leaving the remainder for the next parser': (t) -> | |
| assert.equal t.rest.toString(), ' 1' | |
| 'When Parsing a dot accessor nested in a dot accessor': | |
| topic: -> complexParser.parse '1.[1.foo] 1' | |
| 'Succeeds': (t) -> assert.ok t.succeeded | |
| 'With the correct value': (t) -> | |
| assert.ok t.value instanceof DotAccessor | |
| assert.equal t.value.root, '1' | |
| assert.ok t.value.key instanceof DotAccessor | |
| assert.equal t.value.key.root, '1' | |
| assert.equal t.value.key.key, 'foo' | |
| 'Leaving the remainder for the next parser': (t) -> | |
| assert.equal t.rest, ' 1' | |
| ### | |
| 'When Parsing a primitive accessor w\\ symbol': | |
| topic: -> complexParser.parse '1:foo 1' | |
| 'Succeeds': (t) -> assert.ok t.succeeded | |
| 'With the correct value': (t) -> | |
| assert.equal t.value, '1' | |
| 'Leaving the remainder for the next parser': (t) -> | |
| assert.equal t.rest, ' 1' | |
| 'When Parsing a primitive accessor w\\ expression': | |
| topic: -> complexParser.parse '1:[1]' | |
| 'When Parsing a primitive accessor w\\ expression': | |
| topic: -> complexParser.parse '1:foo:bar' | |
| 'When Parsing a primitive accessor w\\ expression': | |
| topic: -> complexParser.parse '1:[1:foo]' | |
| 'When Parsing a prototype accessor w\\ symbol': | |
| topic: -> complexParser.parse '1::foo 1' | |
| 'When Parsing a prototype accessor w\\ expression': | |
| topic: -> complexParser.parse '1::[1]' | |
| 'When Parsing a prototype accessor w\\ expression': | |
| topic: -> complexParser.parse '1::foo::bar' | |
| 'When Parsing a prototype accessor w\\ expression': | |
| topic: -> complexParser.parse '1::[1::foo]' | |
| ### | |
| ).export(module) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ### | |
| Chitchat Arrays | |
| --------------- | |
| Chitchat arrays start with '#[' and end with ']' | |
| and contain zero or more Chitchat expressions | |
| separated by whitespace | |
| ### | |
| {Parser} = require './Parser' | |
| arrayParser = (parser) -> | |
| Parser.from(parser) | |
| .separatedByWhitespace() | |
| .surroundedByIW('#[', ']') | |
| exports.arrayParser = arrayParser | |
| ### | |
| Chitchat Dictionaries | |
| --------------------- | |
| Chitchat dictionaries | |
| - Start with '#{' | |
| - contain key-value pairs | |
| - Ends with '}' | |
| A key-value pair | |
| - starts with a string-literal | |
| - followed by a ':' | |
| - ends with a chitchat value | |
| ### | |
| {stringParser} = require './stringParser' | |
| {Sequence} = require './Parser' | |
| associate = (pairs) -> | |
| # convert from [[key, value], ...] -> { key: value, ...} | |
| table = {} | |
| for [key, value] in pairs | |
| if table[key]? | |
| throw new SyntaxError("'#{key}' already defined") | |
| table[key] = value | |
| table | |
| dictionaryParser = (parser) -> | |
| separator = Parser(':').ignoreWhitespace() | |
| keyValuePair = -> | |
| stringParser() | |
| .followedBy(separator) | |
| .plus(parser) | |
| keyValuePair() | |
| .separatedByWhitespace() | |
| .surroundedByIW('#{', '}') | |
| .convert(associate) | |
| exports.dictionaryParser = dictionaryParser |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| vows = require 'vows' | |
| assert = require 'assert' | |
| {arrayParser, dictionaryParser} = require '../collections' | |
| vows.describe('Parsing arrays') | |
| .addBatch( | |
| 'When parsing an empty Array': | |
| topic: -> arrayParser('1').parse('#[] rest') | |
| 'It succeeds': (t) -> assert.ok not t.failed | |
| 'With the correct value': (t) -> | |
| assert.equal JSON.stringify(t.value), '[]' | |
| 'Leaving the remainder for the next parser': (t) -> | |
| assert.equal t.rest, ' rest' | |
| 'When parsing a non-empty array': | |
| topic: -> arrayParser('1').parse '#[1 1 1] rest' | |
| 'It succeeds': (t) -> assert.ok not t.failed | |
| 'With the correct value': (t) -> | |
| assert.equal JSON.stringify(t.value), '["1","1","1"]' | |
| 'Leaving the remainder for the next parser': (t) -> | |
| assert.equal t.rest, ' rest' | |
| ) | |
| .addBatch( | |
| 'When parsing an empty dictionary': | |
| topic: -> dictionaryParser('1').parse('#{} rest') | |
| 'It succeeds': (t) -> assert.ok not t.failed | |
| 'With the correct value': (t) -> | |
| assert.equal JSON.stringify(t.value), '{}' | |
| 'Leaving the remainder for the next parser': (t) -> | |
| assert.equal t.rest.toString(), ' rest' | |
| 'When parsing an non-empty dictionary': | |
| topic: -> dictionaryParser('1').parse('#{"poop": 1 "cat": 1} rest') | |
| 'It succeeds': (t) -> assert.ok not t.failed | |
| 'With the correct value': (t) -> | |
| assert.equal JSON.stringify(t.value), '{"poop":"1","cat":"1"}' | |
| 'Leaving the remainder for the next parser': (t) -> | |
| assert.equal t.rest.toString(), ' rest' | |
| ) | |
| .export(module) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ### | |
| Parsing Numbers | |
| ------- | |
| We allow four types of number literals | |
| - JSON style decimal literals (except leading zeros are legal) | |
| - Hexadecimal integers start with '0x' followed by 0-9 a-f (case insensitive) | |
| - Binary integers start with '0b' followed by '0' or '1' | |
| - Octal integers start with '0o' followed by [0-7]+ | |
| ### | |
| {Parser, OR, Maybe} = require './Parser' | |
| jsonNumberParser = -> | |
| Parser.Sequence( | |
| Maybe('-', ''), | |
| /^[0-9]+/, | |
| Maybe(/^\.[0-9]+/, ''), | |
| Maybe(/^e(\+|\-)?[0-9]+/i, '') | |
| ).convert((x) -> parseFloat(x.join(''), 10)) | |
| binaryLiteralParser = -> | |
| new Parser.RegExp(/^0b([0-1]+)/, 1) | |
| .convert((x) -> parseInt(x, 2)) | |
| hexLiteral = -> | |
| new Parser.RegExp(/^0x([0-9a-f]+)/i, 1) | |
| .convert((x) -> parseInt(x, 16)) | |
| octalLiteral = -> | |
| new Parser.RegExp(/^0o([0-7]+)/, 1) | |
| .convert((x) -> parseInt(x, 8)) | |
| numberParser = -> | |
| OR binaryLiteralParser(), hexLiteral(), octalLiteral(), jsonNumberParser() | |
| exports.numberParser = numberParser |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| vows = require 'vows' | |
| assert = require 'assert' | |
| {numberParser} = require '../numberParser' | |
| {Port} = require '../Port' | |
| vows.describe('Parsing Numbers') | |
| .addBatch( | |
| 'When Parsing a binary': | |
| topic: -> numberParser().parse new Port('0b10101 rest') | |
| 'Succeeds': (t) -> assert.ok not t.failed | |
| 'With the correct value': (t) -> | |
| assert.equal t.value, parseInt('10101', 2) | |
| 'Leaving the remainder for the next parser': (t) -> | |
| assert.equal t.rest, ' rest' | |
| 'When Parsing an octal': | |
| topic: -> numberParser().parse new Port('0o777 rest') | |
| 'Succeeds': (t) -> assert.ok not t.failed | |
| 'With the correct value': (t) -> | |
| assert.equal t.value, parseInt('777', 8) | |
| 'Leaving the remainder for the next parser': (t) -> | |
| assert.equal t.rest, ' rest' | |
| 'When Parsing a hexadecimal': | |
| topic: -> numberParser().parse new Port('0xbada55 rest') | |
| 'Succeeds': (t) -> assert.ok not t.failed | |
| 'With the correct value': (t) -> | |
| assert.equal t.value, 0xbada55 | |
| 'Leaving the remainder for the next parser': (t) -> | |
| assert.equal t.rest, ' rest' | |
| 'When Parsing a simple integer': | |
| topic: -> numberParser().parse new Port('8989923 rest') | |
| 'Succeeds': (t) -> assert.ok not t.failed | |
| 'With the correct value': (t) -> | |
| assert.equal t.value, 8989923 | |
| 'Leaving the remainder for the next parser': (t) -> | |
| assert.equal t.rest, ' rest' | |
| 'When Parsing a float': | |
| topic: -> numberParser().parse new Port('04.89763 rest') | |
| 'Succeeds': (t) -> assert.ok not t.failed | |
| 'With the correct value': (t) -> | |
| assert.equal t.value, parseFloat('04.89763', 10) | |
| 'Leaving the remainder for the next parser': (t) -> | |
| assert.equal t.rest, ' rest' | |
| 'When Parsing a float with Exponent': | |
| topic: -> numberParser().parse new Port('4.2e100 rest') | |
| 'Succeeds': (t) -> assert.ok not t.failed | |
| 'With the correct value': (t) -> | |
| assert.equal t.value, parseFloat('4.2e100', 10) | |
| 'Leaving the remainder for the next parser': (t) -> | |
| assert.equal t.rest, ' rest' | |
| 'When parsing a non-number': | |
| topic: -> numberParser().parse new Port('not a number') | |
| 'Fails': (t) -> assert.ok t.failed | |
| ).export(module) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ### | |
| A Parser from A to B is a function that takes A and returns | |
| a Parsing Result where the 'value' is a Tree of B and the 'rest' | |
| is a B | |
| e.g. a Parser<String, Syntax> will consume a string and return a Parsing | |
| Result that contains a Tree of Syntax and a String of the Remainder to | |
| Parse. | |
| Parser<A, B> := Function<A, ParsingResult<B, A>> | |
| ParsingResult<B, A> := { Tree<B> value, A rest } | |
| ### | |
| type = (obj) -> | |
| switch obj | |
| when null then 'Null' | |
| when undefined then 'Undefined' | |
| else | |
| Object::toString.call(obj).slice(8, -1) | |
| prepend = (item, array) -> | |
| throw new TypeError unless type(array) is 'Array' | |
| [ item ].concat(array) | |
| class Parser | |
| constructor: (arg) -> | |
| if arguments.length > 0 | |
| return Parser.from arg | |
| # A class to wrap a Function<string, ParseResult> | |
| # because of my OO religion | |
| parse: (input) -> | |
| throw new Error 'Not Implemented' | |
| # 'parse' should be a Function<string, ParseResult> | |
| @wrap: (parse) -> | |
| parser = new Parser() | |
| parser.parse = (input) -> | |
| input = Port.from input | |
| parse(input) | |
| return parser | |
| # Shortcuts for defining the most common | |
| # simple parser types | |
| @from: (obj) -> | |
| return obj if obj instanceof Parser | |
| switch type obj | |
| when 'String' | |
| new Parser.Exactly obj | |
| when 'RegExp' | |
| new Parser.RegExp obj | |
| when 'Function' | |
| Parser.wrap obj | |
| when 'Array' | |
| Parser.Sequence obj... | |
| else | |
| throw new TypeError("wtf is #{obj}") | |
| exports.Parser = Parser | |
| class ParseResult | |
| constructor: (@value, @rest) -> | |
| throw 'no value' unless @value? | |
| throw 'no string' unless @rest? | |
| failed: false | |
| succeeded: true | |
| bind: (_function) -> | |
| _function(@value, @rest) | |
| class ParseFailure extends ParseResult | |
| constructor: (@input) -> | |
| failed: true | |
| succeeded: false | |
| bind: (makeParser) -> | |
| new ParseFailure | |
| toString: -> | |
| "<ParseFailure @ #{ @input.location() }>" | |
| # Primitive Parsers (Result, Fail and Item) | |
| class Parser.Result extends Parser | |
| constructor: (@value) -> | |
| parse: (input) -> | |
| new ParseResult(@value, input) | |
| class Parser.Fail extends Parser | |
| parse: (input) -> | |
| new ParseFailure(input) | |
| {Port} = require './Port' | |
| class Parser.Item extends Parser | |
| parse: (input) -> | |
| input = Port.from(input) | |
| if !input.isEmpty() | |
| new ParseResult(input.take(1), input.drop(1)) | |
| else | |
| new ParseFailure(input) | |
| # Simple Matching Parsers | |
| class Parser.Exactly extends Parser | |
| constructor: (@str) -> | |
| parse: (input) -> | |
| input = Port.from(input) | |
| len = @str.length | |
| chunk = input.take(len) | |
| rest = input.drop(len) | |
| if chunk is @str | |
| new ParseResult(@str, rest) | |
| else | |
| new ParseFailure(input) | |
| cloneRegexp = (source) -> | |
| destination = new RegExp(source.source) | |
| destination.global = source.global | |
| destination.ignoreCase = source.ignoreCase | |
| destination.multiline = source.multiline | |
| return destination | |
| class Parser.RegExp extends Parser | |
| constructor: (pattern, index) -> | |
| @_pattern = pattern | |
| @index = index ? 0 | |
| getPattern: -> | |
| # clone the RegExp each time we use it | |
| # because JS RegExp objects are stateful | |
| # and we don't want that baggage | |
| cloneRegexp @_pattern | |
| parse: (input) -> | |
| input = Port.from(input) | |
| match = @getPattern().exec(input.slice()) | |
| if match? | |
| val = match[@index] | |
| new ParseResult(val, input.drop(match[0].length)) | |
| else | |
| new ParseFailure(input) | |
| Parser::then = (makeParser) -> | |
| Parser.wrap (input) => | |
| @parse(input).bind (value, rest) -> | |
| makeParser(value).parse(rest) | |
| Parser.Satisfies = (predicate) -> | |
| new Parser.Item().then (x) -> | |
| if predicate(x) | |
| new Parser.Result(x) | |
| else | |
| new Parser.Fail() | |
| ParseResult::otherwise = (_function) -> | |
| this | |
| ParseFailure::otherwise = (_function) -> | |
| _function() | |
| Parser::inverse = (makeParser) -> | |
| Parser.wrap (input) => | |
| @parse(input).otherwise () -> | |
| makeParser().parse(input) | |
| OR = (parser, rest...) -> | |
| Parser.wrap (input) -> | |
| parser = Parser.from(parser) | |
| result = parser.parse(input) | |
| unless result instanceof ParseFailure | |
| return result | |
| if rest.length is 0 | |
| new ParseFailure(input) | |
| else | |
| OR(rest...).parse(input) | |
| Parser::or = (parsers...) -> | |
| OR(this, parsers...) | |
| exports.OR = OR | |
| Maybe = (parser, _default) -> | |
| Parser.wrap (input) -> | |
| parser = Parser.from(parser) | |
| result = parser.parse(input) | |
| if result instanceof ParseFailure | |
| new ParseResult(_default, input) | |
| else | |
| result | |
| exports.Maybe = Maybe | |
| Parser::maybe = (_default) -> | |
| Maybe this, _default | |
| NOT = (parser) -> | |
| Parser.wrap (input) -> | |
| parser = Parser.from(parser) | |
| result = parser.parse input | |
| if result instanceof ParseFailure | |
| new ParseResult(true, input) | |
| else | |
| new ParseFailure(input) | |
| AND = (parser, rest...) -> | |
| Parser.from(parser).then (value) -> | |
| if rest.length is 0 | |
| new Parser.Result(value) | |
| else | |
| AND(rest...) | |
| Parser::and = (parsers...) -> | |
| AND this, parsers... | |
| DO = (table) -> | |
| cloneEnv = (obj) -> | |
| table = {} | |
| for key, value of obj | |
| table[key] = value | |
| table | |
| returns = table.returns | |
| delete table.returns | |
| throw new TypeError() unless returns? | |
| pairs =([key, value] for key, value of table) | |
| env = {} | |
| _DO = (pairs, env) -> | |
| if pairs.length is 0 | |
| new Parser.Result returns.call env | |
| else | |
| [first, rest...] = pairs | |
| [key, _function] = first | |
| throw new TypeError unless _function? | |
| parser = _function.call(env) | |
| unless parser? | |
| throw new TypeError "bad parser @ #{key}" | |
| Parser.from(parser).then (value) -> | |
| env[key] = value | |
| _DO(rest, cloneEnv(env)) | |
| Parser.from (input) -> | |
| _DO(pairs, cloneEnv(env)).parse(input) | |
| exports.DO = DO | |
| OneOrMore = (parser) -> | |
| DO | |
| first: -> parser | |
| rest: -> OneOrMore(parser).maybe([]) | |
| returns: -> | |
| prepend(@first, @rest) | |
| Parser::oneOrMore = -> | |
| OneOrMore this | |
| ZeroOrMore = (parser) -> | |
| OneOrMore(parser).maybe([]) | |
| Parser::zeroOrMore = -> | |
| ZeroOrMore this | |
| ignoreWhitespace = (parser) -> | |
| DO | |
| leading: -> /^\s*/m, | |
| body: -> parser | |
| trailing: -> /^\s*/m | |
| returns: -> @body | |
| Parser::ignoreWhitespace = -> | |
| ignoreWhitespace this | |
| Sequence = Parser.Sequence = (parser, rest...) -> | |
| if rest.length is 0 | |
| return Parser.from(parser).convert (x) -> [x] | |
| DO | |
| first: -> parser | |
| rest: -> Sequence rest... | |
| returns: -> | |
| prepend(@first, @rest) | |
| SequenceIW = Parser.SequenceIW = (parse, rest...) -> | |
| if rest.length is 0 | |
| return Parser.from(parser) | |
| .ignoreWhitespace() | |
| .convert((x) -> [x]) | |
| DO | |
| first: -> parser.ignoreWhitespace() | |
| rest: -> SequenceIW rest... | |
| returns: -> | |
| prepend(@first, @rest) | |
| Parser.Sequence = Sequence | |
| IS = (parser, predicate) -> | |
| parser.then (value) -> | |
| if predicate value | |
| new Parser.Result value | |
| else | |
| new Parser.Fail() | |
| ISNT = (parser, predicate) -> | |
| parser.then (value) -> | |
| if predicate value | |
| new Parser.Fail() | |
| else | |
| new Parser.Result(value) | |
| Parser::is = (predicate) -> | |
| IS this, predicate | |
| Parser::isnt = (predicate) -> | |
| ISNT this, predicate | |
| Parser::convert = (converter) -> | |
| @then (value) -> | |
| new Parser.Result converter(value) | |
| Parser::convertTo = (Klass) -> | |
| @then (value) -> | |
| new Parser.Result new Klass(value) | |
| Parser::surroundedBy = (open, close) -> | |
| parser = this | |
| DO | |
| open: -> open | |
| body: -> parser | |
| close: -> close | |
| returns: -> @body | |
| Parser::surroundedByIW = (_open, _close) -> | |
| open = Parser.from(_open).ignoreWhitespace() | |
| # don't take whitespace following the close | |
| close = Parser.from(_close).precededBy(/\s*/m) | |
| @surroundedBy(open, close) | |
| class Parser.Trace extends Parser | |
| constructor: (parser) -> | |
| @parser = Parser.from parser | |
| parse: (input) -> | |
| console.log "TRACE (before): #{input[0..10]}" | |
| @parser.parse(input).bind (value, input) -> | |
| console.log "TRACE (value): #{value} " | |
| console.log "TRACE (after): #{input[0..10]}" | |
| new ParseResult value, input | |
| Parser::trace = -> new Parser.Trace this | |
| class Parser.WrapWithLocation extends Parser | |
| constructor: (parser, @Klass) -> | |
| @parser = Parser.from parser | |
| parse: (input) -> | |
| input = Port.from input | |
| start = input.location() | |
| Klass = @Klass | |
| @parser.parse(input).then (value, input) -> | |
| end = input.location() | |
| _value = new Klass(value, start, end) | |
| new ParseResult _value, input | |
| Parser::wrapWithLocation = (Klass) -> | |
| new Parser.WrapWithLocation this, Klass | |
| Parser::separatedBy = (comma) -> | |
| parser = this | |
| _comma = Parser.from(comma) | |
| (DO | |
| first: -> parser | |
| rest: -> _comma.and(parser).zeroOrMore() | |
| returns: -> | |
| prepend(@first, @rest) | |
| ).maybe([]) | |
| Parser::separatedByIW = (_comma) -> | |
| comma = Parser.from(_comma).ignoreWhitespace() | |
| @separatedBy(comma).ignoreWhitespace() | |
| Parser::separatedByWhitespace = -> | |
| @separatedBy /^\s+/m | |
| Parser::followedBy = (suffix) -> | |
| parser = this | |
| DO | |
| x: -> parser | |
| _: -> suffix | |
| returns: -> @x | |
| Parser::notFollowedBy = (suffix) -> | |
| @then (value) -> | |
| Parser.wrap (input) -> | |
| result = suffix.parse(input) | |
| if result.failed | |
| new ParseResult value, input | |
| else | |
| new ParseFailure(input) | |
| Parser::precededBy = (prefix) -> | |
| parser = this | |
| DO | |
| _: -> prefix | |
| x: -> parser | |
| returns: -> @x | |
| Parser::skipWhitespace = -> | |
| @followedBy /^\s*/m | |
| Parser::plus = (parser) -> | |
| Sequence this, parser | |
| Parser.delay = (makeParser) -> | |
| Parser.wrap (input) -> | |
| makeParser().parse(input) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class Port | |
| # A wrapper for a string, to support multiple readers | |
| # navigating over a single string and human readable | |
| # source location information | |
| constructor: (@source, @_index=0) -> | |
| @from: (str) -> | |
| if str instanceof Port | |
| str | |
| else | |
| new Port(str) | |
| take: (n) -> | |
| @slice(0, n) | |
| drop: (n) -> | |
| @move(n) | |
| isEmpty: -> | |
| @source.length <= @_index | |
| move: (n) -> | |
| new Port(@source, @_index + n) | |
| index: (n) -> | |
| if n < 0 then n else @_index + n | |
| slice: (a, b) -> | |
| switch arguments.length | |
| when 0 then @source.slice @index(0) | |
| when 1 then @source.slice @index(a) | |
| else @source.slice @index(a), @index(b) | |
| location: -> | |
| row = 0 | |
| column = 0 | |
| while i < @_index | |
| switch @source[i++] | |
| when '\n' | |
| column = 0 | |
| row++ | |
| else | |
| column++ | |
| new Location(row, column) | |
| toString: -> @slice(0) | |
| exports.Port = Port | |
| class Location | |
| constructor: (@row, @column) -> | |
| exports.Location = Location |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ### | |
| Parsing a String | |
| ---------------- | |
| a JSON string is zero or more characters surrounded | |
| by double quotes. | |
| characters are either: | |
| - unescaped (not '\' or '"') | |
| - escaped characters starting with '\' | |
| escaped characters are either: | |
| - '\u' followed by 4 hex digits (a unicode code point) | |
| - '\"', followed by a single character (from the escape table) | |
| our strings are JSON strings except that we allow either | |
| single quotes or double quotes and we allow for quoting | |
| of single quotes (i.e. we added "'" to our escape table) | |
| ### | |
| {Parser, OR} = require './Parser' | |
| _stringParser = (quote) -> | |
| backslash = '\\' | |
| characterParser = -> | |
| OR escaped(), unescaped() | |
| unescaped = -> | |
| new Parser.Item().isnt (x) -> | |
| x is quote or x is backslash | |
| escapeTable = | |
| '"': '"' | |
| '\'': '\'' | |
| '\\': '\\' | |
| '/': '/' | |
| 'b': '\b' | |
| 'f': '\f' | |
| 'n': '\n' | |
| 'r': '\r' | |
| 't': '\t' | |
| unicodeSeq = -> | |
| Parser(['\\u', /^[a-f0-9]{4}/i]) | |
| # discard the '\u' | |
| .convert((x) -> x[1]) | |
| .convert (code) -> | |
| String.fromCharCode parseInt(code, 16) | |
| simpleEscape = -> | |
| Parser(['\\', new Parser.Item() ]) | |
| # discard the leading slash | |
| .convert((arr) -> arr[1]) | |
| # fail unless it is in the table | |
| .is((code) -> escapeTable[code]?) | |
| .convert((code) -> escapeTable[code]) | |
| escaped = -> | |
| OR unicodeSeq(), simpleEscape() | |
| characterParser() | |
| .zeroOrMore() | |
| .surroundedBy(quote, quote) | |
| .convert (arr) -> arr.join('') | |
| stringParser = -> | |
| OR(_stringParser('\''), | |
| _stringParser('\"')) | |
| exports.stringParser = stringParser |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| vows = require 'vows' | |
| assert = require 'assert' | |
| {stringParser} = require '../stringParser' | |
| vows.describe('Parsing Strings') | |
| .addBatch( | |
| 'When parsing an empty string': | |
| topic: -> stringParser().parse '"" rest' | |
| 'It succeeds': (t) -> assert.ok not t.failed | |
| 'With the correct value': (t) -> assert.equal t.value, '' | |
| 'Leaving the remainder for the next parse': (t) -> | |
| assert.equal t.rest,' rest' | |
| 'When parsing a unicode literal': | |
| topic: -> stringParser().parse '"\\u99ff" rest' | |
| 'It succeeds': (t) -> assert.ok not t.failed | |
| 'With the correct value': (t) -> | |
| assert.equal t.value, '\u99ff' | |
| 'Leaving the remainder for the next parse': (t) -> | |
| assert.equal t.rest, ' rest' | |
| 'When parsing a non-string': | |
| topic: -> stringParser().parse 'lolercoasters' | |
| 'It fails': (t) -> assert.ok t.failed | |
| ).export(module) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| {Parser} = require './Parser' | |
| class Symbol | |
| constructor: (@value) -> | |
| symbolParser = -> | |
| Parser | |
| .from(/^[a-zA-Z\-_+=$&%@!?~`<>|][0-9a-zA-Z\-_+=$&%@!?~`<>|]*/) | |
| .convertTo Symbol | |
| exports.Symbol = Symbol | |
| exports.symbolParser = symbolParser |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment