Skip to content

Instantly share code, notes, and snippets.

@dalehenrich
Created April 1, 2012 18:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dalehenrich/2277677 to your computer and use it in GitHub Desktop.
Save dalehenrich/2277677 to your computer and use it in GitHub Desktop.
simple JSON parser
'From Pharo1.3 of 16 June 2011 [Latest update: #13315] on 23 March 2012 at 9:24:50 am'!
Object subclass: #MCFileTreeJsonParser
instanceVariableNames: 'stream'
classVariableNames: ''
poolDictionaries: ''
category: 'MonticelloFileTree-Core'!
!MCFileTreeJsonParser methodsFor: 'adding' stamp: 'dkh 3/1/2012 16:36:43'!
addProperty: anAssociation to: anObject
"Add the property anAssociation described with key and value to anObject. Subclasses might want to refine this implementation."
^ anObject
add: anAssociation;
yourself! !
!MCFileTreeJsonParser methodsFor: 'adding' stamp: 'dkh 3/1/2012 16:36:43'!
addValue: anObject to: aCollection
"Add anObject to aCollection. Subclasses might want to refine this implementation."
^ aCollection copyWith: anObject! !
!MCFileTreeJsonParser methodsFor: 'creating' stamp: 'dkh 3/1/2012 16:36:43'!
createArray
"Create an empty collection. Subclasses might want to refine this implementation."
^ Array new! !
!MCFileTreeJsonParser methodsFor: 'creating' stamp: 'dkh 3/1/2012 16:36:43'!
createFalse
"Create the false literal. Subclasses might want to refine this implementation."
^ false! !
!MCFileTreeJsonParser methodsFor: 'creating' stamp: 'dkh 3/1/2012 16:36:43'!
createNull
"Create the null literal. Subclasses might want to refine this implementation."
^ nil! !
!MCFileTreeJsonParser methodsFor: 'creating' stamp: 'dkh 3/1/2012 16:36:43'!
createNumber: aString
"Create a number literal. Subclasses might want to refine this implementation."
^ aString asNumber! !
!MCFileTreeJsonParser methodsFor: 'creating' stamp: 'dkh 3/1/2012 16:36:43'!
createObject
"Create an empty object. Subclasses might want to refine this implementation."
^ Dictionary new! !
!MCFileTreeJsonParser methodsFor: 'creating' stamp: 'dkh 3/1/2012 16:36:43'!
createProperty: aKey with: aValue
"Create an empty attribute value pair. Subclasses might want to refine this implementation."
^ aKey -> aValue! !
!MCFileTreeJsonParser methodsFor: 'creating' stamp: 'dkh 3/1/2012 16:36:43'!
createString: aString
"Create a string literal. Subclasses might want to refine this implementation."
^ aString! !
!MCFileTreeJsonParser methodsFor: 'creating' stamp: 'dkh 3/1/2012 16:36:43'!
createTrue
"Create the true literal. Subclasses might want to refine this implementation."
^ true! !
!MCFileTreeJsonParser methodsFor: 'private' stamp: 'dkh 3/1/2012 16:36:43'!
error: aString
"Raise a parse error labelled aString."
^ JSJsonSyntaxError signal: aString! !
!MCFileTreeJsonParser methodsFor: 'private' stamp: 'dkh 3/1/2012 16:36:43'!
expect: aString
"Expects aString and consume input, throw an error otherwise."
^ (self match: aString) ifFalse: [ self error: aString , ' expected' ]! !
!MCFileTreeJsonParser methodsFor: 'private' stamp: 'dkh 3/1/2012 16:36:43'!
match: aString
"Tries to match aString, consume input and answer true if successful."
| position |
position := stream position.
aString do: [ :each |
(stream atEnd or: [ stream next ~= each ]) ifTrue: [
stream position: position.
^ false ] ].
self whitespace.
^ true! !
!MCFileTreeJsonParser methodsFor: 'private' stamp: 'dkh 3/1/2012 16:36:43'!
whitespace
"Strip whitespaces from the input stream."
[ stream atEnd not and: [ stream peek isSeparator ] ]
whileTrue: [ stream next ]! !
!MCFileTreeJsonParser methodsFor: 'initialization' stamp: 'dkh 3/1/2012 16:36:43'!
initializeOn: aStream
self initialize.
stream := aStream! !
!MCFileTreeJsonParser methodsFor: 'parsing' stamp: 'dkh 3/1/2012 16:36:43'!
parse
| result |
result := self whitespace; parseValue.
stream atEnd
ifFalse: [ self error: 'end of input expected' ].
^ result! !
!MCFileTreeJsonParser methodsFor: 'parsing' stamp: 'dkh 3/1/2012 16:36:43'!
parseArray
| result |
self expect: '['.
result := self createArray.
(self match: ']')
ifTrue: [ ^ result ].
[ stream atEnd ] whileFalse: [
result := self
addValue: self parseValue
to: result.
(self match: ']')
ifTrue: [ ^ result ].
self expect: ',' ].
self error: 'end of array expected'! !
!MCFileTreeJsonParser methodsFor: 'parsing' stamp: 'dkh 3/1/2012 16:36:43'!
parseObject
| result |
self expect: '{'.
result := self createObject.
(self match: '}')
ifTrue: [ ^ result ].
[ stream atEnd ] whileFalse: [
result := self
addProperty: self parseProperty
to: result.
(self match: '}')
ifTrue: [ ^ result ].
self expect: ',' ].
self error: 'end of object expected'! !
!MCFileTreeJsonParser methodsFor: 'parsing' stamp: 'dkh 3/1/2012 16:36:43'!
parseValue
| char |
stream atEnd ifFalse: [
char := stream peek.
char = ${
ifTrue: [ ^ self parseObject ].
char = $[
ifTrue: [ ^ self parseArray ].
char = $"
ifTrue: [ ^ self parseString ].
(char = $- or: [ char between: $0 and: $9 ])
ifTrue: [ ^ self parseNumber ].
(self match: 'true')
ifTrue: [ ^ self createTrue ].
(self match: 'false')
ifTrue: [ ^ self createFalse ].
(self match: 'null')
ifTrue: [ ^ self createNull ] ].
self error: 'invalid input'! !
!MCFileTreeJsonParser methodsFor: 'parsing-internal' stamp: 'dkh 3/1/2012 16:36:43'!
parseCharacter
| char |
(char := stream next) = $\
ifFalse: [ ^ char ].
(char := stream next) = $"
ifTrue: [ ^ char ].
char = $\
ifTrue: [ ^ char ].
char = $/
ifTrue: [ ^ char ].
char = $b
ifTrue: [ ^ Character backspace ].
char = $f
ifTrue: [ ^ Character newPage ].
char = $n
ifTrue: [ ^ Character lf ].
char = $r
ifTrue: [ ^ Character cr ].
char = $t
ifTrue: [ ^ Character tab ].
char = $u
ifTrue: [ ^ self parseCharacterHex ].
self error: 'invalid escape character \' , (String with: char)! !
!MCFileTreeJsonParser methodsFor: 'parsing-internal' stamp: 'dkh 3/1/2012 16:36:43'!
parseCharacterHex
| value |
value := self parseCharacterHexDigit.
3 timesRepeat: [ value := (value << 4) + self parseCharacterHexDigit ].
^ Character codePoint: value! !
!MCFileTreeJsonParser methodsFor: 'parsing-internal' stamp: 'dkh 3/1/2012 16:36:43'!
parseCharacterHexDigit
| digit |
stream atEnd ifFalse: [
digit := stream next greaseInteger.
(digit between: "$0" 48 and: "$9" 57)
ifTrue: [ ^ digit - 48 ].
(digit between: "$A" 65 and: "$F" 70)
ifTrue: [ ^ digit - 55 ].
(digit between: "$a" 97 and: "$f" 102)
ifTrue: [ ^ digit - 87 ] ].
self error: 'hex-digit expected'! !
!MCFileTreeJsonParser methodsFor: 'parsing-internal' stamp: 'dkh 3/1/2012 16:36:43'!
parseNumber
| negated number |
negated := stream peek = $-.
negated ifTrue: [ stream next ].
number := self parseNumberInteger.
(stream peek = $.) ifTrue: [
stream next.
number := number + self parseNumberFraction ].
(stream peek = $e or: [ stream peek = $E ]) ifTrue: [
stream next.
number := number * self parseNumberExponent ].
negated ifTrue: [ number := number negated ].
^ self whitespace; createNumber: number! !
!MCFileTreeJsonParser methodsFor: 'parsing-internal' stamp: 'dkh 3/1/2012 16:36:43'!
parseNumberExponent
| number negated |
number := 0.
negated := stream peek = $-.
(negated or: [ stream peek = $+ ]) ifTrue: [ stream next ].
[ stream atEnd not and: [ stream peek isDigit ] ]
whileTrue: [ number := 10 * number + (stream next greaseInteger - 48) ].
negated ifTrue: [ number := number negated ].
^ 10 raisedTo: number! !
!MCFileTreeJsonParser methodsFor: 'parsing-internal' stamp: 'dkh 3/1/2012 16:36:43'!
parseNumberFraction
| number power |
number := 0.
power := 1.0.
[ stream atEnd not and: [ stream peek isDigit ] ] whileTrue: [
number := 10 * number + (stream next greaseInteger - 48).
power := power * 10.0 ].
^ number / power! !
!MCFileTreeJsonParser methodsFor: 'parsing-internal' stamp: 'dkh 3/1/2012 16:36:43'!
parseNumberInteger
| number |
number := 0.
[ stream atEnd not and: [ stream peek isDigit ] ]
whileTrue: [ number := 10 * number + (stream next greaseInteger - 48) ].
^ number! !
!MCFileTreeJsonParser methodsFor: 'parsing-internal' stamp: 'dkh 3/1/2012 16:36:43'!
parseProperty
| name value |
name := self parseString.
self expect: ':'.
value := self parseValue.
^ self createProperty: name with: value.! !
!MCFileTreeJsonParser methodsFor: 'parsing-internal' stamp: 'dkh 3/1/2012 16:36:43'!
parseString
| result |
self expect: '"'.
result := WriteStream on: String new.
[ stream atEnd or: [ stream peek = $" ] ]
whileFalse: [ result nextPut: self parseCharacter ].
^ self expect: '"'; createString: result contents! !
"-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
MCFileTreeJsonParser class
instanceVariableNames: ''!
!MCFileTreeJsonParser class methodsFor: 'instance creation' stamp: 'dkh 3/1/2012 16:36:43'!
new
self error: 'Instantiate the parser with a stream.'! !
!MCFileTreeJsonParser class methodsFor: 'instance creation' stamp: 'dkh 3/1/2012 16:36:43'!
on: aStream
^ self basicNew initializeOn: aStream! !
!MCFileTreeJsonParser class methodsFor: 'accessing' stamp: 'dkh 3/1/2012 16:36:43'!
parse: aString
^ self parseStream: aString readStream! !
!MCFileTreeJsonParser class methodsFor: 'accessing' stamp: 'dkh 3/1/2012 16:36:43'!
parseStream: aStream
^ (self on: aStream) parse! !
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment