Commit c305005d authored by Felix Lange's avatar Felix Lange

Merge pull request #2227 from bas-vk/mathrandom

console: seed random number generator
parents 17649edd 6777531a
......@@ -84,7 +84,7 @@
},
{
"ImportPath": "github.com/robertkrimen/otto",
"Rev": "c21072f61b64b51ea58138ccacf0a85d54b9f07c"
"Rev": "53221230c215611a90762720c9042ac782ef74ee"
},
{
"ImportPath": "github.com/syndtr/goleveldb/leveldb",
......
package ast
import (
"fmt"
"github.com/robertkrimen/otto/file"
)
// CommentPosition determines where the comment is in a given context
type CommentPosition int
const (
_ CommentPosition = iota
LEADING // Before the pertinent expression
TRAILING // After the pertinent expression
KEY // After a key or keyword
COLON // After a colon in a field declaration
FINAL // Final comments in a block, not belonging to a specific expression or the comment after a trailing , in an array or object literal
TBD
)
// Comment contains the data of the comment
type Comment struct {
Begin file.Idx
Text string
Position CommentPosition
}
// String returns a stringified version of the position
func (cp CommentPosition) String() string {
switch cp {
case LEADING:
return "Leading"
case TRAILING:
return "Trailing"
case KEY:
return "Key"
case COLON:
return "Colon"
case FINAL:
return "Final"
default:
return "???"
}
}
// String returns a stringified version of the comment
func (c Comment) String() string {
return fmt.Sprintf("Comment: %v", c.Text)
}
// CommentMap is the data structure where all found comments are stored
type CommentMap map[Node][]*Comment
// AddComment adds a single comment to the map
func (cm CommentMap) AddComment(node Node, comment *Comment) {
list := cm[node]
list = append(list, comment)
cm[node] = list
}
// AddComments adds a slice of comments, given a node and an updated position
func (cm CommentMap) AddComments(node Node, comments []*Comment, position CommentPosition) {
for _, comment := range comments {
comment.Position = position
cm.AddComment(node, comment)
}
}
// Size returns the size of the map
func (cm CommentMap) Size() int {
size := 0
for _, comments := range cm {
size += len(comments)
}
return size
}
// MoveComments moves comments with a given position from a node to another
func (cm CommentMap) MoveComments(from, to Node, position CommentPosition) {
for i, c := range cm[from] {
if c.Position == position {
cm.AddComment(to, c)
// Remove the comment from the "from" slice
cm[from][i] = cm[from][len(cm[from])-1]
cm[from][len(cm[from])-1] = nil
cm[from] = cm[from][:len(cm[from])-1]
}
}
}
......@@ -86,6 +86,11 @@ type (
Identifier Identifier
}
EmptyExpression struct {
Begin file.Idx
End file.Idx
}
FunctionLiteral struct {
Function file.Idx
Name *Identifier
......@@ -185,6 +190,7 @@ func (*BracketExpression) _expressionNode() {}
func (*CallExpression) _expressionNode() {}
func (*ConditionalExpression) _expressionNode() {}
func (*DotExpression) _expressionNode() {}
func (*EmptyExpression) _expressionNode() {}
func (*FunctionLiteral) _expressionNode() {}
func (*Identifier) _expressionNode() {}
func (*NewExpression) _expressionNode() {}
......@@ -399,6 +405,7 @@ func (self *BracketExpression) Idx0() file.Idx { return self.Left.Idx0() }
func (self *CallExpression) Idx0() file.Idx { return self.Callee.Idx0() }
func (self *ConditionalExpression) Idx0() file.Idx { return self.Test.Idx0() }
func (self *DotExpression) Idx0() file.Idx { return self.Left.Idx0() }
func (self *EmptyExpression) Idx0() file.Idx { return self.Begin }
func (self *FunctionLiteral) Idx0() file.Idx { return self.Function }
func (self *Identifier) Idx0() file.Idx { return self.Idx }
func (self *NewExpression) Idx0() file.Idx { return self.New }
......@@ -447,6 +454,7 @@ func (self *BracketExpression) Idx1() file.Idx { return self.RightBracket +
func (self *CallExpression) Idx1() file.Idx { return self.RightParenthesis + 1 }
func (self *ConditionalExpression) Idx1() file.Idx { return self.Test.Idx1() }
func (self *DotExpression) Idx1() file.Idx { return self.Identifier.Idx1() }
func (self *EmptyExpression) Idx1() file.Idx { return self.End }
func (self *FunctionLiteral) Idx1() file.Idx { return self.Body.Idx1() }
func (self *Identifier) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Name)) }
func (self *NewExpression) Idx1() file.Idx { return self.RightParenthesis + 1 }
......
......@@ -117,7 +117,13 @@ func builtinMath_pow(call FunctionCall) Value {
}
func builtinMath_random(call FunctionCall) Value {
return toValue_float64(rand.Float64())
var v float64
if call.runtime.random != nil {
v = call.runtime.random()
} else {
v = rand.Float64()
}
return toValue_float64(v)
}
func builtinMath_round(call FunctionCall) Value {
......
......@@ -82,6 +82,9 @@ func (cmpl *_compiler) parseExpression(in ast.Expression) _nodeExpression {
identifier: in.Identifier.Name,
}
case *ast.EmptyExpression:
return nil
case *ast.FunctionLiteral:
name := ""
if in.Name != nil {
......
......@@ -363,6 +363,10 @@ func (self Otto) SetDebuggerHandler(fn func(vm *Otto)) {
self.runtime.debugger = fn
}
func (self Otto) SetRandomSource(fn func() float64) {
self.runtime.random = fn
}
// Context is a structure that contains information about the current execution
// context.
type Context struct {
......
......@@ -120,6 +120,7 @@ func isLineTerminator(chr rune) bool {
func (self *_parser) scan() (tkn token.Token, literal string, idx file.Idx) {
self.implicitSemicolon = false
self.skippedLineBreak = false
for {
self.skipWhiteSpace()
......@@ -238,9 +239,20 @@ func (self *_parser) scan() (tkn token.Token, literal string, idx file.Idx) {
tkn = self.switch2(token.MULTIPLY, token.MULTIPLY_ASSIGN)
case '/':
if self.chr == '/' {
if self.mode&StoreComments != 0 {
runes := self.readSingleLineComment()
literal = string(runes)
tkn = token.COMMENT
return
}
self.skipSingleLineComment()
continue
} else if self.chr == '*' {
if self.mode&StoreComments != 0 {
literal = string(self.readMultiLineComment())
tkn = token.COMMENT
return
}
self.skipMultiLineComment()
continue
} else {
......@@ -411,6 +423,39 @@ func (self *_RegExp_parser) read() {
}
}
func (self *_parser) readSingleLineComment() (result []rune) {
for self.chr != -1 {
self.read()
if isLineTerminator(self.chr) {
return
}
result = append(result, self.chr)
}
// Get rid of the trailing -1
result = result[:len(result)-1]
return
}
func (self *_parser) readMultiLineComment() (result []rune) {
self.read()
for self.chr >= 0 {
chr := self.chr
self.read()
if chr == '*' && self.chr == '/' {
self.read()
return
}
result = append(result, chr)
}
self.errorUnexpected(0, self.chr)
return
}
func (self *_parser) skipSingleLineComment() {
for self.chr != -1 {
self.read()
......@@ -442,6 +487,7 @@ func (self *_parser) skipWhiteSpace() {
continue
case '\r':
if self._peek() == '\n' {
self.skippedLineBreak = true
self.read()
}
fallthrough
......@@ -449,6 +495,7 @@ func (self *_parser) skipWhiteSpace() {
if self.insertSemicolon {
return
}
self.skippedLineBreak = true
self.read()
continue
}
......
......@@ -49,12 +49,13 @@ type Mode uint
const (
IgnoreRegExpErrors Mode = 1 << iota // Ignore RegExp compatibility errors (allow backtracking)
StoreComments // Store the comments from source to the comments map
)
type _parser struct {
str string
length int
base int
str string
length int
base int
chr rune // The current character
chrOffset int // The offset of current character
......@@ -79,15 +80,22 @@ type _parser struct {
mode Mode
file *file.File
comments []*ast.Comment
commentMap *ast.CommentMap
skippedLineBreak bool
}
func _newParser(filename, src string, base int) *_parser {
return &_parser{
chr: ' ', // This is set so we can start scanning by skipping whitespace
str: src,
length: len(src),
base: base,
file: file.NewFile(filename, src, base),
chr: ' ', // This is set so we can start scanning by skipping whitespace
str: src,
length: len(src),
base: base,
file: file.NewFile(filename, src, base),
comments: make([]*ast.Comment, 0),
commentMap: &ast.CommentMap{},
skippedLineBreak: false,
}
}
......@@ -184,6 +192,9 @@ func (self *_parser) parse() (*ast.Program, error) {
if false {
self.errors.Sort()
}
self.addCommentStatements(program, ast.FINAL)
return program, self.errors.Err()
}
......@@ -270,3 +281,63 @@ func (self *_parser) position(idx file.Idx) file.Position {
return position
}
// findComments finds the following comments.
// Comments on the same line will be grouped together and returned.
// After the first line break, comments will be added as statement comments.
func (self *_parser) findComments(ignoreLineBreak bool) []*ast.Comment {
if self.mode&StoreComments == 0 {
return nil
}
comments := make([]*ast.Comment, 0)
newline := false
for self.implicitSemicolon == false || ignoreLineBreak {
if self.token != token.COMMENT {
break
}
comment := &ast.Comment{
Begin: self.idx,
Text: self.literal,
Position: ast.TBD,
}
newline = self.skippedLineBreak || newline
if newline && !ignoreLineBreak {
self.comments = append(self.comments, comment)
} else {
comments = append(comments, comment)
}
self.next()
}
return comments
}
// addCommentStatements will add the previously parsed, not positioned comments to the provided node
func (self *_parser) addCommentStatements(node ast.Node, position ast.CommentPosition) {
if len(self.comments) > 0 {
self.commentMap.AddComments(node, self.comments, position)
// Reset comments
self.comments = make([]*ast.Comment, 0)
}
}
// fetchComments fetches the current comments, resets the slice and returns the comments
func (self *_parser) fetchComments() (comments []*ast.Comment) {
comments = self.comments
self.comments = nil
return comments
}
// consumeComments consumes the current comments and appends them to the provided node
func (self *_parser) consumeComments(node ast.Node, position ast.CommentPosition) {
self.commentMap.AddComments(node, self.comments, position)
self.comments = nil
}
......@@ -55,6 +55,7 @@ type _runtime struct {
otto *Otto
eval *_object // The builtin eval, for determine indirect versus direct invocation
debugger func(*Otto)
random func() float64
labels []string // FIXME
lck sync.Mutex
......
......@@ -18,8 +18,11 @@
package jsre
import (
crand "crypto/rand"
"encoding/binary"
"fmt"
"io/ioutil"
"math/rand"
"sync"
"time"
......@@ -70,6 +73,18 @@ func New(assetPath string) *JSRE {
return re
}
// randomSource returns a pseudo random value generator.
func randomSource() *rand.Rand {
bytes := make([]byte, 8)
seed := time.Now().UnixNano()
if _, err := crand.Read(bytes); err == nil {
seed = int64(binary.LittleEndian.Uint64(bytes))
}
src := rand.NewSource(seed)
return rand.New(src)
}
// This function runs the main event loop from a goroutine that is started
// when JSRE is created. Use Stop() before exiting to properly stop it.
// The event loop processes vm access requests from the evalQueue in a
......@@ -81,6 +96,9 @@ func New(assetPath string) *JSRE {
// called from JS through an RPC call.
func (self *JSRE) runEventLoop() {
vm := otto.New()
r := randomSource()
vm.SetRandomSource(r.Float64)
registry := map[*jsTimer]*jsTimer{}
ready := make(chan *jsTimer)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment