Skip to content

Instantly share code, notes, and snippets.

@gerardpaapu
Created May 2, 2012 05:26
Show Gist options
  • Save gerardpaapu/2574097 to your computer and use it in GitHub Desktop.
Save gerardpaapu/2574097 to your computer and use it in GitHub Desktop.
###
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
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)
###
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
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)
###
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
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)
###
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)
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
###
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
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)
{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