[squeak-dev] The Inbox: JSON-ul.47.mcz

commits at source.squeak.org commits at source.squeak.org
Sun Feb 11 21:08:58 UTC 2018


Levente Uzonyi uploaded a new version of JSON to project The Inbox:
http://source.squeak.org/inbox/JSON-ul.47.mcz

==================== Summary ====================

Name: JSON-ul.47
Author: ul
Time: 22 January 2018, 12:32:52.716166 am
UUID: f2d44ea2-c2a1-4da0-9dec-5a3b1823ab3b
Ancestors: JSON-ul.46, JSON-klub.46

- merged with JSON-klub.46
- ScaledDecimal>>jsonWriteOn: rounds the number instead of truncating it

=============== Diff against JSON-tonyg.39 ===============

Item was changed:
  Object subclass: #Json
+ 	instanceVariableNames: 'stream currentCharacter arrayBufferStream stringBufferStream numberParser ctorMap'
- 	instanceVariableNames: 'stream ctorMap'
  	classVariableNames: ''
  	poolDictionaries: ''
  	category: 'JSON'!
  Json class
+ 	instanceVariableNames: 'escapeArray escapeSet'!
- 	instanceVariableNames: 'escapeArray'!
  
+ !Json commentStamp: 'ul 3/29/2016 04:38' prior: 0!
+ I support reading and writing JSON (see http://json.org) formatted data - strings, numbers, boolean, nil, arrays and dictionaries.
+ The implementation is mainly based on RFC 7159 (https://www.ietf.org/rfc/rfc7159.txt). It has been extended with syntax for invoking a prearranged list of constructors on read objects.
+ Character encoding is not handled here. That's something you have to do before reading or after writing. Mixed processing is possible, but it's entirely your responsibility to keep things correct.
+ 
+ Instance Variables
+ 	stream:				<PositionableStream>
+ 	currentCharacter:	<Character>
+ 	arrayBufferStream:	<WriteStream>
+ 	stringBufferStream: <ReadWriteStream>
+ 	numberParser: <ExtendedNumberParser>
+ 	ctorMap: <Dictionary>
+ 
+ For parsing, stream has to be initialized by sending #readFrom: to my instance. The parser will pre-read one character, which will be stored in currentCharacter, so the stream is expected to support position manipulation, hence it should be a PositionableStream.
+ arrayBufferStream is WriteStream on an Array, which holds the values during array parsing (see #readArray). When there's a recursive call, this stream is used as a stack to separate the elements parsed in the previous invocation from the current one.
+ stringBufferStream is a ReadWriteStream on a String, which holds the currently parsed String (see #readString), the currently parsed number (see #readNumber) or the name of the currently parsed constructor (see #readConstructor). Recursion has no effect on this.
+ numberParser is a cached instance of ExtendedNumberParser. It's initialized on stringBufferStream, so it can quickly parse the number stored in it.
+ ctorMap is the Dictionary holding the constructor classes by name.
+ 
+ arrayBufferStream, stringBufferStream and numberParser are initialized lazily, and should not be accessed externally. Since these are shared objects, along with stream and currentCharacter, holding state, therefore a single instance of me shouldn't be used to parse multiple streams at the same time.
+ 
+ For writing, you can use the methods in the class-side rendering category. To convert individual objects to a string, you can use #asJsonString, to serialize it on a stream, you can use #jsonWriteOn:.
+ !
- !Json commentStamp: '<historical>' prior: 0!
- This class reads and writes JSON format data - strings, numbers, boolean, nil, arrays and dictionaries. See http://www.crockford.com/JSON/index.html. It has been extended with syntax for invoking a prearranged list of constructors on read objects.!
  Json class
+ 	instanceVariableNames: 'escapeArray escapeSet'!
- 	instanceVariableNames: 'escapeArray'!

Item was changed:
+ ----- Method: Json class>>escapeForCharacter: (in category 'accessing') -----
- ----- Method: Json class>>escapeForCharacter: (in category 'as yet unclassified') -----
  escapeForCharacter: c
  	
  	| asciiValue |
  	(asciiValue := c asciiValue) < 128
  		ifTrue: [ ^escapeArray at: asciiValue + 1 ].
  	^nil!

Item was added:
+ ----- Method: Json class>>escapeSet (in category 'accessing') -----
+ escapeSet 
+ 
+ 	^escapeSet!

Item was changed:
+ ----- Method: Json class>>initialize (in category 'class initialization') -----
- ----- Method: Json class>>initialize (in category 'as yet unclassified') -----
  initialize
  	"Json initialize."
  	
+ 	| newEscapeArray newEscapeSet |
+ 	newEscapeArray := Array new: 128.
+ 	newEscapeSet := CharacterSet new.
- 	escapeArray := Array new: 128.
  	(0 to: 31), #(127) do: [ :each |
+ 		newEscapeArray at: each + 1 put: '\u', (each printStringHex padded: #left to: 4 with: $0).
+ 		newEscapeSet add: (Character value: each) ].
- 		escapeArray at: each + 1 put: '\u', (each printStringHex padded: #left to: 4 with: $0) ].
  	{
  		$" -> '\"'.
  		$\ -> '\\'.
+ 		$/ -> '\/'. "A hack, so the generated JSON will always be HTML+Javascript compatible, because the generated JSON will not contain the seqence </script>."
  		Character backspace -> '\b'.
  		Character lf -> '\n'.
  		Character newPage -> '\f'.
  		Character cr -> '\r'.
  		Character tab -> '\t'.
  	} do: [ :each |
+ 		newEscapeArray at: each key asciiValue + 1 put: each value.
+ 		newEscapeSet add: each key ].
+ 	escapeArray := newEscapeArray.
+ 	escapeSet := newEscapeSet!
- 		escapeArray at: each key asciiValue + 1 put: each value ].
- !

Item was changed:
+ ----- Method: Json class>>mimeType (in category 'accessing') -----
- ----- Method: Json class>>mimeType (in category 'as yet unclassified') -----
  mimeType
  	^ 'application/x-json'!

Item was changed:
+ ----- Method: Json class>>newWithConstructors: (in category 'instance creation') -----
- ----- Method: Json class>>newWithConstructors: (in category 'as yet unclassified') -----
  newWithConstructors: aCollection
  	| m |
  	m := Dictionary new.
  	aCollection do: [:each |
  		(each isKindOf: Association)
  			ifTrue: [m add: each]
  			ifFalse: [m at: each name asString put: each]].
  	^ self new ctorMap: m; yourself.!

Item was removed:
- ----- Method: Json class>>numbersMayContain: (in category 'as yet unclassified') -----
- numbersMayContain: aChar
- 	^ aChar isDigit or: [#($- $+ $. $e $E) includes: aChar]!

Item was changed:
+ ----- Method: Json class>>readFrom: (in category 'instance creation') -----
- ----- Method: Json class>>readFrom: (in category 'as yet unclassified') -----
  readFrom: aStream
  	^ self new readFrom: aStream.!

Item was changed:
+ ----- Method: Json class>>render: (in category 'rendering') -----
- ----- Method: Json class>>render: (in category 'as yet unclassified') -----
  render: anObject
+ 
+ 	^String streamContents: [ :stream |
+ 		anObject jsonWriteOn: stream ]!
- 	| s |
- 	s := WriteStream on: String new.
- 	anObject jsonWriteOn: s.
- 	^ s contents.!

Item was changed:
+ ----- Method: Json class>>render:withConstructor:on: (in category 'rendering') -----
- ----- Method: Json class>>render:withConstructor:on: (in category 'as yet unclassified') -----
  render: anObject withConstructor: aConstructorName on: aStream
  	aStream nextPutAll: '@', aConstructorName.
  	anObject jsonWriteOn: aStream.
  !

Item was changed:
+ ----- Method: Json class>>renderInstanceVariables:of:on: (in category 'rendering') -----
- ----- Method: Json class>>renderInstanceVariables:of:on: (in category 'as yet unclassified') -----
  renderInstanceVariables: aCollection of: anObject on: aStream
  	| map |
  	map := Dictionary new.
  	aCollection do: [:ivarName | map at: ivarName put: (anObject instVarNamed: ivarName)].
  	self render: map withConstructor: anObject class name asString on: aStream!

Item was added:
+ ----- Method: Json>>arrayBufferStream (in category 'private') -----
+ arrayBufferStream
+ 
+ 	^arrayBufferStream ifNil: [ arrayBufferStream := (Array new: 10) writeStream ]!

Item was added:
+ ----- Method: Json>>consume: (in category 'private') -----
+ consume: aString
+ 
+ 	| message |
+ 	1 to: aString size do: [ :index |
+ 		(currentCharacter := stream next) == (aString at: index) ifFalse: [
+ 			message := 'Expected ', (aString at: index)  printString.
+ 			currentCharacter
+ 				ifNil: [ self incomplete: message ]
+ 				ifNotNil: [ self invalid: message ] ] ].
+ 	currentCharacter := stream next!

Item was removed:
- ----- Method: Json>>consume:returning: (in category 'private') -----
- consume: aString returning: anObject
- 	|v|
- 	aString do: [:c |
- 		v := stream next.
- 		v ifNil: [JsonIncompleteError signal: 'Incomplete ''', aString, ''' seen'].
- 		v == c ifFalse: [JsonInvalidError signal: 'Expected ''', aString, ''''] ].
- 	^ anObject!

Item was added:
+ ----- Method: Json>>error: (in category 'error handling') -----
+ error: aString
+ 
+ 	(currentCharacter isNil and: [ stream atEnd ])
+ 		ifTrue: [ self incomplete: aString ]
+ 		ifFalse: [ self invalid: aString ]!

Item was added:
+ ----- Method: Json>>incomplete: (in category 'error handling') -----
+ incomplete: aString
+ 
+ 	JsonIncompleteError signal: aString!

Item was removed:
- ----- Method: Json>>interpretStringEscape (in category 'private') -----
- interpretStringEscape
- 	| c |
- 	c := stream next.
- 	c ifNil: [JsonIncompleteError signal: 'Expected character following ''\'' in string escape'].
- 	c == $b ifTrue: [^ Character backspace].
- 	c == $n ifTrue: [^ Character lf].
- 	c == $f ifTrue: [^ Character newPage].
- 	c == $r ifTrue: [^ Character cr].
- 	c == $t ifTrue: [^ Character tab].
- 	c == $u ifTrue: [^ self unescapeUnicode].
- 	^ c.!

Item was added:
+ ----- Method: Json>>interpretStringEscape: (in category 'private') -----
+ interpretStringEscape: aCharacter
+ 
+ 	aCharacter == $b ifTrue: [ ^Character backspace ].
+ 	aCharacter == $n ifTrue: [ ^Character lf ].
+ 	aCharacter == $f ifTrue: [ ^Character newPage ].
+ 	aCharacter == $r ifTrue: [ ^Character cr ].
+ 	aCharacter == $t ifTrue: [ ^Character tab ].
+ 	aCharacter == $u ifTrue: [ ^self unescapeUnicode ].
+ 	aCharacter == $" ifTrue: [ ^aCharacter ].
+ 	aCharacter == $\ ifTrue: [ ^aCharacter ].
+ 	aCharacter == $/ ifTrue: [ ^aCharacter ].
+ 	self error: 'Unexpected escaped character: ', aCharacter asString
+ !

Item was added:
+ ----- Method: Json>>invalid: (in category 'error handling') -----
+ invalid: aString
+ 
+ 	JsonInvalidError signal: aString!

Item was removed:
- ----- Method: Json>>nextHexDigit (in category 'private') -----
- nextHexDigit
- 	| c |
- 	c := stream next.
- 	c ifNil: [JsonIncompleteError signal: 'Expecting hex digit'].
- 	c := c asUppercase.
- 	(c isDigit or: [c >= $A and: [c <= $F]]) ifTrue: [^ c].
- 	JsonInvalidError signal: 'Expected hex digit'.!

Item was changed:
+ ----- Method: Json>>readAny (in category 'private') -----
- ----- Method: Json>>readAny (in category 'parsing') -----
  readAny
+ 
- 	"This is the main entry point for the JSON parser. See also readFrom: on the class side."
- 	| c |
  	self skipWhitespace.
+ 	currentCharacter == ${ ifTrue: [ ^self readDictionary ].
+ 	currentCharacter == $[ ifTrue: [ ^self readArray ].
+ 	currentCharacter == $" ifTrue: [ ^self readString ].
+ 	currentCharacter == $t ifTrue: [ self consume: 'rue'. ^true ].
+ 	currentCharacter == $f ifTrue: [ self consume: 'alse'. ^false ].
+ 	currentCharacter == $n ifTrue: [ self consume: 'ull'. ^nil ].
+ 	currentCharacter == $- ifTrue: [ ^self readNumber: true ].
+ 	(#($0 $1 $2 $3 $4 $5 $6 $7 $8 $9) instVarsInclude: currentCharacter) ifTrue: [
+ 		^self readNumber: false ].
+ 	currentCharacter == $@ ifTrue: [ ^self readConstructor ].
+ 	self invalid: 'Unknown Json input'!
- 	c := stream peek asLowercase.
- 	c == ${ ifTrue: [stream next. ^ self readDictionary].
- 	c == $[ ifTrue: [stream next. ^ self readArray].
- 	c == $" ifTrue: [stream next. ^ self readString].
- 	c == $t ifTrue: [^ self consume: 'true' returning: true].
- 	c == $f ifTrue: [^ self consume: 'false' returning: false].
- 	c == $n ifTrue: [^ self consume: 'null' returning: nil].
- 	c == $@ ifTrue: [stream next. ^ self readConstructor].
- 	(Json numbersMayContain: c) ifTrue: [^ self readNumber].
- 	JsonInvalidError signal: 'Unknown Json input'!

Item was changed:
  ----- Method: Json>>readArray (in category 'private') -----
  readArray
+ 
+ 	| initialPosition |
+ 	currentCharacter := stream next.
- 	| a |
  	self skipWhitespace.
+ 	currentCharacter == $] ifTrue: [
+ 		currentCharacter := stream next.
+ 		^#() ].
+ 	initialPosition := self arrayBufferStream position.
- 	(stream peekFor: $]) ifTrue: [ ^ #() ].	
- 	a := OrderedCollection new.
  	[
+ 		arrayBufferStream nextPut: self readAny.
- 		a add: self readAny.
  		self skipWhitespace.
+ 		currentCharacter == $] ifTrue: [ 
+ 			|  result |
+ 			result := arrayBufferStream originalContents copyFrom: initialPosition + 1 to: arrayBufferStream position.
+ 			arrayBufferStream position: initialPosition.
+ 			currentCharacter := stream next.
+ 			^result ].
+ 		currentCharacter == $, ifFalse: [ self error: 'Unexpected character: ', currentCharacter asString ].
+ 		currentCharacter := stream next ] repeat.
- 		(stream peekFor: $]) ifTrue: [ ^ a asArray].
- 		(stream peekFor: $, ) ifFalse: [JsonInvalidError signal: 'Missing comma'].
- 	] repeat.
  !

Item was changed:
  ----- Method: Json>>readConstructor (in category 'private') -----
  readConstructor
+ 
+ 	ctorMap ifNil: [ ^self error: 'No constructors were declared.' ].
+ 	self resetStringBufferStream.
+ 	[ (currentCharacter := stream next) == $. or: [ currentCharacter isLetter ] ] whileTrue: [
+ 		stringBufferStream nextPut: currentCharacter ].
+ 	(ctorMap at: stringBufferStream contents ifAbsent: nil) ifNotNil: [ :constructor |
+ 		^constructor constructFromJson: self readAny ].	
+ 	self error: 'Unknown constructor: ', stringBufferStream contents!
- 	| s c v ctor |
- 	s := WriteStream on: ''.
- 	[
- 		c := stream peek.
- 		c ifNil: [JsonIncompleteError signal: 'Premature EOF reading constructor name'].
- 		((c == $.) or: [c isLetter])
- 			ifTrue: [s nextPut: c. stream next]
- 			ifFalse: [
- 				v := self readAny.
- 				s := s contents.
- 				ctor := ctorMap ifNotNil: [ctor := ctorMap at: s ifAbsent: [nil]].
- 				ctor ifNil: [JsonInvalidError signal: 'Unknown ctor ', s].
- 				^ ctor constructFromJson: v]
- 	] repeat.
- !

Item was changed:
  ----- Method: Json>>readDictionary (in category 'private') -----
  readDictionary
+ 
+ 	| result key commaNeeded |
+ 	result := JsonObject new.
+ 	commaNeeded := false.
+ 	currentCharacter := stream next.
- 	| m k v needComma |
- 	m := JsonObject new.
- 	needComma := false.
  	[
  		self skipWhitespace.
+ 		currentCharacter == $} ifTrue: [ 
+ 			currentCharacter := stream next.
+ 			^result ].
+ 		commaNeeded
+ 			ifFalse: [ commaNeeded := true ]
+ 			ifTrue: [ 
+ 				currentCharacter == $, ifFalse: [ self error: 'Missing comma' ].
+ 				currentCharacter := stream next.
+ 				self skipWhitespace ].
+ 		currentCharacter == $" ifFalse: [  self error: 'Key in dictionary must be string' ].
+ 		key := self readString.
- 		(stream peekFor: $}) ifTrue: [ ^ m].
- 		needComma
- 			ifTrue: [ (stream peekFor: $,) ifFalse: [JsonInvalidError signal: 'Missing comma'].
- 					self skipWhitespace]
- 			ifFalse: [needComma := true].
- "		k := self readAny. "
- 		(stream peekFor: $") ifFalse: [JsonInvalidError signal: 'Key in dictionary must be string'].
- 		k := self readString.
  		self skipWhitespace.
+ 		currentCharacter == $: ifFalse: [ self error: 'Missing colon' ].
+ 		currentCharacter := stream next.
+ 		result at: key put: self readAny  ] repeat.
- 		(stream peekFor: $:) ifFalse: [JsonInvalidError signal: 'Missing colon'].
- 		v := self readAny.
- 		m at: k put: v.
- 	] repeat.
  !

Item was changed:
  ----- Method: Json>>readFrom: (in category 'parsing') -----
  readFrom: aStream
+ 
+ 	| result |
+ 	stream := aStream.
+ 	numberParser ifNotNil: [ numberParser on: stream ].
+ 	currentCharacter := stream next.
+ 	result := self readAny.
+ 	stream atEnd ifFalse: [ stream skip: -1 ]. "Undo prereading."
+ 	^result!
- 	self stream: aStream.
- 	^ self readAny!

Item was removed:
- ----- Method: Json>>readNumber (in category 'private') -----
- readNumber
- 	| acc c |
- 	acc := WriteStream on: ''.
- 	[
- 		c := stream peek.
- 		(c isNil not and: [Json numbersMayContain: c]) ifFalse: [
- 			[^ acc contents asNumber] on: Error do: [JsonInvalidError signal: 'Invalid number']].
- 		acc nextPut: c.
- 		stream next.
- 	] repeat.!

Item was added:
+ ----- Method: Json>>readNumber: (in category 'private') -----
+ readNumber: negative
+ 
+ 	| result |
+ 	negative ifFalse: [ stream skip: -1 ].
+ 	result := (numberParser ifNil: [
+ 		numberParser := JsonNumberParser new
+ 			on: stream;
+ 			failBlock: [ self error: 'Invalid number.' ];
+ 			yourself ])
+ 		nextNumber: negative.
+ 	currentCharacter := stream next.
+ 	^result
+ 	!

Item was changed:
  ----- Method: Json>>readString (in category 'private') -----
  readString
+ 
+ 	self resetStringBufferStream.
- 	| s c |
- 	s := WriteStream on: ''.
  	[
+ 		currentCharacter := stream next ifNil: [self incomplete: 'Unexpected end of string' ].
+ 		currentCharacter == $" ifTrue: [ 
+ 			currentCharacter := stream next.
+ 			^stringBufferStream contents ].
+ 		currentCharacter == $\
+ 			ifTrue: [ 
+ 				stringBufferStream nextPut: (self interpretStringEscape: (stream next ifNil: [
+ 					self incomplete: 'Unexpected end of string' ])) ]
+ 			ifFalse: [ stringBufferStream nextPut: currentCharacter ] ] repeat!
- 		c := stream next.
- 		c ifNil: [JsonIncompleteError signal: 'Incomplete read of JSON string'].
- 		c == $\
- 			ifTrue: [s nextPut: self interpretStringEscape.]
- 			ifFalse: [c == $" ifTrue: [^ s contents.].
- 					s nextPut: c]
- 	] repeat.!

Item was added:
+ ----- Method: Json>>resetStringBufferStream (in category 'private') -----
+ resetStringBufferStream
+ 
+ 	stringBufferStream 
+ 		ifNil: [ stringBufferStream := ReadWriteStream on: (String new: 64) ]
+ 		ifNotNil: [ stringBufferStream resetToStart ]!

Item was removed:
- ----- Method: Json>>skipComment (in category 'private') -----
- skipComment
- 	stream peek == $/ ifTrue: [
- 		stream next.
- 		stream peek == $/
- 			ifTrue: [self skipToEndOfLine]
- 			ifFalse: [stream peek == $*
- 						ifTrue: [stream next. self skipCommentBody]
- 						ifFalse: [JsonInvalidError signal: 'Invalid comment syntax']]]
- !

Item was removed:
- ----- Method: Json>>skipCommentBody (in category 'private') -----
- skipCommentBody
- 	[
- 		[stream next == $*] whileFalse.
- 		stream peek == $/
- 	] whileFalse.
- 	stream next. "skip that last slash"
- 	self skipWhitespace.!

Item was removed:
- ----- Method: Json>>skipToEndOfLine (in category 'private') -----
- skipToEndOfLine
- 	| cr lf |
- 	cr := Character cr. lf := Character lf.
- 	[ | c | (c := stream peek) == cr or: [ c == lf]] whileFalse: [stream next].
- 	self skipWhitespace!

Item was changed:
  ----- Method: Json>>skipWhitespace (in category 'private') -----
  skipWhitespace
+ 
+ 	currentCharacter ifNil: [
+ 		self incomplete: 'Input stream is empty' ].
+ 	[ currentCharacter isSeparator ] whileTrue: [
+ 		currentCharacter := stream next ifNil: [
+ 			self incomplete: 'Input stream is empty' ]. ]!
- 	|c|
- 	[
- 		c := stream peek.
- 		c ifNil: [JsonIncompleteError signal: 'Expected JSON input'].
- 		c isSeparator
- 	] whileTrue: [stream next].
- 	self skipComment.!

Item was changed:
+ ----- Method: Json>>stream: (in category 'private') -----
+ stream: aStream
+ 	"For testing purposes only"
- ----- Method: Json>>stream: (in category 'accessing') -----
- stream: anObject
- 	"Set the value of stream"
  
+ 	stream := aStream.
+ 	numberParser ifNotNil: [ numberParser on: stream ].
+ 	currentCharacter := stream next!
- 	stream := anObject.
- 	(stream respondsTo: #reset) ifTrue: [
- 		stream reset.
- 	].
- !

Item was changed:
  ----- Method: Json>>unescapeUnicode (in category 'private') -----
  unescapeUnicode
+ 
+ 	| code digitValue |
+ 	code := 0.
+ 	1 to: 4 do: [ :index |
+ 		(digitValue := (stream next ifNil: [ self incomplete: 'Unexpected end of stream' ]) digitValue) < 0 ifTrue: [
+ 			self invalid: 'Invalid hexadecimal digit' ].
+ 		digitValue < 16
+ 			ifTrue: [ code := code * 16 + digitValue ]
+ 			ifFalse: [ self invalid: 'Invalid hexadecimal digit' ] ].
+ 	^Character value: code!
- 	|string a b c d|
- 	a := self nextHexDigit.
- 	b := self nextHexDigit.
- 	c := self nextHexDigit.
- 	d := self nextHexDigit.
- 	string := String with: a with: b with: c with: d.
- 	^ Unicode value: (Integer readFrom: string readStream base: 16)!

Item was added:
+ NumberParser subclass: #JsonNumberParser
+ 	instanceVariableNames: 'fraction'
+ 	classVariableNames: ''
+ 	poolDictionaries: ''
+ 	category: 'JSON'!

Item was added:
+ ----- Method: JsonNumberParser>>allowPlusSign (in category 'accessing') -----
+ allowPlusSign
+ 
+ 	^false!

Item was added:
+ ----- Method: JsonNumberParser>>allowPlusSignInExponent (in category 'accessing') -----
+ allowPlusSignInExponent
+ 
+ 	^true!

Item was added:
+ ----- Method: JsonNumberParser>>error: (in category 'error') -----
+ error: aString
+ 
+ 	JsonSyntaxError signal: aString!

Item was added:
+ ----- Method: JsonNumberParser>>exponentLetters (in category 'accessing') -----
+ exponentLetters
+ 
+ 	^'eE'!

Item was added:
+ ----- Method: JsonNumberParser>>initialize (in category 'initialize-release') -----
+ initialize
+ 
+ 	super initialize.
+ 	base := 10.
+ 	fraction := Fraction numerator: 0 denominator: 1!

Item was added:
+ ----- Method: JsonNumberParser>>makeFloatFromMantissa:exponent: (in category 'parsing-private') -----
+ makeFloatFromMantissa: mantissa exponent: exponent
+ 
+ 	exponent = 0 ifTrue: [ ^mantissa asFloat ].
+ 	exponent >= 16 ifTrue: [ ^(mantissa * (self tenRaisedTo: exponent)) asFloat ].
+ 	(exponent >= -15 and: [ mantissa highBitOfMagnitude <= 54 ]) ifTrue: [
+ 		exponent >= 1 ifTrue: [ ^mantissa asFloat * (self tenRaisedTo: exponent) asFloat ].
+ 		^mantissa asFloat / (self tenRaisedTo: exponent negated) asFloat ].
+ 	^(fraction setNumerator: mantissa denominator: (self tenRaisedTo: exponent negated)) asFloat!

Item was added:
+ ----- Method: JsonNumberParser>>nextElementaryLargeInteger (in category 'parsing-large int') -----
+ nextElementaryLargeInteger
+ 	"Form an unsigned integer with incoming digits from sourceStream.
+ 	Return this integer, or zero if no digits found.
+ 	Stop reading if end of digits or if a LargeInteger is formed.
+ 	Count the number of digits and the position of lastNonZero digit and store them in instVar."
+ 
+ 	| digit value |
+ 	value := 0.
+ 	nDigits := 0.
+ 	lastNonZero := 0.
+ 	[
+ 		value isLarge ifTrue: [ ^value ].
+ 		((digit := (sourceStream next ifNil: [ ^value ]) asInteger - 48 "$0 asInteger") < 0 
+ 			or: [ digit >= 10 ]) ifTrue: [
+ 			sourceStream skip: -1.
+ 			^value ].
+ 		nDigits := nDigits + 1.
+ 		digit = 0
+ 			ifFalse: [
+ 				lastNonZero := nDigits.
+ 				value := value * 10 + digit ]
+ 			ifTrue: [ value := value * 10 ] ] repeat!

Item was added:
+ ----- Method: JsonNumberParser>>nextNumber: (in category 'parsing-public') -----
+ nextNumber: negative
+ 
+ 	| numberOfTrailingZeroInIntegerPart numberOfTrailingZeroInFractionPart numberOfNonZeroFractionDigits mantissa value |
+ 	integerPart := self nextUnsignedIntegerOrNil ifNil: [ ^self error: 'Missing integer part!!' ].
+ 	numberOfTrailingZeroInIntegerPart := nDigits - lastNonZero.
+ 	(sourceStream peekFor: $.) ifFalse: [ "No fraction part"
+ 		negative ifTrue: [ integerPart := integerPart negated ].
+ 		self readExponent ifFalse: [ ^integerPart ].
+ 		exponent > 0 ifTrue: [ ^integerPart * (self tenRaisedTo: exponent) ].
+ 		^(Fraction numerator: integerPart denominator: (self tenRaisedTo: exponent negated)) asFloat ].
+ 	fractionPart := self nextUnsignedIntegerOrNil ifNil: [ self error: 'Missing fraction part!!' ].
+ 	fractionPart isZero ifTrue: [
+ 		self readExponent ifFalse: [
+ 			negative ifTrue: [
+ 				integerPart isZero ifTrue: [ ^Float negativeZero ].
+ 				^integerPart negated ].
+ 			^integerPart ].
+ 		exponent >= 0 ifTrue: [ 
+ 			negative ifTrue: [ integerPart := integerPart negated ].
+ 			^integerPart * (self tenRaisedTo: exponent) ].
+ 		value := self makeFloatFromMantissa: integerPart exponent: exponent.
+ 		negative ifTrue: [ ^value negated ].
+ 		^value ].
+ 	numberOfTrailingZeroInFractionPart := nDigits - lastNonZero.
+ 	numberOfNonZeroFractionDigits := lastNonZero.
+ 	self readExponent.
+ 	exponent := exponent - numberOfNonZeroFractionDigits.
+ 	mantissa := integerPart * (self tenRaisedTo: numberOfNonZeroFractionDigits) + (fractionPart // (self tenRaisedTo: numberOfTrailingZeroInFractionPart)).
+ 	value := self makeFloatFromMantissa: mantissa exponent: exponent.
+ 	negative ifTrue: [ ^value negated ].
+ 	^value!

Item was added:
+ ----- Method: JsonNumberParser>>nextUnsignedIntegerOrNil (in category 'parsing-public') -----
+ nextUnsignedIntegerOrNil
+ 	"Form an unsigned integer with incoming digits from sourceStream.
+ 	Answer this integer, or nil if no digit found.
+ 	Count the number of digits and the position of lastNonZero digit and store them in instVar"
+ 	
+ 	| nPackets high nDigitsHigh lastNonZeroHigh low |
+ 	"read no more digits than one elementary LargeInteger"
+ 	high := self nextElementaryLargeInteger.
+ 	nDigits = 0 ifTrue: [^nil].
+ 	
+ 	"Not enough digits to form a LargeInteger, stop iteration"
+ 	high isLarge ifFalse: [^high].
+ 
+ 	"We now have to engage arithmetic with LargeInteger
+ 	Decompose the integer in a high and low packets of growing size:"
+ 	nPackets := 1.
+ 	nDigitsHigh := nDigits.
+ 	lastNonZeroHigh := lastNonZero.
+ 	[
+ 	low := self nextLargeIntegerBase: 10 nPackets: nPackets .
+ 	high := high * (self tenRaisedTo: nDigits) + low.
+ 	lastNonZero = 0 ifFalse: [lastNonZeroHigh := lastNonZero + nDigitsHigh].
+ 	nDigitsHigh := nDigitsHigh + nDigits.
+ 	low isLarge]
+ 		whileTrue: [nPackets := nPackets * 2].
+ 
+ 	nDigits := nDigitsHigh.
+ 	lastNonZero := lastNonZeroHigh.
+ 	^high!

Item was added:
+ ----- Method: JsonNumberParser>>tenRaisedTo: (in category 'parsing-private') -----
+ tenRaisedTo: anInteger
+ 
+ 	anInteger >= 0 ifFalse: [ ^10 raisedToInteger: anInteger ].
+ 	anInteger >= 16 ifTrue: [ ^10 raisedToInteger: anInteger ].
+ 	^#(1 10 100 1000 10000 100000 1000000 10000000 100000000 1000000000 10000000000 100000000000 1000000000000 10000000000000 100000000000000 1000000000000000) at: anInteger + 1!

Item was changed:
  ----- Method: JsonObject>>doesNotUnderstand: (in category 'as yet unclassified') -----
  doesNotUnderstand: aMessage
+ 
+ 	| key precedence |
- 	| key |
  	key := aMessage selector.
+ 	(precedence := key precedence) = 1 ifTrue: [ ^self at: key ifAbsent: nil ].
+ 	(precedence = 3 and: [ (key indexOf: $:) = key size ]) ifTrue: [
+ 		^self
+ 			at: key allButLast asSymbol
+ 			put: aMessage arguments first ].
+ 	^super doesNotUnderstand: aMessage!
- 	key isUnary ifTrue: [^ self at: key ifAbsent: [nil]].
- 	^ (key isKeyword and: [(key occurrencesOf: $:) = 1])
- 		ifTrue: [key := key allButLast asSymbol.
- 				self at: key put: aMessage arguments first]
- 		ifFalse: [super doesNotUnderstand: aMessage]
- 				
- 	!

Item was added:
+ OrderedDictionary subclass: #OrderedJsonObject
+ 	instanceVariableNames: ''
+ 	classVariableNames: ''
+ 	poolDictionaries: ''
+ 	category: 'JSON'!

Item was added:
+ ----- Method: OrderedJsonObject class>>fromAssociations: (in category 'as yet unclassified') -----
+ fromAssociations: collectionOfAssociations
+ 
+ 	| result |
+ 	result := self new.
+ 	
+ 	collectionOfAssociations do: [:each |
+ 		result at: each key put: each value ].
+ 	^ result!

Item was added:
+ ----- Method: OrderedJsonObject>>at: (in category 'accessing') -----
+ at: aKey
+ 
+ 	"make it work more like javascript objects"
+ 	^ self at: aKey ifAbsent: [nil]!

Item was added:
+ ----- Method: OrderedJsonObject>>doesNotUnderstand: (in category 'as yet unclassified') -----
+ doesNotUnderstand: aMessage
+ 
+ 	| key precedence |
+ 	key := aMessage selector.
+ 	(precedence := key precedence) = 1 ifTrue: [ ^self at: key ifAbsent: nil ].
+ 	(precedence = 3 and: [ (key indexOf: $:) = key size ]) ifTrue: [
+ 		^self
+ 			at: key allButLast asSymbol
+ 			put: aMessage arguments first ].
+ 	^super doesNotUnderstand: aMessage!

Item was added:
+ ----- Method: OrderedJsonObject>>name (in category 'accessing') -----
+ name
+ "override 'cause Object defines this"
+ 	^self at: 'name'!

Item was added:
+ ----- Method: OrderedJsonObject>>value (in category 'accessing') -----
+ value
+ "override 'cause Object defines this"
+ 	^self at: 'value'!

Item was added:
+ ----- Method: ScaledDecimal>>jsonWriteOn: (in category '*JSON-writing') -----
+ jsonWriteOn: stream
+ 
+ 	self printOn: stream showingDecimalPlaces: scale!

Item was changed:
  ----- Method: String>>jsonWriteOn: (in category '*JSON-writing') -----
  jsonWriteOn: aStream
  
+ 	| index start |
  	aStream nextPut: $".
+ 	start := 1.
+ 	[ (index := self indexOfAnyOf: Json escapeSet startingAt: start) = 0 ] whileFalse: [
+ 		aStream next: index - start putAll: self startingAt: start.
+ 		aStream nextPutAll: (Json escapeForCharacter: (self at: index)).
+ 		start := index + 1 ].
+ 	start <= self size ifTrue: [
+ 		aStream next: self size + 1 - start putAll: self startingAt: start ].
- 	self do: [ :ch |
- 		(Json escapeForCharacter: ch)
- 			ifNil: [ aStream nextPut: ch ]
- 			ifNotNil: [ :replacement |
- 				aStream nextPutAll: replacement ] ].
  	aStream nextPut: $".
  !

Item was added:
+ ----- Method: Time>>jsonWriteOn: (in category '*JSON') -----
+ jsonWriteOn: stream
+ 
+ 	stream nextPut: $".
+ 	self print24: true showSeconds: true on: stream.
+ 	stream nextPut: $"!



More information about the Squeak-dev mailing list