github.com/nuvolaris/goja@v0.0.0-20230825100449-967811910c6d/parser/parser.go (about)

     1  /*
     2  Package parser implements a parser for JavaScript.
     3  
     4  	import (
     5  	    "github.com/nuvolaris/goja/parser"
     6  	)
     7  
     8  Parse and return an AST
     9  
    10  	filename := "" // A filename is optional
    11  	src := `
    12  	    // Sample xyzzy example
    13  	    (function(){
    14  	        if (3.14159 > 0) {
    15  	            console.log("Hello, World.");
    16  	            return;
    17  	        }
    18  
    19  	        var xyzzy = NaN;
    20  	        console.log("Nothing happens.");
    21  	        return xyzzy;
    22  	    })();
    23  	`
    24  
    25  	// Parse some JavaScript, yielding a *ast.Program and/or an ErrorList
    26  	program, err := parser.ParseFile(nil, filename, src, 0)
    27  
    28  # Warning
    29  
    30  The parser and AST interfaces are still works-in-progress (particularly where
    31  node types are concerned) and may change in the future.
    32  */
    33  package parser
    34  
    35  import (
    36  	"bytes"
    37  	"errors"
    38  	"io"
    39  	"os"
    40  
    41  	"github.com/nuvolaris/goja/ast"
    42  	"github.com/nuvolaris/goja/file"
    43  	"github.com/nuvolaris/goja/token"
    44  	"github.com/nuvolaris/goja/unistring"
    45  )
    46  
    47  // A Mode value is a set of flags (or 0). They control optional parser functionality.
    48  type Mode uint
    49  
    50  const (
    51  	IgnoreRegExpErrors Mode = 1 << iota // Ignore RegExp compatibility errors (allow backtracking)
    52  )
    53  
    54  type options struct {
    55  	disableSourceMaps bool
    56  	sourceMapLoader   func(path string) ([]byte, error)
    57  }
    58  
    59  // Option represents one of the options for the parser to use in the Parse methods. Currently supported are:
    60  // WithDisableSourceMaps and WithSourceMapLoader.
    61  type Option func(*options)
    62  
    63  // WithDisableSourceMaps is an option to disable source maps support. May save a bit of time when source maps
    64  // are not in use.
    65  func WithDisableSourceMaps(opts *options) {
    66  	opts.disableSourceMaps = true
    67  }
    68  
    69  // WithSourceMapLoader is an option to set a custom source map loader. The loader will be given a path or a
    70  // URL from the sourceMappingURL. If sourceMappingURL is not absolute it is resolved relatively to the name
    71  // of the file being parsed. Any error returned by the loader will fail the parsing.
    72  // Note that setting this to nil does not disable source map support, there is a default loader which reads
    73  // from the filesystem. Use WithDisableSourceMaps to disable source map support.
    74  func WithSourceMapLoader(loader func(path string) ([]byte, error)) Option {
    75  	return func(opts *options) {
    76  		opts.sourceMapLoader = loader
    77  	}
    78  }
    79  
    80  type _parser struct {
    81  	str    string
    82  	length int
    83  	base   int
    84  
    85  	chr       rune // The current character
    86  	chrOffset int  // The offset of current character
    87  	offset    int  // The offset after current character (may be greater than 1)
    88  
    89  	idx           file.Idx    // The index of token
    90  	token         token.Token // The token
    91  	literal       string      // The literal of the token, if any
    92  	parsedLiteral unistring.String
    93  
    94  	scope             *_scope
    95  	insertSemicolon   bool // If we see a newline, then insert an implicit semicolon
    96  	implicitSemicolon bool // An implicit semicolon exists
    97  
    98  	errors ErrorList
    99  
   100  	recover struct {
   101  		// Scratch when trying to seek to the next statement, etc.
   102  		idx   file.Idx
   103  		count int
   104  	}
   105  
   106  	mode Mode
   107  	opts options
   108  
   109  	file *file.File
   110  }
   111  
   112  func _newParser(filename, src string, base int, opts ...Option) *_parser {
   113  	p := &_parser{
   114  		chr:    ' ', // This is set so we can start scanning by skipping whitespace
   115  		str:    src,
   116  		length: len(src),
   117  		base:   base,
   118  		file:   file.NewFile(filename, src, base),
   119  	}
   120  	for _, opt := range opts {
   121  		opt(&p.opts)
   122  	}
   123  	return p
   124  }
   125  
   126  func newParser(filename, src string) *_parser {
   127  	return _newParser(filename, src, 1)
   128  }
   129  
   130  func ReadSource(filename string, src interface{}) ([]byte, error) {
   131  	if src != nil {
   132  		switch src := src.(type) {
   133  		case string:
   134  			return []byte(src), nil
   135  		case []byte:
   136  			return src, nil
   137  		case *bytes.Buffer:
   138  			if src != nil {
   139  				return src.Bytes(), nil
   140  			}
   141  		case io.Reader:
   142  			var bfr bytes.Buffer
   143  			if _, err := io.Copy(&bfr, src); err != nil {
   144  				return nil, err
   145  			}
   146  			return bfr.Bytes(), nil
   147  		}
   148  		return nil, errors.New("invalid source")
   149  	}
   150  	return os.ReadFile(filename)
   151  }
   152  
   153  // ParseFile parses the source code of a single JavaScript/ECMAScript source file and returns
   154  // the corresponding ast.Program node.
   155  //
   156  // If fileSet == nil, ParseFile parses source without a FileSet.
   157  // If fileSet != nil, ParseFile first adds filename and src to fileSet.
   158  //
   159  // The filename argument is optional and is used for labelling errors, etc.
   160  //
   161  // src may be a string, a byte slice, a bytes.Buffer, or an io.Reader, but it MUST always be in UTF-8.
   162  //
   163  //	// Parse some JavaScript, yielding a *ast.Program and/or an ErrorList
   164  //	program, err := parser.ParseFile(nil, "", `if (abc > 1) {}`, 0)
   165  func ParseFile(fileSet *file.FileSet, filename string, src interface{}, mode Mode, options ...Option) (*ast.Program, error) {
   166  	str, err := ReadSource(filename, src)
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  	{
   171  		str := string(str)
   172  
   173  		base := 1
   174  		if fileSet != nil {
   175  			base = fileSet.AddFile(filename, str)
   176  		}
   177  
   178  		parser := _newParser(filename, str, base, options...)
   179  		parser.mode = mode
   180  		return parser.parse()
   181  	}
   182  }
   183  
   184  // ParseFunction parses a given parameter list and body as a function and returns the
   185  // corresponding ast.FunctionLiteral node.
   186  //
   187  // The parameter list, if any, should be a comma-separated list of identifiers.
   188  func ParseFunction(parameterList, body string, options ...Option) (*ast.FunctionLiteral, error) {
   189  
   190  	src := "(function(" + parameterList + ") {\n" + body + "\n})"
   191  
   192  	parser := _newParser("", src, 1, options...)
   193  	program, err := parser.parse()
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  
   198  	return program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.FunctionLiteral), nil
   199  }
   200  
   201  func (self *_parser) slice(idx0, idx1 file.Idx) string {
   202  	from := int(idx0) - self.base
   203  	to := int(idx1) - self.base
   204  	if from >= 0 && to <= len(self.str) {
   205  		return self.str[from:to]
   206  	}
   207  
   208  	return ""
   209  }
   210  
   211  func (self *_parser) parse() (*ast.Program, error) {
   212  	self.openScope()
   213  	defer self.closeScope()
   214  	self.next()
   215  	program := self.parseProgram()
   216  	if false {
   217  		self.errors.Sort()
   218  	}
   219  	return program, self.errors.Err()
   220  }
   221  
   222  func (self *_parser) next() {
   223  	self.token, self.literal, self.parsedLiteral, self.idx = self.scan()
   224  }
   225  
   226  func (self *_parser) optionalSemicolon() {
   227  	if self.token == token.SEMICOLON {
   228  		self.next()
   229  		return
   230  	}
   231  
   232  	if self.implicitSemicolon {
   233  		self.implicitSemicolon = false
   234  		return
   235  	}
   236  
   237  	if self.token != token.EOF && self.token != token.RIGHT_BRACE {
   238  		self.expect(token.SEMICOLON)
   239  	}
   240  }
   241  
   242  func (self *_parser) semicolon() {
   243  	if self.token != token.RIGHT_PARENTHESIS && self.token != token.RIGHT_BRACE {
   244  		if self.implicitSemicolon {
   245  			self.implicitSemicolon = false
   246  			return
   247  		}
   248  
   249  		self.expect(token.SEMICOLON)
   250  	}
   251  }
   252  
   253  func (self *_parser) idxOf(offset int) file.Idx {
   254  	return file.Idx(self.base + offset)
   255  }
   256  
   257  func (self *_parser) expect(value token.Token) file.Idx {
   258  	idx := self.idx
   259  	if self.token != value {
   260  		self.errorUnexpectedToken(self.token)
   261  	}
   262  	self.next()
   263  	return idx
   264  }
   265  
   266  func (self *_parser) position(idx file.Idx) file.Position {
   267  	return self.file.Position(int(idx) - self.base)
   268  }