├── .gitignore ├── .rspec ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── exe └── minjs ├── lib ├── minjs.rb └── minjs │ ├── compressor.rb │ ├── compressor │ └── compressor.rb │ ├── ctype.rb │ ├── ecma262.rb │ ├── ecma262 │ ├── base.rb │ ├── env.rb │ ├── expression.rb │ ├── literal.rb │ ├── punctuator.rb │ └── statement.rb │ ├── lex.rb │ ├── lex │ ├── exceptions.rb │ ├── expression.rb │ ├── function.rb │ ├── parser.rb │ ├── program.rb │ └── statement.rb │ ├── minjs_compressor.rb │ └── version.rb ├── minjs.gemspec └── spec ├── compress ├── assignment_after_var_spec.rb ├── block_to_statement_spec.rb ├── compress_var_spec.rb ├── grouping_statement_spec.rb ├── if_to_cond_spec.rb ├── if_to_return2_spec.rb ├── reduce_exp_spec.rb ├── reduce_if_spec.rb ├── remove_paren_spec.rb ├── remove_then_or_else_spec.rb ├── reorder_function_decl_spec.rb ├── reorder_var_decl_spec.rb ├── simple_replacement_spec.rb └── use_strcit_spec.rb ├── expression ├── additive_spec.rb ├── assignment_spec.rb ├── binary_bitwise_spec.rb ├── binary_logical_spec.rb ├── bitwise_shift_spec.rb ├── comma_spec.rb ├── conditional_spec.rb ├── equality_spec.rb ├── left_hand_side_spec.rb ├── multiplicative_spec.rb ├── postfix_spec.rb ├── primary_spec.rb ├── relational_spec.rb └── unary_spec.rb ├── literal ├── array_spec.rb ├── comment_spec.rb ├── identifier_name_spec.rb ├── numeric_spec.rb ├── object_spec.rb ├── regexp_spec.rb ├── string_spec.rb └── white_space_spec.rb ├── minjs_spec.rb ├── spec_helper.rb └── statement ├── block_spec.rb ├── break_spec.rb ├── continue_spec.rb ├── debugger_spec.rb ├── empty_spec.rb ├── if_spec.rb ├── iteration_spec.rb ├── labelled_statement_spec.rb ├── return_spec.rb ├── switch_spec.rb ├── throw_spec.rb ├── try_spec.rb ├── var_spec.rb └── with_spec.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.1.6 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 | 5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion. 6 | 7 | Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. 8 | 9 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. 10 | 11 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 12 | 13 | This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) 14 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in minjs.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Issei Numata 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Minjs 2 | 3 | Minjs provide minifying JavaScript code. 4 | 5 | ## Installation 6 | 7 | Add this line to your application's Gemfile: 8 | 9 | ```ruby 10 | gem 'minjs' 11 | ``` 12 | 13 | And then execute: 14 | 15 | $ bundle 16 | 17 | Or install it yourself as: 18 | 19 | $ gem install minjs 20 | 21 | ## Usage 22 | 23 | From command line, type: 24 | 25 | $ minjs 26 | 27 | For rails, add next line to your `config/environments/production.rb` 28 | 29 | config.assets.js_compressor = Minjs::MinjsCompressor 30 | 31 | ## Development 32 | 33 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment. 34 | 35 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). 36 | 37 | ## Contributing 38 | 39 | 1. Fork it ( https://github.com/i10a/minjs/fork ) 40 | 2. Create your feature branch (`git checkout -b my-new-feature`) 41 | 3. Commit your changes (`git commit -am 'Add some feature'`) 42 | 4. Push to the branch (`git push origin my-new-feature`) 43 | 5. Create a new Pull Request 44 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | require "yard" 4 | require "yard/rake/yardoc_task" 5 | 6 | RSpec::Core::RakeTask.new(:spec) 7 | YARD::Rake::YardocTask.new do |t| 8 | t.files = FileList["lib/**/*.rb"] 9 | t.options = ['--embed-mixins'] 10 | # t.stats_options = ['--list-undoc'] 11 | end 12 | 13 | task :default => :spec 14 | 15 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "minjs" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | 5 | bundle install 6 | 7 | # Do any other automated setup that you need to do here 8 | -------------------------------------------------------------------------------- /exe/minjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'minjs' 4 | require 'minjs/compressor/compressor' 5 | 6 | argv = ARGV.dup 7 | f = [] 8 | options = {} 9 | argv.each do |x| 10 | if x.match(/^--?version/) 11 | puts Minjs::VERSION 12 | exit(0) 13 | elsif x.match(/^--?/) 14 | opt = $'.gsub(/-/, '_').to_sym 15 | options[opt] = true 16 | else 17 | f.push(open(x.to_s).read()) 18 | end 19 | end 20 | 21 | prog = Minjs::Compressor::Compressor.new(:debug => false) 22 | prog.compress(f.join("\n"), options) 23 | puts prog.to_js(options) 24 | -------------------------------------------------------------------------------- /lib/minjs.rb: -------------------------------------------------------------------------------- 1 | require "minjs/version" 2 | require "minjs/lex" 3 | require "minjs/compressor" 4 | require "minjs/ecma262" 5 | require "minjs/minjs_compressor" 6 | require "minjs/ctype" 7 | 8 | # Minjs 9 | module Minjs 10 | # Your code goes here... 11 | end 12 | -------------------------------------------------------------------------------- /lib/minjs/compressor.rb: -------------------------------------------------------------------------------- 1 | module Minjs 2 | # Compressor 3 | module Compressor 4 | end 5 | end 6 | 7 | require 'minjs/compressor/compressor' 8 | -------------------------------------------------------------------------------- /lib/minjs/ecma262.rb: -------------------------------------------------------------------------------- 1 | module Minjs 2 | #ECMA262 3 | module ECMA262 4 | end 5 | end 6 | require "minjs/ecma262/base" 7 | require "minjs/ecma262/env" 8 | require "minjs/ecma262/expression" 9 | require "minjs/ecma262/literal" 10 | require "minjs/ecma262/punctuator" 11 | require "minjs/ecma262/statement" 12 | -------------------------------------------------------------------------------- /lib/minjs/ecma262/base.rb: -------------------------------------------------------------------------------- 1 | module Minjs 2 | module ECMA262 3 | #ECMA262 Elements 4 | class Base 5 | attr_accessor :parent 6 | 7 | # Returns a ECMAScript string containg the representation of element. 8 | # @param options [Hash] options for Base#concat 9 | # @return [String] ECMAScript string. 10 | def to_js(options = {}) 11 | self.class.to_s + "??" 12 | end 13 | 14 | # concatenate some of ECMA262 elements and convert it to ECMAScript 15 | # 16 | # @param args ECMA262 element 17 | # @option options :debug [Boolean] if set, output is easy to read. 18 | # 19 | def concat(options, *args) 20 | prev = nil 21 | j = [] 22 | args.flatten.each do|x| 23 | sep = '' 24 | nl = '' 25 | if x.kind_of? Base 26 | js = x.to_js(options); 27 | else 28 | js = x.to_s 29 | end 30 | if prev 31 | if prev.match(/[\w\$]\z/) and js.match(/\A[\w\$]/) 32 | sep = ' ' 33 | end 34 | # ';;' means 'empty statement' or separator of 'for statement' 35 | # that must not be deleted 36 | if prev.match(/;;\Z/) 37 | prev.sub!(/;;\Z/, ";") 38 | elsif prev.match(/;\Z/) and js == "}" 39 | prev.sub!(/;\Z/, "") 40 | elsif prev.match(/;\Z/) and js == ";" 41 | prev.sub!(/;\Z/, "") 42 | elsif prev.match(/[\-]\Z/) and js.match(/^\-/) 43 | sep = ' ' 44 | elsif prev.match(/[\+]\Z/) and js.match(/^\+/) 45 | sep = ' ' 46 | end 47 | end 48 | #for debug 49 | unless options[:no_debug] 50 | if (@logger and @logger.debug?) || options[:debug] 51 | if js.match(/;\z/) 52 | nl = "\n" 53 | end 54 | if js.match(/}\z/) 55 | nl = "\n" 56 | end 57 | end 58 | end 59 | js = "#{sep}#{js}#{nl}"; 60 | j.push(js) 61 | prev = js 62 | end 63 | j.join("") 64 | end 65 | 66 | # Replaces child (if own it) object 67 | # 68 | # @param from [Base] from 69 | # @param to [Base] to 70 | def replace(from, to) 71 | puts "warning: #{self.class}: replace not implement" 72 | end 73 | 74 | # duplicate object 75 | # 76 | # duplicate this object's children (if own) and itself. 77 | def deep_dup 78 | puts "warning: #{self.class}: deep_dup not implement" 79 | end 80 | 81 | # compare object 82 | def ==(obj) 83 | puts "warning: #{self.class}: == not implement" 84 | raise "warning: #{self.class}: == not implement" 85 | end 86 | 87 | # add / remove parenthesis if need 88 | def add_remove_paren(node = self) 89 | node.traverse(nil) {|st, parent| 90 | if st.respond_to? :remove_paren 91 | st.add_paren 92 | st.remove_paren 93 | end 94 | } 95 | node 96 | end 97 | 98 | # Traverses this children and itself with given block. 99 | # 100 | # If this element has children, traverse children first, 101 | # then yield block with parent and self. 102 | # 103 | # @param parent [Base] parent element. 104 | # @yield [parent, self] parent and this element. 105 | # @yieldparam [Base] self this element. 106 | # @yieldparam [Base] parent parent element. 107 | def traverse(parent, &block) 108 | 109 | end 110 | end 111 | 112 | # Class of ECMA262 Statement List 113 | # 114 | # @see http://www.ecma-international.org/ecma-262 ECMA262 12.1 115 | class StatementList < Base 116 | attr_reader :statement_list 117 | 118 | def initialize(statement_list) 119 | @statement_list = statement_list #array 120 | end 121 | 122 | # Groups statements and reduce number of them as few as posibble. 123 | def grouping 124 | remove_empty_statement 125 | new_sl = [] 126 | sl = [] 127 | g = [] 128 | @statement_list.each do |st| 129 | if st.to_exp? 130 | g.push(st) 131 | else 132 | if g.length > 0 133 | sl.push(g) 134 | end 135 | sl.push([st]) 136 | g = [] 137 | end 138 | end 139 | if g.length > 0 140 | sl.push(g) 141 | end 142 | 143 | sl.each do |g| 144 | if g.length == 1 145 | new_sl.push(g[0]) 146 | else 147 | i = 1 148 | t = ExpParen.new(g[0].to_exp) 149 | while i < g.length 150 | t = ExpComma.new(t, ExpParen.new(g[i].to_exp)) 151 | i += 1 152 | end 153 | new_sl.push(StExp.new(t)) 154 | end 155 | end 156 | 157 | if idx = new_sl.index{|x| x.class == StReturn} 158 | idx += 1 159 | while idx < new_sl.length 160 | if new_sl[idx].kind_of? StVar 161 | ; 162 | elsif new_sl[idx].kind_of? StFunc 163 | ; 164 | else 165 | new_sl[idx] = StEmpty.new 166 | end 167 | idx += 1 168 | end 169 | end 170 | 171 | if self.kind_of? SourceElements 172 | if new_sl[-1].kind_of? StReturn and new_sl[-1].exp.nil? 173 | new_sl.pop 174 | end 175 | end 176 | 177 | if new_sl[-1].kind_of? StReturn and new_sl[-2].kind_of? StExp 178 | if new_sl[-1].exp 179 | new_sl[-2] = StReturn.new(ExpComma.new(new_sl[-2].exp, new_sl[-1].exp)) 180 | new_sl.pop 181 | end 182 | end 183 | @statement_list = new_sl 184 | end 185 | 186 | # duplicate object 187 | # @see Base#deep_dup 188 | def deep_dup 189 | self.class.new(@statement_list.collect{|s| s.deep_dup}) 190 | end 191 | 192 | # Replaces children object 193 | # @see Base#replace 194 | def replace(from, to) 195 | idx = @statement_list.index(from) 196 | if idx 197 | @statement_list[idx] = to 198 | end 199 | end 200 | 201 | # Removes statement from statement list 202 | # @param st statement 203 | def remove(st) 204 | @statement_list.delete(st) 205 | end 206 | 207 | # Removes empty statement in this statement list 208 | def remove_empty_statement 209 | @statement_list.reject!{|x| 210 | x.class == StEmpty 211 | } 212 | end 213 | 214 | # Traverses this children and itself with given block. 215 | # @see Base#traverse 216 | def traverse(parent, &block) 217 | _self = self 218 | @statement_list.each do|st| 219 | st.traverse(self, &block) 220 | end 221 | yield parent, self 222 | end 223 | 224 | # compare object 225 | def ==(obj) 226 | @statement_list == obj.statement_list 227 | end 228 | 229 | # Returns a ECMAScript string containg the representation of element. 230 | # @see Base#to_js 231 | def to_js(options = {}) 232 | concat options, @statement_list 233 | end 234 | 235 | # Returns number of the statements 236 | def length 237 | @statement_list.length 238 | end 239 | 240 | # return true if this can convert to expression. 241 | def to_exp? 242 | @statement_list.each do |s| 243 | return false if s.to_exp? == false 244 | end 245 | return true 246 | end 247 | 248 | # Converts statement list to expression and returns it. 249 | def to_exp(options = {}) 250 | return nil if to_exp? == false 251 | t = @statement_list[0].to_exp(options) 252 | return t.to_exp(options) if @statement_list.length <= 1 253 | i = 1 254 | while(i < @statement_list.length) 255 | t = ExpComma.new(t, @statement_list[i]) 256 | i += 1 257 | end 258 | t 259 | end 260 | 261 | def each(&block) 262 | @statement_list.each(&block) 263 | end 264 | 265 | # Returns the statement at index 266 | # @param i index 267 | # @return [Statement] statement 268 | def [](i) 269 | @statement_list[i] 270 | end 271 | 272 | # Sets the statement at index. 273 | # @param i index 274 | # @param st statement 275 | def []=(i, st) 276 | @statement_list[i] = st 277 | end 278 | 279 | # Returns index of statement. 280 | # @param st statement. 281 | # @return [Fixnum] index of statement. 282 | def index(st) 283 | @statement_list.index(st) 284 | end 285 | end 286 | 287 | # Class of ECMA262 Source Elements 288 | # 289 | # @see http://www.ecma-international.org/ecma-262 ECMA262 14 290 | class SourceElements < StatementList 291 | # 292 | # source_elements: [statement, statement, ...] 293 | # 294 | def initialize(source_elements) 295 | @statement_list = source_elements 296 | end 297 | 298 | # alias of statement_list 299 | def source_elements 300 | @statement_list 301 | end 302 | 303 | # alias of statement_list= 304 | def source_elements=(source_elements) 305 | @statement_list = source_elements 306 | end 307 | 308 | alias :source_elements :statement_list 309 | 310 | # compare object 311 | def ==(obj) 312 | statement_list == obj.statement_list 313 | end 314 | end 315 | 316 | # Class of ECMA262 Program 317 | # 318 | # @see http://www.ecma-international.org/ecma-262 ECMA262 14 319 | class Prog < Base 320 | attr_reader :source_elements 321 | attr_reader :var_env 322 | attr_accessor :exe_context 323 | 324 | def initialize(var_env, source_elements) 325 | @source_elements = source_elements 326 | @var_env = var_env 327 | end 328 | 329 | # duplicate object 330 | # @see Base#deep_dup 331 | def deep_dup 332 | self.class.new(var_env, source_elements.deep_dup) 333 | end 334 | 335 | # Replaces children object 336 | # @see Base#replace 337 | def replace(from, to) 338 | if from == @source_elements 339 | @source_elements = to 340 | end 341 | end 342 | 343 | # Traverses this children and itself with given block. 344 | # @see Base#traverse 345 | def traverse(parent, &block) 346 | @source_elements.traverse(self, &block) 347 | yield parent, self 348 | end 349 | 350 | # compare object 351 | def ==(obj) 352 | self.class == obj.class and self.source_elements == obj.source_elements 353 | end 354 | 355 | # Returns a ECMAScript string containg the representation of element. 356 | # @see Base#to_js 357 | def to_js(options = {}) 358 | concat options, @source_elements 359 | end 360 | end 361 | end 362 | end 363 | 364 | -------------------------------------------------------------------------------- /lib/minjs/ecma262/env.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | module Minjs 3 | module ECMA262 4 | # class of Environment Record 5 | # 6 | # @see http://www.ecma-international.org/ecma-262 ECMA262 10.2.1 7 | class EnvRecord 8 | attr_reader :binding 9 | attr_reader :options 10 | 11 | def initialize(options = {}) 12 | @binding = {} 13 | @options = {} 14 | end 15 | 16 | # CreateMutableBinding(N, D) 17 | # 18 | # @see http://www.ecma-international.org/ecma-262 ECMA262 10.2.1 19 | def create_mutable_binding(n, d, options = {}) 20 | if n.kind_of? IdentifierName 21 | n = n.val 22 | end 23 | @binding[n] = {:value => nil} 24 | end 25 | 26 | # SetMutableBinding(N, V, S) 27 | # 28 | # @see http://www.ecma-international.org/ecma-262 ECMA262 10.2.1 29 | def set_mutable_binding(n, v, s, options = {}) 30 | if n.kind_of? IdentifierName 31 | n = n.val 32 | end 33 | @binding[n][:value] = v 34 | @binding[n].merge!(options || {}) 35 | end 36 | end 37 | 38 | # class of Declarative Environment Record 39 | # 40 | # @see http://www.ecma-international.org/ecma-262 ECMA262 10.2.1.1 41 | class DeclarativeEnvRecord < EnvRecord 42 | end 43 | 44 | # class of Object Environment Record 45 | # 46 | # @see http://www.ecma-international.org/ecma-262 ECMA262 10.2.1.2 47 | class ObjectEnvRecord < EnvRecord 48 | end 49 | 50 | # class of Lexical Environment 51 | # 52 | # @see http://www.ecma-international.org/ecma-262 ECMA262 10.2 53 | class LexEnv 54 | attr_accessor :record 55 | attr_reader :outer 56 | attr_reader :type 57 | 58 | def initialize(options = {}) 59 | @outer = options[:outer] 60 | @type = (options[:type] || :declarative) 61 | if options[:record] 62 | @record = ObjectEnvRecord.new 63 | elsif options[:type] == :declarative 64 | @record = DeclarativeEnvRecord.new 65 | elsif options[:type] == :object or true 66 | @record = ObjectEnvRecord.new 67 | end 68 | end 69 | 70 | # NewDeclarativeEnvironment(E) 71 | # 72 | # @see http://www.ecma-international.org/ecma-262 ECMA262 10.2.2.2 73 | def new_declarative_env(outer = nil) 74 | e = LexEnv.new(outer: (outer || self), type: :declarative) 75 | end 76 | 77 | # NewDeclarativeEnvironment(E) 78 | # 79 | # @see http://www.ecma-international.org/ecma-262 ECMA262 10.2.2.2 80 | def self.new_declarative_env(e) 81 | env = LexEnv.new(outer: e, type: :declarative) 82 | end 83 | 84 | # NewObjectEnvironment(O, E) 85 | # 86 | # @see http://www.ecma-international.org/ecma-262 ECMA262 10.2.2.3 87 | def new_object_env(object, outer = nil)#TODO 88 | raise 'TODO' 89 | e = LexEnv.new(outer: (outer || self), type: :object) 90 | object.val.each do |k, v| 91 | if k.id_name? 92 | e.create_mutable_binding(k) 93 | e.set_mutable_binding(k, v) 94 | end 95 | end 96 | end 97 | 98 | # debug 99 | def debug 100 | STDERR.puts @record.binding.keys.join(", ") 101 | end 102 | end 103 | 104 | # Class of Execution Contexts 105 | # 106 | # @see http://www.ecma-international.org/ecma-262 ECMA262 10.3 107 | class ExeContext 108 | attr_accessor :lex_env 109 | attr_accessor :var_env 110 | attr_accessor :this_binding 111 | def initialize(options = {}) 112 | @var_env = LexEnv.new(options) 113 | @lex_env = LexEnv.new(options) 114 | @this_binding = nil 115 | end 116 | end 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /lib/minjs/ecma262/punctuator.rb: -------------------------------------------------------------------------------- 1 | module Minjs 2 | module ECMA262 3 | # ECMA262 punctuator element 4 | # 5 | # @see http://www.ecma-international.org/ecma-262 ECMA262 7.7 6 | class Punctuator < Literal 7 | attr_reader :val 8 | 9 | @@sym = {} 10 | 11 | def initialize(val) 12 | @val = val.to_sym 13 | end 14 | 15 | # Returns punctuator object representation of string. 16 | # 17 | # @param val [String] punctuator 18 | def self.get(val) 19 | @@sym[val] ||= self.new(val) 20 | end 21 | 22 | # Returns a string containg the representation of punctuator. 23 | def to_s 24 | val.to_s 25 | end 26 | 27 | # Returns a ECMAScript string containg the representation of element. 28 | # @see Base#to_js 29 | def to_js 30 | val.to_s 31 | end 32 | 33 | # Return true if punctuator equals to other. 34 | # 35 | # @param obj other element. 36 | def ==(obj) 37 | self.class == obj.class and self.val == obj.val 38 | end 39 | end 40 | #punctuator 41 | PUNC_CONDIF = Punctuator.get('?') 42 | #punctuator 43 | PUNC_ASSIGN = Punctuator.get('=') 44 | #punctuator 45 | PUNC_DIVASSIGN = Punctuator.get('/=') 46 | #punctuator 47 | PUNC_MULASSIGN = Punctuator.get('*=') 48 | #punctuator 49 | PUNC_MODASSIGN = Punctuator.get('%=') 50 | #punctuator 51 | PUNC_ADDASSIGN = Punctuator.get('+=') 52 | #punctuator 53 | PUNC_SUBASSIGN = Punctuator.get('-=') 54 | #punctuator 55 | PUNC_LSHIFTASSIGN = Punctuator.get('<<=') 56 | #punctuator 57 | PUNC_RSHIFTASSIGN = Punctuator.get('>>=') 58 | #punctuator 59 | PUNC_URSHIFTASSIGN = Punctuator.get('>>>=') 60 | #punctuator 61 | PUNC_ANDASSIGN = Punctuator.get('&=') 62 | #punctuator 63 | PUNC_XORASSIGN = Punctuator.get('^=') 64 | #punctuator 65 | PUNC_ORASSIGN = Punctuator.get('|=') 66 | #punctuator 67 | PUNC_LOR = Punctuator.get('||') 68 | #punctuator 69 | PUNC_LAND = Punctuator.get('&&') 70 | #punctuator 71 | PUNC_OR = Punctuator.get('|') 72 | #punctuator 73 | PUNC_XOR = Punctuator.get('^') 74 | #punctuator 75 | PUNC_AND = Punctuator.get('&') 76 | #punctuator 77 | PUNC_EQ = Punctuator.get('==') 78 | #punctuator 79 | PUNC_NEQ = Punctuator.get('!=') 80 | #punctuator 81 | PUNC_SEQ = Punctuator.get('===') 82 | #punctuator 83 | PUNC_SNEQ = Punctuator.get('!==') 84 | #punctuator 85 | PUNC_LT = Punctuator.get('<') 86 | #punctuator 87 | PUNC_GT = Punctuator.get('>') 88 | #punctuator 89 | PUNC_LTEQ = Punctuator.get('<=') 90 | #punctuator 91 | PUNC_GTEQ = Punctuator.get('>=') 92 | #punctuator 93 | PUNC_LSHIFT = Punctuator.get('<<') 94 | #punctuator 95 | PUNC_RSHIFT = Punctuator.get('>>') 96 | #punctuator 97 | PUNC_URSHIFT = Punctuator.get('>>>') 98 | #punctuator 99 | PUNC_ADD = Punctuator.get('+') 100 | #punctuator 101 | PUNC_SUB = Punctuator.get('-') 102 | #punctuator 103 | PUNC_MUL = Punctuator.get('*') 104 | #punctuator 105 | PUNC_DIV = Punctuator.get('/') 106 | #punctuator 107 | PUNC_MOD = Punctuator.get('%') 108 | #punctuator 109 | PUNC_INC = Punctuator.get('++') 110 | #punctuator 111 | PUNC_DEC = Punctuator.get('--') 112 | #punctuator 113 | PUNC_NOT = Punctuator.get('~') 114 | #punctuator 115 | PUNC_LNOT = Punctuator.get('!') 116 | #punctuator 117 | PUNC_LPARENTHESIS = Punctuator.get('(') 118 | #punctuator 119 | PUNC_RPARENTHESIS = Punctuator.get(')') 120 | #punctuator 121 | PUNC_LSQBRAC = Punctuator.get('[') 122 | #punctuator 123 | PUNC_RSQBRAC = Punctuator.get(']') 124 | #punctuator 125 | PUNC_LCURLYBRAC = Punctuator.get('{') 126 | #punctuator 127 | PUNC_RCURLYBRAC = Punctuator.get('}') 128 | #punctuator 129 | PUNC_COMMA = Punctuator.get(',') 130 | #punctuator 131 | PUNC_COLON = Punctuator.get(':') 132 | #punctuator 133 | PUNC_SEMICOLON = Punctuator.get(';') 134 | #punctuator 135 | PUNC_PERIOD = Punctuator.get('.') 136 | 137 | Punctuator.class_eval { 138 | private_class_method :new 139 | } 140 | end 141 | end 142 | -------------------------------------------------------------------------------- /lib/minjs/lex.rb: -------------------------------------------------------------------------------- 1 | module Minjs 2 | #Lex 3 | module Lex 4 | end 5 | end 6 | require "minjs/lex/exceptions" 7 | require "minjs/lex/expression" 8 | require "minjs/lex/function" 9 | require "minjs/lex/program" 10 | require "minjs/lex/statement" 11 | require "minjs/lex/parser" 12 | -------------------------------------------------------------------------------- /lib/minjs/lex/exceptions.rb: -------------------------------------------------------------------------------- 1 | module Minjs::Lex 2 | # ParseError 3 | class ParseError < StandardError 4 | def initialize(error_message = nil, lex = nil) 5 | super(error_message) 6 | if lex 7 | @lex = lex 8 | @lex_pos = lex.pos 9 | end 10 | end 11 | 12 | # to string 13 | def to_s 14 | t = '' 15 | t << super 16 | t << "\n" 17 | if @lex 18 | row, col = @lex.row_col(@lex_pos) 19 | t << "row: #{row}, col: #{col}\n" 20 | t << @lex.debug_str(@lex_pos, row, col) 21 | end 22 | t 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/minjs/lex/expression.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | module Minjs::Lex 3 | # Expression 4 | module Expression 5 | include Minjs 6 | # Tests next literal is PrimaryExpression or not. 7 | # 8 | # If literal is PrimaryExpression 9 | # return ECMA262::Base object and 10 | # forward lexical parser position. 11 | # Otherwise return nil and position is not changed. 12 | # 13 | # @param var_env [EnvRecord] Lexical Environment 14 | # 15 | # @return [ECMA262::ECMA262Object] expression 16 | # 17 | # @see ECMA262 11.1 18 | def primary_exp(var_env) 19 | @logger.debug "*** primary_exp" 20 | 21 | if eql_lit?(ECMA262::ID_THIS) 22 | @logger.debug "*** primary_exp => this" 23 | return ECMA262::This.new 24 | end 25 | # (exp) 26 | if eql_lit?(ECMA262::PUNC_LPARENTHESIS) 27 | if a=exp(var_env, {}) and eql_lit?(ECMA262::PUNC_RPARENTHESIS) 28 | @logger.debug "*** primary_exp => ()" 29 | return ECMA262::ExpParen.new(a) 30 | else 31 | raise ParseError.new("no `)' at end of expression", self) 32 | end 33 | end 34 | 35 | t = literal(var_env) || 36 | identifier(var_env) || 37 | array_literal(var_env) || 38 | object_literal(var_env) 39 | 40 | @logger.debug { 41 | "*** primary_exp => #{t ? t.to_js : t}" 42 | } 43 | t 44 | end 45 | 46 | # Tests next literal is Literal or not 47 | # 48 | # If literal is Literal, 49 | # return ECMA262::Base object correspoding to expression and 50 | # forward lexical parser position. 51 | # Otherwise return nil and position is not changed. 52 | # 53 | # @param var_env [EnvRecord] Lexical Environment 54 | # @return [ECMA262::Base] expression 55 | # 56 | # @see ECMA262 7.8, 7.8.1, 7.8.2 57 | def literal(var_env) 58 | # Literal :: 59 | # NullLiteral 60 | # BooleanLiteral 61 | # NumericLiteral 62 | # StringLiteral 63 | # RegularExpressionLiteral 64 | a = peek_lit(:regexp) 65 | if a.kind_of? ECMA262::ECMA262Numeric or a.kind_of? ECMA262::ECMA262String or a.kind_of? ECMA262::ECMA262RegExp 66 | fwd_after_peek 67 | a 68 | elsif a .eql? ECMA262::ID_NULL 69 | fwd_after_peek 70 | ECMA262::Null.get 71 | elsif a .eql? ECMA262::ID_TRUE 72 | fwd_after_peek 73 | ECMA262::Boolean.get(:true) 74 | elsif a .eql? ECMA262::ID_FALSE 75 | fwd_after_peek 76 | ECMA262::Boolean.get(:false) 77 | else 78 | nil 79 | end 80 | end 81 | 82 | # Tests next literal is Identifier or not. 83 | # 84 | # If literal is Identifier 85 | # return ECMA262::Lit object and 86 | # forward lexical parser position. 87 | # Otherwise return nil and position is not changed. 88 | # 89 | # @param var_env [EnvRecord] Lexical Environment 90 | # 91 | # @return [ECMA262::Literal] expression 92 | # 93 | # @see ECMA262 11.1.2 94 | def identifier(var_env) 95 | a = peek_lit(:regexp) 96 | if a.kind_of? ECMA262::IdentifierName and !a.reserved? 97 | fwd_after_peek 98 | #a.var_env = var_env 99 | a 100 | else 101 | nil 102 | end 103 | end 104 | # Tests next literal is ArrayLiteral or not. 105 | # 106 | # If literal is ArrayLiteral 107 | # return ECMA262::ECMA262Array object and 108 | # forward lexical parser position. 109 | # Otherwise return nil and position is not changed. 110 | # 111 | # @param var_env [EnvRecord] Lexical Environment 112 | # 113 | # @return [ECMA262::ECMA262Array] expression 114 | # 115 | # @see ECMA262 11.1.4 116 | def array_literal(var_env) 117 | return nil unless eql_lit?(ECMA262::PUNC_LSQBRAC) 118 | t = [] 119 | while true 120 | if eql_lit?(ECMA262::PUNC_COMMA) 121 | t.push(nil) 122 | elsif eql_lit?(ECMA262::PUNC_RSQBRAC) 123 | break 124 | elsif a = assignment_exp(var_env, {}) 125 | t.push(a) 126 | eql_lit?(ECMA262::PUNC_COMMA) 127 | else 128 | raise ParseError.new("no `]' end of array", self) 129 | end 130 | end 131 | ECMA262::ECMA262Array.new(t) 132 | end 133 | # Tests next literal is ObjectLiteral or not. 134 | # 135 | # If literal is ObjectLiteral 136 | # return ECMA262::ECMA262Object object and 137 | # forward lexical parser position. 138 | # Otherwise return nil and position is not changed. 139 | # 140 | # @param var_env [EnvRecord] Lexical Environment 141 | # 142 | # @return [ECMA262::ECMA262Object] expression 143 | # 144 | # @see ECMA262 11.1.5 145 | def object_literal(var_env) 146 | # 147 | # 11.1.5 148 | # 149 | # ObjectLiteral : 150 | # { } 151 | # { PropertyNameAndValueList } 152 | # { PropertyNameAndValueList , } 153 | # 154 | return nil unless eql_lit?(ECMA262::PUNC_LCURLYBRAC) 155 | #{} 156 | if eql_lit?(ECMA262::PUNC_RCURLYBRAC) 157 | ECMA262::ECMA262Object.new([]) 158 | else 159 | ECMA262::ECMA262Object.new(property_name_and_value_list(var_env)) 160 | end 161 | end 162 | 163 | # Tests next literal is PropertyNameAndValueList or not. 164 | # 165 | # If literal is PropertyNameAndValueList 166 | # return Array object and 167 | # forward lexical parser position. 168 | # Otherwise return nil and position is not changed. 169 | # 170 | # @param var_env [EnvRecord] Lexical Environment 171 | # 172 | # @return [Array] expression 173 | # 174 | # @see ECMA262 11.1.5 175 | # 176 | def property_name_and_value_list(var_env) 177 | # PropertyNameAndValueList : 178 | # PropertyAssignment 179 | # PropertyNameAndValueList , PropertyAssignment 180 | # 181 | # PropertyAssignment : 182 | # PropertyName : AssignmentExpression 183 | # get PropertyName ( ) { FunctionBody } 184 | # set PropertyName ( PropertySetParameterList ) { FunctionBody } 185 | h = [] 186 | while !eof? 187 | #get 188 | if match_lit? ECMA262::ID_GET 189 | # {get : val} 190 | if eql_lit? ECMA262::PUNC_COLON 191 | b = assignment_exp(var_env, {}) 192 | h.push([ECMA262::ID_GET, b]) 193 | # {get name(){}} 194 | else 195 | new_var_env = ECMA262::LexEnv.new(outer: var_env) 196 | if(a = property_name(var_env) and 197 | eql_lit? ECMA262::PUNC_LPARENTHESIS and 198 | eql_lit? ECMA262::PUNC_RPARENTHESIS and 199 | eql_lit? ECMA262::PUNC_LCURLYBRAC and 200 | b = func_body(new_var_env) and 201 | eql_lit? ECMA262::PUNC_RCURLYBRAC) 202 | h.push([a, f = ECMA262::StFunc.new(new_var_env, ECMA262::ID_GET, [], b, :getter => true)]) 203 | #new_var_env.func = f 204 | else 205 | raise ParseError.new("unexpceted token", self) 206 | end 207 | end 208 | #set 209 | elsif match_lit?(ECMA262::ID_SET) 210 | # {set : val} 211 | if eql_lit? ECMA262::PUNC_COLON 212 | b = assignment_exp(var_env, {}) 213 | h.push([ECMA262::ID_SET, b]) 214 | # {set name(arg){}} 215 | else 216 | new_var_env = ECMA262::LexEnv.new(outer: var_env) 217 | if(a = property_name(var_env) and 218 | eql_lit? ECMA262::PUNC_LPARENTHESIS and 219 | arg = property_set_parameter_list(new_var_env) and 220 | eql_lit? ECMA262::PUNC_RPARENTHESIS and 221 | eql_lit? ECMA262::PUNC_LCURLYBRAC and 222 | b = func_body(new_var_env) and 223 | eql_lit? ECMA262::PUNC_RCURLYBRAC) 224 | h.push([a, f = ECMA262::StFunc.new(new_var_env, ECMA262::ID_SET, arg, b, :setter => true)]) 225 | #new_var_env.func = f 226 | else 227 | raise ParseError.new("unexpceted token", self) 228 | end 229 | end 230 | #property 231 | elsif(a = property_name(var_env) and 232 | eql_lit? ECMA262::PUNC_COLON and 233 | b = assignment_exp(var_env, {})) 234 | h.push([a, b]) 235 | else 236 | raise ParseError.new("unexpceted token", self) 237 | end 238 | 239 | if eql_lit?(ECMA262::PUNC_COMMA) 240 | break if eql_lit?(ECMA262::PUNC_RCURLYBRAC) 241 | elsif eql_lit?(ECMA262::PUNC_RCURLYBRAC) 242 | break 243 | else 244 | raise ParseError.new("no `}' end of object", self) 245 | end 246 | end 247 | h 248 | end 249 | 250 | # Tests next literal is PropertyName or not. 251 | # 252 | # If literal is PropertyName 253 | # return ECMA262::Base object and 254 | # forward lexical parser position. 255 | # Otherwise return nil and position is not changed. 256 | # 257 | # @param var_env [EnvRecord] Lexical Environment 258 | # 259 | # @return [ECMA262::Base] expression 260 | # 261 | # @see ECMA262 11.1.5 262 | # 11.1.5 263 | # 264 | # 265 | def property_name(var_env) 266 | # PropertyName : 267 | # IdentifierName 268 | # StringLiteral 269 | # NumericLiteral 270 | a = fwd_lit(nil) 271 | if a.kind_of?(ECMA262::ECMA262String) 272 | a 273 | elsif a.kind_of?(ECMA262::IdentifierName) 274 | ECMA262::ECMA262String.new(a.to_js) 275 | elsif a.kind_of?(ECMA262::ECMA262Numeric) 276 | a 277 | elsif a.eql?(ECMA262::PUNC_COLON) 278 | nil 279 | else 280 | raise ParseError.new("unexpceted token", self) 281 | end 282 | end 283 | 284 | # Tests next literal is PropertySetParameterList or not. 285 | # 286 | # If literal is PropertySetParameterList 287 | # return them and 288 | # forward lexical parser position. 289 | # Otherwise return nil and position is not changed. 290 | # 291 | # @param var_env [EnvRecord] Lexical Environment 292 | # 293 | # @return [Array] arguments 294 | # 295 | # @see ECMA262 11.1.5 296 | def property_set_parameter_list(var_env) 297 | # PropertySetParameterList : 298 | # Identifier 299 | argName = identifier(var_env) 300 | 301 | var_env.record.create_mutable_binding(argName, nil) 302 | var_env.record.set_mutable_binding(argName, :undefined, nil, _parameter_list: true) 303 | [argName] 304 | end 305 | 306 | # Tests next literal is LeftHandSideExpression or not. 307 | # 308 | # If literal is LeftHandSideExpression, 309 | # return ECMA262::Base object correspoding to expression and 310 | # forward lexical parser position. 311 | # Otherwise return nil and position is not changed. 312 | # 313 | # @param var_env [EnvRecord] Lexical Environment 314 | # @return [ECMA262::Base] expression 315 | # 316 | # @see ECMA262 11.2 317 | def left_hand_side_exp(var_env) 318 | # 319 | # LeftHandSideExpression : 320 | # NewExpression 321 | # CallExpression 322 | # 323 | @logger.debug "*** left_hand_side_exp" 324 | 325 | t = call_exp(var_env) || new_exp(var_env) 326 | #t = new_exp(var_env) || call_exp(var_env) 327 | 328 | @logger.debug{ 329 | "*** left_hand_side_exp => #{t ? t.to_js: t}" 330 | } 331 | t 332 | end 333 | 334 | # Tests next literal is NewExpression or not. 335 | # 336 | # If literal is NewExpression, 337 | # return ECMA262::Base object correspoding to expression and 338 | # forward lexical parser position. 339 | # Otherwise return nil and position is not changed. 340 | # 341 | # The NewExpression only matchs no-arguments-constructor because 342 | # member expression also has "new MemberExpression Arguments" 343 | # 344 | # For example, 345 | # 346 | # 1. new A; 347 | # 2. new A[B]; 348 | # 3. new A.B; 349 | # 4. new A.B(); 350 | # 5. new new B(); 351 | # 6. A(); 352 | # 353 | # 1 to 3 are NewExpression. 354 | # 4 is MemberExpression. 355 | # 5 's first new is NewExpression and second one is MemberExpression. 356 | # 6 is CallExpression 357 | # 358 | # In the results, NewExpression can be rewritten as follows: 359 | # 360 | # NewExpression : 361 | # MemberExpression [lookahead ∉ {(}] 362 | # new NewExpression [lookahead ∉ {(}] 363 | # 364 | # @param var_env [EnvRecord] Lexical Environment 365 | # @option options :no_in [Boolean] If set, the parser interpret as RelationExpressionNoIn 366 | # 367 | # @return [ECMA262::Base] expression 368 | # 369 | # @see ECMA262 11.2 370 | # @see #call_exp 371 | def new_exp(var_env) 372 | # NewExpression : 373 | # MemberExpression 374 | # new NewExpression 375 | if eql_lit?(ECMA262::ID_NEW) 376 | if a = new_exp(var_env) 377 | if eql_lit? ECMA262::PUNC_LPARENTHESIS 378 | # minjs evaluate CallExpression first, so 379 | # program never falls to here. 380 | raise ParseError.new("unexpceted token", self) 381 | nil # this is not NewExpression, may be MemberExpression. 382 | end 383 | #puts "new_exp> #{a.to_js}" 384 | ECMA262::ExpNew.new(a, nil) 385 | else 386 | # minjs evaluate CallExpression first, so 387 | # raise exception when program falls to here. 388 | raise ParseError.new("unexpceted token", self) 389 | #nil 390 | end 391 | else 392 | member_exp(var_env) 393 | end 394 | end 395 | # Tests next literal is CallExpression or not. 396 | # 397 | # If literal is CallExpression, 398 | # return ECMA262::Base object correspoding to expression and 399 | # forward lexical parser position. 400 | # Otherwise return nil and position is not changed. 401 | # 402 | # @see ECMA262 11.2 403 | # @see #new_exp 404 | def call_exp(var_env) 405 | # CallExpression : 406 | # MemberExpression Arguments 407 | # CallExpression Arguments 408 | # CallExpression [ Expression ] 409 | # CallExpression . IdentifierName 410 | if a = member_exp(var_env) 411 | if b = arguments(var_env) 412 | t = ECMA262::ExpCall.new(a, b) 413 | # if b is nil, this may be MemberExpression of NewExpression 414 | else 415 | return a 416 | end 417 | else 418 | return nil 419 | end 420 | 421 | while true 422 | if b = arguments(var_env) 423 | t = ECMA262::ExpCall.new(t, b) 424 | elsif eql_lit?(ECMA262::PUNC_LSQBRAC) 425 | if b=exp(var_env, {}) and eql_lit?(ECMA262::PUNC_RSQBRAC) 426 | t = ECMA262::ExpPropBrac.new(t, b) 427 | else 428 | raise ParseError.new("unexpceted token", self) 429 | end 430 | elsif eql_lit?(ECMA262::PUNC_PERIOD) 431 | if (b=fwd_lit(nil)).kind_of?(ECMA262::IdentifierName) 432 | t = ECMA262::ExpProp.new(t, b) 433 | else 434 | raise ParseError.new("unexpceted token", self) 435 | end 436 | else 437 | break 438 | end 439 | end 440 | t 441 | end 442 | 443 | # Tests next literal is MemberExpression or not. 444 | # 445 | # If literal is MemberExpression, 446 | # return ECMA262::Base object correspoding to expression and 447 | # forward lexical parser position. 448 | # Otherwise return nil and position is not changed. 449 | # 450 | # @param var_env [EnvRecord] Lexical Environment 451 | # @option options :no_in [Boolean] If set, the parser interpret as RelationExpressionNoIn 452 | # 453 | # @return [ECMA262::Base] expression 454 | # 455 | # @see ECMA262 11.2 456 | # 457 | def member_exp(var_env) 458 | # MemberExpression : 459 | # PrimaryExpression 460 | # FunctionExpression 461 | # MemberExpression [ Expression ] 462 | # MemberExpression . IdentifierName 463 | # new MemberExpression Arguments 464 | # 465 | t = eval_lit{ 466 | if eql_lit? ECMA262::ID_NEW 467 | if a = member_exp(var_env) 468 | b = arguments(var_env) 469 | # if b is nil, this may be NewExpression 470 | if b 471 | s = b.collect{|x| x.to_js}.join(','); 472 | #puts "member_exp> [new] #{a.to_js} (#{s})" 473 | next ECMA262::ExpNew.new(a, b) 474 | else 475 | return nil 476 | end 477 | else 478 | return nil 479 | end 480 | end 481 | } || primary_exp(var_env) || func_exp(var_env) 482 | return nil if t.nil? 483 | 484 | while true 485 | if eql_lit?(ECMA262::PUNC_LSQBRAC) 486 | if b=exp(var_env, {}) and eql_lit?(ECMA262::PUNC_RSQBRAC) 487 | t = ECMA262::ExpPropBrac.new(t, b) 488 | else 489 | raise ParseError.new("unexpceted token", self) 490 | end 491 | elsif eql_lit?(ECMA262::PUNC_PERIOD) 492 | if (b=fwd_lit(nil)).kind_of?(ECMA262::IdentifierName) 493 | t = ECMA262::ExpProp.new(t, b) 494 | else 495 | raise ParseError.new("unexpceted token", self) 496 | end 497 | else 498 | break 499 | end 500 | end 501 | t 502 | end 503 | # Tests next literal is Arguments or not. 504 | # 505 | # If literal is Arguments 506 | # return them and 507 | # forward lexical parser position. 508 | # Otherwise return nil and position is not changed. 509 | # 510 | # @param var_env [EnvRecord] Lexical Environment 511 | # 512 | # @return [Array] arguments 513 | # 514 | # @see ECMA262 11.2 515 | def arguments(var_env) 516 | # Arguments : 517 | # ( ) 518 | # ( ArgumentList ) 519 | return nil if eql_lit?(ECMA262::PUNC_LPARENTHESIS).nil? 520 | return [] if eql_lit?(ECMA262::PUNC_RPARENTHESIS) 521 | 522 | args = [] 523 | while true 524 | if t = assignment_exp(var_env, {}) 525 | args.push(t) 526 | else 527 | raise ParseError.new("unexpected token", self) 528 | end 529 | if eql_lit?(ECMA262::PUNC_COMMA) 530 | ; 531 | elsif eql_lit?(ECMA262::PUNC_RPARENTHESIS) 532 | break 533 | else 534 | raise ParseError.new("unexpected token", self) 535 | end 536 | end 537 | args 538 | end 539 | 540 | # Tests next literal is PostfixExpression or not. 541 | # 542 | # If literal is PostfixExpression 543 | # return ECMA262::Base object and 544 | # forward lexical parser position. 545 | # Otherwise return nil and position is not changed. 546 | # 547 | # @param var_env [EnvRecord] Lexical Environment 548 | # 549 | # @return [ECMA262::ECMA262Object] expression 550 | # 551 | # @see ECMA262 11.3 552 | def postfix_exp(var_env) 553 | exp = left_hand_side_exp(var_env) 554 | return nil if exp.nil? 555 | if punc = (eql_lit_nolt?(ECMA262::PUNC_INC) || 556 | eql_lit_nolt?(ECMA262::PUNC_DEC)) 557 | if punc == ECMA262::PUNC_INC 558 | ECMA262::ExpPostInc.new(exp) 559 | else 560 | ECMA262::ExpPostDec.new(exp) 561 | end 562 | else 563 | exp 564 | end 565 | end 566 | 567 | # Tests next literal is UnaryExpression or not. 568 | # 569 | # If literal is UnaryExpression, 570 | # return ECMA262::Base object correspoding to expression and 571 | # forward lexical parser position. 572 | # Otherwise return nil and position is not changed. 573 | # 574 | # @param var_env [EnvRecord] Lexical Environment 575 | # @option options :no_in [Boolean] If set, the parser interpret as RelationExpressionNoIn 576 | # @return [ECMA262::Base] expression 577 | # 578 | # see ECMA262 11.4 579 | def unary_exp(var_env) 580 | if punc = (eql_lit?(ECMA262::ID_DELETE) || 581 | eql_lit?(ECMA262::ID_VOID) || 582 | eql_lit?(ECMA262::ID_TYPEOF) || 583 | eql_lit?(ECMA262::PUNC_INC) || 584 | eql_lit?(ECMA262::PUNC_DEC) || 585 | eql_lit?(ECMA262::PUNC_ADD) || 586 | eql_lit?(ECMA262::PUNC_SUB) || 587 | eql_lit?(ECMA262::PUNC_NOT) || 588 | eql_lit?(ECMA262::PUNC_LNOT)) 589 | exp = unary_exp(var_env) 590 | if exp.nil? 591 | raise ParseError.new("unexpceted token", self) 592 | elsif punc == ECMA262::PUNC_INC 593 | ECMA262::ExpPreInc.new(exp) 594 | elsif punc == ECMA262::PUNC_DEC 595 | ECMA262::ExpPreDec.new(exp) 596 | elsif punc == ECMA262::PUNC_ADD 597 | ECMA262::ExpPositive.new(exp) 598 | elsif punc == ECMA262::PUNC_SUB 599 | ECMA262::ExpNegative.new(exp) 600 | elsif punc == ECMA262::PUNC_NOT 601 | ECMA262::ExpBitwiseNot.new(exp) 602 | elsif punc == ECMA262::PUNC_LNOT 603 | ECMA262::ExpLogicalNot.new(exp) 604 | elsif punc.respond_to?(:val) 605 | if punc.val == :delete 606 | ECMA262::ExpDelete.new(exp) 607 | elsif punc.val == :void 608 | ECMA262::ExpVoid.new(exp) 609 | elsif punc.val == :typeof 610 | ECMA262::ExpTypeof.new(exp) 611 | end 612 | end 613 | else 614 | postfix_exp(var_env) 615 | end 616 | end 617 | 618 | # Tests next literal is MultiplicativeExpression or not. 619 | # 620 | # If literal is MultiplicativeExpression, 621 | # return ECMA262::Base object correspoding to expression and 622 | # forward lexical parser position. 623 | # Otherwise return nil and position is not changed. 624 | # 625 | # @param var_env [EnvRecord] Lexical Environment 626 | # @option options :no_in [Boolean] If set, the parser interpret as RelationExpressionNoIn 627 | # 628 | # @return [ECMA262::Base] expression 629 | # 630 | # @see ECMA262 11.5 631 | def multiplicative_exp(var_env) 632 | a = unary_exp(var_env) 633 | return nil if !a 634 | t = a 635 | while punc = eql_lit?(ECMA262::PUNC_MUL) || 636 | eql_lit?(ECMA262::PUNC_DIV, :div) || 637 | eql_lit?(ECMA262::PUNC_MOD) 638 | 639 | if b = unary_exp(var_env) 640 | if punc == ECMA262::PUNC_MUL 641 | t = ECMA262::ExpMul.new(t, b) 642 | elsif punc == ECMA262::PUNC_DIV 643 | t = ECMA262::ExpDiv.new(t, b) 644 | else 645 | t = ECMA262::ExpMod.new(t, b) 646 | end 647 | else 648 | raise ParseError.new("unexpceted token", self) 649 | end 650 | end 651 | t 652 | end 653 | 654 | # Tests next literal is AdditiveExpression or not. 655 | # 656 | # If literal is AdditiveExpression, 657 | # return ECMA262::Base object correspoding to expression and 658 | # forward lexical parser position. 659 | # Otherwise return nil and position is not changed. 660 | # 661 | # @param var_env [EnvRecord] Lexical Environment 662 | # 663 | # @return [ECMA262::Base] expression 664 | # 665 | # @see ECMA262 11.6 666 | def additive_exp(var_env) 667 | # AdditiveExpression : 668 | # MultiplicativeExpression AdditiveExpression + 669 | # MultiplicativeExpression AdditiveExpression - 670 | # MultiplicativeExpression 671 | a = multiplicative_exp(var_env) 672 | return nil if !a 673 | 674 | t = a 675 | while punc = eql_lit?(ECMA262::PUNC_ADD) || eql_lit?(ECMA262::PUNC_SUB) 676 | if b = multiplicative_exp(var_env) 677 | if punc == ECMA262::PUNC_ADD 678 | t = ECMA262::ExpAdd.new(t, b) 679 | else 680 | t = ECMA262::ExpSub.new(t, b) 681 | end 682 | else 683 | raise ParseError.new("unexpceted token", self) 684 | end 685 | end 686 | t 687 | end 688 | # Tests next literal is ShiftExpression or not. 689 | # 690 | # If literal is ShiftExpression, 691 | # return ECMA262::Base object correspoding to expression and 692 | # forward lexical parser position. 693 | # Otherwise return nil and position is not changed. 694 | # 695 | # @param var_env [EnvRecord] Lexical Environment 696 | # @option options :no_in [Boolean] If set, the parser interpret as RelationExpressionNoIn 697 | # @return [ECMA262::Base] expression 698 | # 699 | # see ECMA262 11.8 700 | def shift_exp(var_env) 701 | a = additive_exp(var_env) 702 | return nil if !a 703 | 704 | t = a 705 | while punc = eql_lit?(ECMA262::PUNC_LSHIFT) || 706 | eql_lit?(ECMA262::PUNC_RSHIFT) || 707 | eql_lit?(ECMA262::PUNC_URSHIFT) 708 | if b = additive_exp(var_env) 709 | if punc == ECMA262::PUNC_LSHIFT 710 | t = ECMA262::ExpLShift.new(t, b) 711 | elsif punc == ECMA262::PUNC_RSHIFT 712 | t = ECMA262::ExpRShift.new(t, b) 713 | elsif punc == ECMA262::PUNC_URSHIFT 714 | t = ECMA262::ExpURShift.new(t, b) 715 | end 716 | else 717 | raise ParseError.new("unexpceted token", self) 718 | end 719 | end 720 | t 721 | end 722 | # Tests next literal is RelationalExpression or not. 723 | # 724 | # If literal is RelationalExpression, 725 | # return ECMA262::Base object correspoding to expression and 726 | # forward lexical parser position. 727 | # Otherwise return nil and position is not changed. 728 | # 729 | # @param var_env [EnvRecord] Lexical Environment 730 | # @option options :no_in [Boolean] If set, the parser interpret as RelationExpressionNoIn 731 | # @return [ECMA262::Base] expression 732 | # 733 | # see ECMA262 11.8 734 | def relational_exp(var_env, options) 735 | #RelationalExpression : 736 | # ShiftExpression 737 | # RelationalExpression < ShiftExpression 738 | # RelationalExpression > ShiftExpression 739 | # RelationalExpression <= ShiftExpression 740 | # RelationalExpression >= ShiftExpression 741 | # RelationalExpression instanceof ShiftExpression 742 | # RelationalExpression in ShiftExpression 743 | a = shift_exp(var_env) 744 | return nil if !a 745 | 746 | t = a 747 | while (punc = eql_lit?(ECMA262::PUNC_LT) || eql_lit?(ECMA262::PUNC_GT) || 748 | eql_lit?(ECMA262::PUNC_LTEQ) || eql_lit?(ECMA262::PUNC_GTEQ) || 749 | eql_lit?(ECMA262::ID_INSTANCEOF) || (!options[:no_in] && eql_lit?(ECMA262::ID_IN))) 750 | if b = shift_exp(var_env) 751 | if punc == ECMA262::PUNC_LT 752 | t = ECMA262::ExpLt.new(t, b) 753 | elsif punc == ECMA262::PUNC_GT 754 | t = ECMA262::ExpGt.new(t, b) 755 | elsif punc == ECMA262::PUNC_LTEQ 756 | t = ECMA262::ExpLtEq.new(t, b) 757 | elsif punc == ECMA262::PUNC_GTEQ 758 | t = ECMA262::ExpGtEq.new(t, b) 759 | elsif punc.val == :instanceof 760 | t = ECMA262::ExpInstanceOf.new(t, b) 761 | elsif !options[:no_in] and punc.val == :in 762 | t = ECMA262::ExpIn.new(t, b) 763 | else 764 | end 765 | else 766 | raise ParseError.new("unexpceted token", self) 767 | end 768 | end 769 | t 770 | end 771 | # Tests next literal is EqualityExpression or not. 772 | # 773 | # If literal is EqualityExpression, 774 | # return ECMA262::Base object correspoding to expression and 775 | # forward lexical parser position. 776 | # Otherwise return nil and position is not changed. 777 | # 778 | # @param var_env [EnvRecord] Lexical Environment 779 | # @option options :no_in [Boolean] If set, the parser interpret as RelationExpressionNoIn 780 | # 781 | # @return [ECMA262::Base] expression 782 | # 783 | # @see ECMA262 11.9 784 | def equality_exp(var_env, options) 785 | a = relational_exp(var_env, options) 786 | return nil if !a 787 | 788 | t = a 789 | while punc = eql_lit?(ECMA262::PUNC_EQ) || 790 | eql_lit?(ECMA262::PUNC_NEQ) || 791 | eql_lit?(ECMA262::PUNC_SEQ) || 792 | eql_lit?(ECMA262::PUNC_SNEQ) 793 | if b = relational_exp(var_env, options) 794 | if punc == ECMA262::PUNC_EQ 795 | t = ECMA262::ExpEq.new(t, b) 796 | elsif punc == ECMA262::PUNC_NEQ 797 | t = ECMA262::ExpNotEq.new(t, b) 798 | elsif punc == ECMA262::PUNC_SEQ 799 | t = ECMA262::ExpStrictEq.new(t, b) 800 | elsif punc == ECMA262::PUNC_SNEQ 801 | t = ECMA262::ExpStrictNotEq.new(t, b) 802 | end 803 | else 804 | raise ParseError.new("unexpceted token", self) 805 | end 806 | end 807 | t 808 | end 809 | 810 | # Tests next literal is BitwiseAndExpression or not. 811 | # 812 | # If literal is BitwiseAndExpression, 813 | # return ECMA262::Base object correspoding to expression and 814 | # forward lexical parser position. 815 | # Otherwise return nil and position is not changed. 816 | # 817 | # @param var_env [EnvRecord] Lexical Environment 818 | # @option options :no_in [Boolean] If set, the parser interpret as RelationExpressionNoIn 819 | # 820 | # @return [ECMA262::Base] expression 821 | # 822 | # @see ECMA262 11.10 823 | def bitwise_and_exp(var_env, options) 824 | a = equality_exp(var_env, options) 825 | return nil if !a 826 | 827 | t = a 828 | while punc = eql_lit?(ECMA262::PUNC_AND) 829 | if b = equality_exp(var_env, options) 830 | t = ECMA262::ExpAnd.new(t, b) 831 | else 832 | raise ParseError.new("unexpceted token", self) 833 | end 834 | end 835 | t 836 | end 837 | 838 | # Tests next literal is BitwiseXorExpression or not. 839 | # 840 | # If literal is BitwiseXorExpression, 841 | # return ECMA262::Base object correspoding to expression and 842 | # forward lexical parser position. 843 | # Otherwise return nil and position is not changed. 844 | # 845 | # @param var_env [EnvRecord] Lexical Environment 846 | # @option options :no_in [Boolean] If set, the parser interpret as RelationExpressionNoIn 847 | # 848 | # @return [ECMA262::Base] expression 849 | # 850 | # @see ECMA262 11.10 851 | def bitwise_xor_exp(var_env, options) 852 | a = bitwise_and_exp(var_env, options) 853 | return nil if !a 854 | 855 | t = a 856 | while punc = eql_lit?(ECMA262::PUNC_XOR) 857 | if b = bitwise_and_exp(var_env, options) 858 | t = ECMA262::ExpXor.new(t, b) 859 | else 860 | raise ParseError.new("unexpceted token", self) 861 | end 862 | end 863 | 864 | t 865 | end 866 | 867 | # Tests next literal is BitwiseOrExpression or not. 868 | # 869 | # If literal is BitwiseOrExpression, 870 | # return ECMA262::Base object correspoding to expression and 871 | # forward lexical parser position. 872 | # Otherwise return nil and position is not changed. 873 | # 874 | # @param var_env [EnvRecord] Lexical Environment 875 | # @option options :no_in [Boolean] If set, the parser interpret as RelationExpressionNoIn 876 | # 877 | # @return [ECMA262::Base] expression 878 | # 879 | # @see ECMA262 11.10 880 | def bitwise_or_exp(var_env, options) 881 | a = bitwise_xor_exp(var_env, options) 882 | return nil if !a 883 | 884 | t = a 885 | while punc = eql_lit?(ECMA262::PUNC_OR) 886 | if b = bitwise_xor_exp(var_env, options) 887 | t = ECMA262::ExpOr.new(t, b) 888 | else 889 | raise ParseError.new("unexpceted token", self) 890 | end 891 | end 892 | t 893 | end 894 | 895 | # Tests next literal is LogicalAndExpression or not 896 | # 897 | # If literal is LogicalAndExpression, 898 | # return ECMA262::Base object correspoding to expression and 899 | # forward lexical parser position. 900 | # Otherwise return nil and position is not changed. 901 | # 902 | # @param var_env [EnvRecord] Lexical Environment 903 | # @return [ECMA262::Base] expression 904 | # @option options :no_in [Boolean] If set, the parser interpret as RelationExpressionNoIn 905 | # 906 | # @see ECMA262 11.11 907 | def logical_and_exp(var_env, options) 908 | a = bitwise_or_exp(var_env, options) 909 | return nil if !a 910 | 911 | t = a 912 | while punc = eql_lit?(ECMA262::PUNC_LAND) 913 | if b = bitwise_or_exp(var_env, options) 914 | t = ECMA262::ExpLogicalAnd.new(t, b) 915 | else 916 | raise ParseError.new("unexpceted token", self) 917 | end 918 | end 919 | 920 | t 921 | end 922 | 923 | # Tests next literal is LogicalOrExpression or not 924 | # 925 | # If literal is LogicalOrExpression, 926 | # return ECMA262::Base object correspoding to expression and 927 | # forward lexical parser position. 928 | # Otherwise return nil and position is not changed. 929 | # 930 | # @param var_env [EnvRecord] Lexical Environment 931 | # @return [ECMA262::Base] expression 932 | # @option options :no_in [Boolean] If set, the parser interpret as RelationExpressionNoIn 933 | # 934 | # @see ECMA262 11.12 935 | def logical_or_exp(var_env, options) 936 | a = logical_and_exp(var_env, options) 937 | return nil if !a 938 | 939 | t = a 940 | while punc = eql_lit?(ECMA262::PUNC_LOR) 941 | if b = logical_and_exp(var_env, options) 942 | t = ECMA262::ExpLogicalOr.new(t, b) 943 | else 944 | raise ParseError.new("unexpceted token", self) 945 | end 946 | end 947 | 948 | t 949 | end 950 | # Tests next literal is ConditionalExpression or not. 951 | # 952 | # If literal is ConditionalExpression, 953 | # return ECMA262::Base object correspoding to expression and 954 | # forward lexical parser position. 955 | # Otherwise return nil and position is not changed. 956 | # 957 | # @param var_env [EnvRecord] Lexical Environment 958 | # 959 | # @return [ECMA262::Base] expression 960 | # 961 | # @see ECMA262 11.12 962 | def cond_exp(var_env, options) 963 | a = logical_or_exp(var_env, options) 964 | return nil if !a 965 | 966 | if eql_lit?(ECMA262::PUNC_CONDIF) 967 | if b=assignment_exp(var_env, options) and eql_lit?(ECMA262::PUNC_COLON) and c=assignment_exp(var_env, options) 968 | ECMA262::ExpCond.new(a, b, c) 969 | else 970 | raise ParseError.new("unexpceted token", self) 971 | end 972 | else 973 | a 974 | end 975 | end 976 | # Tests next literal is AssignmentExpression or not. 977 | # 978 | # If literal is AssignmentExpression, 979 | # return ECMA262::Base object correspoding to expression and 980 | # forward lexical parser position. 981 | # Otherwise return nil and position is not changed. 982 | # 983 | # @param var_env [EnvRecord] Lexical Environment 984 | # @option options :no_in [Boolean] If set, the parser interpret as RelationExpressionNoIn 985 | # 986 | # @see ECMA262 11.13 987 | def assignment_exp(var_env, options) 988 | # AssignmentExpression : 989 | # ConditionalExpression 990 | # LeftHandSideExpression = AssignmentExpression 991 | # LeftHandSideExpression AssignmentOperator AssignmentExpression 992 | @logger.debug "*** assignment_exp" 993 | 994 | t = cond_exp(var_env, options) 995 | return nil if t.nil? 996 | 997 | if !t.left_hand_side_exp? 998 | return t 999 | end 1000 | left_hand = t 1001 | punc = peek_lit(:div) 1002 | if punc == ECMA262::PUNC_ASSIGN || 1003 | punc == ECMA262::PUNC_DIVASSIGN || 1004 | punc == ECMA262::PUNC_MULASSIGN || 1005 | punc == ECMA262::PUNC_MODASSIGN || 1006 | punc == ECMA262::PUNC_ADDASSIGN || 1007 | punc == ECMA262::PUNC_SUBASSIGN || 1008 | punc == ECMA262::PUNC_LSHIFTASSIGN || 1009 | punc == ECMA262::PUNC_RSHIFTASSIGN || 1010 | punc == ECMA262::PUNC_URSHIFTASSIGN || 1011 | punc == ECMA262::PUNC_ANDASSIGN || 1012 | punc == ECMA262::PUNC_ORASSIGN || 1013 | punc == ECMA262::PUNC_XORASSIGN 1014 | fwd_after_peek 1015 | if b = assignment_exp(var_env, options) 1016 | case punc 1017 | when ECMA262::PUNC_ASSIGN 1018 | ECMA262::ExpAssign.new(left_hand, b) 1019 | when ECMA262::PUNC_DIVASSIGN 1020 | ECMA262::ExpDivAssign.new(left_hand, b) 1021 | when ECMA262::PUNC_MULASSIGN 1022 | ECMA262::ExpMulAssign.new(left_hand, b) 1023 | when ECMA262::PUNC_MODASSIGN 1024 | ECMA262::ExpModAssign.new(left_hand, b) 1025 | when ECMA262::PUNC_ADDASSIGN 1026 | ECMA262::ExpAddAssign.new(left_hand, b) 1027 | when ECMA262::PUNC_SUBASSIGN 1028 | ECMA262::ExpSubAssign.new(left_hand, b) 1029 | when ECMA262::PUNC_LSHIFTASSIGN 1030 | ECMA262::ExpLShiftAssign.new(left_hand, b) 1031 | when ECMA262::PUNC_RSHIFTASSIGN 1032 | ECMA262::ExpRShiftAssign.new(left_hand, b) 1033 | when ECMA262::PUNC_URSHIFTASSIGN 1034 | ECMA262::ExpURShiftAssign.new(left_hand, b) 1035 | when ECMA262::PUNC_ANDASSIGN 1036 | ECMA262::ExpAndAssign.new(left_hand, b) 1037 | when ECMA262::PUNC_ORASSIGN 1038 | ECMA262::ExpOrAssign.new(left_hand, b) 1039 | when ECMA262::PUNC_XORASSIGN 1040 | ECMA262::ExpXorAssign.new(left_hand, b) 1041 | else 1042 | raise "internal error" 1043 | end 1044 | else 1045 | raise ParseError.new("unexpceted token", self) 1046 | end 1047 | else 1048 | @logger.debug { 1049 | "*** assignment_exp => #{t ? t.to_js : t}" 1050 | } 1051 | t 1052 | end 1053 | end 1054 | 1055 | # Tests next literal is Expression or not. 1056 | # 1057 | # If literal is Expression, 1058 | # return ECMA262::Base object correspoding to expression and 1059 | # forward lexical parser position. 1060 | # Otherwise return nil and position is not changed. 1061 | # 1062 | # @param var_env [EnvRecord] Lexical Environment 1063 | # @option options :no_in [Boolean] If set, the parser interpret as RelationExpressionNoIn 1064 | # 1065 | # @return [ECMA262::Base] expression 1066 | # 1067 | # @see ECMA262 11.14 1068 | def exp(var_env, options) 1069 | # Expression : 1070 | # AssignmentExpression 1071 | # Expression , AssignmentExpression 1072 | @logger.debug "*** expression" 1073 | 1074 | t = assignment_exp(var_env, options) 1075 | return nil if t.nil? 1076 | while punc = eql_lit?(ECMA262::PUNC_COMMA) 1077 | if b = assignment_exp(var_env, options) 1078 | t = ECMA262::ExpComma.new(t, b) 1079 | else 1080 | raise ParseError.new("unexpceted token", self) 1081 | end 1082 | end 1083 | @logger.debug{ 1084 | "*** expression => #{t ? t.to_js : t}" 1085 | } 1086 | t 1087 | end 1088 | end 1089 | end 1090 | -------------------------------------------------------------------------------- /lib/minjs/lex/function.rb: -------------------------------------------------------------------------------- 1 | module Minjs::Lex 2 | # Function 3 | module Function 4 | include Minjs 5 | # Tests next literal is FunctionDeclaration or not. 6 | # 7 | # If literal is FunctionDeclaration 8 | # return ECMA262::StFunc object and 9 | # forward lexical parser position. 10 | # Otherwise return nil and position is not changed. 11 | # 12 | # @see http://www.ecma-international.org/ecma-262 ECMA262 13 13 | # 14 | # @note 15 | # The function declaration in statement(block) is not permitted by ECMA262. 16 | # However, almost all implementation permit it, so minjs cannot raise 17 | # exception even if function declarataion in block. 18 | # 19 | def func_declaration(var_env) 20 | # FunctionDeclaration : 21 | # function Identifier ( FormalParameterListopt ) { FunctionBody } 22 | return nil if eql_lit?(ECMA262::ID_FUNCTION).nil? 23 | 24 | new_var_env = ECMA262::LexEnv.new(outer: var_env) 25 | 26 | if id=identifier(var_env) and 27 | eql_lit?(ECMA262::PUNC_LPARENTHESIS) and 28 | args = formal_parameter_list(new_var_env) and 29 | eql_lit?(ECMA262::PUNC_RPARENTHESIS) and 30 | eql_lit?(ECMA262::PUNC_LCURLYBRAC) and 31 | b=func_body(new_var_env) and eql_lit?(ECMA262::PUNC_RCURLYBRAC) 32 | f = ECMA262::StFunc.new(new_var_env, id, args, b, {:decl => true}) 33 | 34 | var_env.record.create_mutable_binding(id, nil) 35 | var_env.record.set_mutable_binding(id, f, nil) 36 | f 37 | else 38 | if b 39 | raise ParseError.new("No `}' at end of function", self) 40 | else 41 | raise ParseError.new("Bad function declaration", self) 42 | end 43 | end 44 | end 45 | 46 | # Tests next literal is FunctionExpression or not. 47 | # 48 | # If literal is FunctionExpression 49 | # return ECMA262::StFunc object and 50 | # forward lexical parser position. 51 | # Otherwise return nil and position is not changed. 52 | # 53 | # @see http://www.ecma-international.org/ecma-262 ECMA262 13 54 | # 55 | # @note 56 | # The function expression and declaration uses same class 57 | # for convenience. 58 | # 59 | def func_exp(var_env) 60 | # FunctionExpression : 61 | # function Identifieropt ( FormalParameterListopt ) { FunctionBody } 62 | return nil if eql_lit?(ECMA262::ID_FUNCTION).nil? 63 | @logger.debug "*** func_exp" 64 | 65 | id_opt = identifier(var_env) 66 | new_var_env = ECMA262::LexEnv.new(outer: var_env) 67 | 68 | if eql_lit?(ECMA262::PUNC_LPARENTHESIS) and 69 | args = formal_parameter_list(new_var_env) and 70 | eql_lit?(ECMA262::PUNC_RPARENTHESIS) and 71 | eql_lit?(ECMA262::PUNC_LCURLYBRAC) and 72 | b = func_body(new_var_env) and eql_lit?(ECMA262::PUNC_RCURLYBRAC) 73 | f = ECMA262::StFunc.new(new_var_env, id_opt, args, b) 74 | #new_var_env.func = f 75 | if id_opt 76 | var_env.record.create_mutable_binding(id_opt, nil) 77 | var_env.record.set_mutable_binding(id_opt, f, nil) 78 | end 79 | f 80 | else 81 | if b 82 | raise ParseError.new("No `}' at end of function", self) 83 | else 84 | raise ParseError.new("Bad function expression", self) 85 | end 86 | end 87 | end 88 | 89 | def formal_parameter_list(var_env) 90 | ret = [] 91 | unless peek_lit(nil).eql? ECMA262::PUNC_RPARENTHESIS 92 | while true 93 | if arg = identifier(var_env) 94 | ret.push(arg) 95 | else 96 | raise ParseError.new("unexpceted token", self) 97 | end 98 | if peek_lit(nil).eql? ECMA262::PUNC_RPARENTHESIS 99 | break 100 | elsif eql_lit? ECMA262::PUNC_COMMA 101 | ; 102 | else 103 | raise ParseError.new("unexpceted token", self) 104 | end 105 | end 106 | end 107 | ret.each do |argName| 108 | var_env.record.create_mutable_binding(argName, nil) 109 | var_env.record.set_mutable_binding(argName, :undefined, nil, _parameter_list: true) 110 | end 111 | ret 112 | end 113 | 114 | def func_body(var_env) 115 | source_elements(var_env) 116 | end 117 | 118 | private :func_body, :formal_parameter_list 119 | end 120 | end 121 | -------------------------------------------------------------------------------- /lib/minjs/lex/parser.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'minjs/ctype' 3 | require 'minjs/ecma262' 4 | 5 | module Minjs::Lex 6 | # ECMA262 Parser class 7 | # 8 | # This class parses ECMA262 script language's source text 9 | # and convers it to elements (ECMA262::Base). 10 | class Parser 11 | include Minjs 12 | include Ctype 13 | include Lex::Program 14 | include Lex::Statement 15 | include Lex::Expression 16 | include Lex::Function 17 | 18 | attr_reader :pos 19 | attr_reader :codes 20 | 21 | # @param source_text [String] input source text 22 | # @option options :logger [Logger] logger for debug 23 | def initialize(source_text = "", options = {}) 24 | source_text = source_text.gsub(/\r\n/, "\n") 25 | @codes = source_text.codepoints 26 | if !source_text.match(/\n\z/) 27 | @codes.push(10) 28 | end 29 | @pos = 0 30 | clear_cache 31 | @logger = options[:logger] 32 | 33 | @eval_nest = 0 34 | end 35 | 36 | # clear cache of ECMA262 elements 37 | def clear_cache 38 | @lit_cache = {} 39 | @lit_nextpos = {} 40 | end 41 | 42 | # Fetch next literal and forward position. 43 | # 44 | # @param hint [Symbol] hint of parsing. The hint must be one of the 45 | # :regexp, :div, nil 46 | # The hint parameter is used to determine next literal is division-mark or 47 | # regular expression. because ECMA262 says: 48 | # 49 | # There are no syntactic grammar contexts where both a leading division 50 | # or division-assignment, and a leading RegularExpressionLiteral are permitted. 51 | # This is not affected by semicolon insertion (see 7.9); in examples such as the following: 52 | # To determine `/' is regular expression or not 53 | # 54 | def next_input_element(hint) 55 | if ret = @lit_cache[@pos] 56 | @pos = @lit_nextpos[@pos] 57 | @head_pos = @pos 58 | return ret 59 | end 60 | pos0 = @pos 61 | # 62 | # skip white space here, because ECMA262(5.1.2) says: 63 | # 64 | # Simple white space and single-line comments are discarded and 65 | # do not appear in the stream of input elements for the 66 | # syntactic grammar. 67 | # 68 | while white_space or single_line_comment 69 | end 70 | 71 | ret = line_terminator || multi_line_comment || token 72 | if ret 73 | @lit_cache[pos0] = ret 74 | @lit_nextpos[pos0] = @pos 75 | @head_pos = @pos 76 | return ret 77 | end 78 | 79 | if @codes[@pos].nil? 80 | return nil 81 | end 82 | if hint.nil? 83 | if @codes[@pos] == 0x2f 84 | ECMA262::LIT_DIV_OR_REGEXP_LITERAL 85 | else 86 | nil 87 | end 88 | elsif hint == :div 89 | ret = div_punctuator 90 | if ret 91 | @lit_cache[pos0] = ret 92 | @lit_nextpos[pos0] = @pos 93 | end 94 | @head_pos = @pos 95 | return ret 96 | elsif hint == :regexp 97 | ret = regexp_literal 98 | if ret 99 | @lit_cache[pos0] = ret 100 | @lit_nextpos[pos0] = @pos 101 | end 102 | @head_pos = @pos 103 | return ret 104 | else 105 | if @codes[@pos] == 0x2f 106 | ECMA262::LIT_DIV_OR_REGEXP_LITERAL 107 | else 108 | nil 109 | end 110 | end 111 | end 112 | 113 | # Tests next literal is WhiteSpace or not. 114 | # 115 | # If literal is WhiteSpace 116 | # return ECMA262::WhiteSpace object and 117 | # forward lexical parser position. 118 | # Otherwise return nil and position is not changed. 119 | # 120 | # Even if next literal is sequence of two or more white spaces, 121 | # this method returns only one white space. 122 | # 123 | # @return [ECMA262::WhiteSpace] element 124 | # 125 | # @see http://www.ecma-international.org/ecma-262 ECMA262 7.2 126 | def white_space 127 | if white_space?(@codes[@pos]) 128 | begin 129 | @pos += 1 130 | end until !white_space?(@codes[@pos]) 131 | return ECMA262::WhiteSpace.get 132 | else 133 | nil 134 | end 135 | end 136 | 137 | # Tests next literal is LineTerminator or not. 138 | # 139 | # If literal is LineTerminator 140 | # return ECMA262::LineTerminator object and 141 | # forward lexical parser position. 142 | # Otherwise return nil and position is not changed. 143 | # 144 | # Even if next literal is sequence of two or more line terminators, 145 | # this method returns only one line terminator. 146 | # 147 | # @return [ECMA262::LineTerminator] element 148 | # 149 | # @see http://www.ecma-international.org/ecma-262 ECMA262 7.3 150 | def line_terminator 151 | if line_terminator?(@codes[@pos]) 152 | begin 153 | @pos += 1 154 | end until !line_terminator?(@codes[@pos]) 155 | return ECMA262::LineTerminator.get 156 | else 157 | nil 158 | end 159 | end 160 | 161 | # Tests next literal is Comment or not. 162 | # 163 | # If literal is Comment 164 | # return ECMA262::MultiLineComment or SingeLineComment object and 165 | # forward lexical parser position. 166 | # Otherwise return nil and position is not changed. 167 | # 168 | # @see http://www.ecma-international.org/ecma-262 ECMA262 7.4 169 | def comment 170 | multi_line_comment || single_line_comment 171 | end 172 | 173 | # Tests next literal is MultiLineComment or not. 174 | # 175 | # If literal is MultiLineComment 176 | # return ECMA262::MultiLineComment object and 177 | # forward lexical parser position. 178 | # Otherwise return nil and position is not changed. 179 | # 180 | # @see http://www.ecma-international.org/ecma-262 ECMA262 7.4 181 | def multi_line_comment 182 | # /* 183 | if @codes[@pos] == 0x2f and @codes[@pos + 1] == 0x2a 184 | @pos += 2 185 | pos0 = @pos 186 | # */ 187 | while (code = @codes[@pos] != 0x2a) or @codes[@pos + 1] != 0x2f 188 | raise ParseError.new("no `*/' at end of comment", self) if code.nil? 189 | @pos += 1 190 | end 191 | @pos +=2 192 | return ECMA262::MultiLineComment.new(@codes[pos0...(@pos-2)].pack("U*")) 193 | else 194 | nil 195 | end 196 | end 197 | 198 | # Tests next literal is SinleLineComment or not. 199 | # 200 | # If literal is SingleLineComment 201 | # return ECMA262::SingleLineComment object and 202 | # forward lexical parser position. 203 | # Otherwise return nil and position is not changed. 204 | # 205 | # @see http://www.ecma-international.org/ecma-262 ECMA262 7.4 206 | def single_line_comment 207 | # // 208 | if @codes[@pos] == 0x2f and @codes[@pos + 1] == 0x2f 209 | @pos += 2 210 | pos0 = @pos 211 | while (code = @codes[@pos]) and !line_terminator?(code) 212 | @pos += 1 213 | end 214 | return ECMA262::SingleLineComment.new(@codes[pos0...@pos].pack("U*")) 215 | else 216 | nil 217 | end 218 | end 219 | 220 | # Tests next literal is Token or not 221 | # 222 | # If literal is Token 223 | # return ECMA262::Base object and 224 | # forward lexical parser position. 225 | # Otherwise return nil and position is not changed. 226 | # 227 | # @see http://www.ecma-international.org/ecma-262 ECMA262 7.5 228 | def token 229 | identifier_name || numeric_literal || punctuator || string_literal 230 | end 231 | 232 | def unicode_escape? 233 | # @codes[@pos] == 0x5c 234 | if @codes[@pos+1] == 0x75 #u 235 | if hex_digit?(@codes[@pos+2]) and 236 | hex_digit?(@codes[@pos+3]) and 237 | hex_digit?(@codes[@pos+4]) and 238 | hex_digit?(@codes[@pos+5]) 239 | @codes[(@pos+2)..(@pos+5)].pack("U*").to_i(16) 240 | else 241 | raise ParseError.new("bad unicode escpae sequence", self) 242 | end 243 | else 244 | nil 245 | end 246 | end 247 | private :unicode_escape? 248 | 249 | # Tests next literal is IdentifierName or not 250 | # 251 | # If literal is IdentifierName 252 | # return ECMA262::IdentifierName object and 253 | # forward lexical parser position. 254 | # Otherwise return nil and position is not changed. 255 | # 256 | # @see http://www.ecma-international.org/ecma-262 ECMA262 7.6 257 | def identifier_name 258 | return nil if (code = @codes[@pos]).nil? 259 | 260 | pos0 = @pos 261 | chars = [] 262 | if code == 0x5c and ucode = unicode_escape? and identifier_start?(ucode) 263 | chars.push(ucode) 264 | @pos += 6 265 | elsif identifier_start?(code) 266 | chars.push(code) 267 | @pos += 1 268 | else 269 | return nil 270 | end 271 | 272 | while true 273 | code = @codes[@pos] 274 | if code == 0x5c and ucode = unicode_escape? and identifier_part?(ucode) 275 | chars.push(ucode) 276 | @pos += 6 277 | elsif identifier_part?(code) 278 | chars.push(code) 279 | @pos += 1 280 | else 281 | name = chars.pack("U*").to_sym 282 | return ECMA262::IdentifierName.get(name) 283 | end 284 | end 285 | end 286 | 287 | # Tests next literal is Punctuator or not 288 | # 289 | # If literal is Punctuator 290 | # return ECMA262::Punctuator object and 291 | # forward lexical parser position. 292 | # Otherwise return nil and position is not changed. 293 | # 294 | # @see http://www.ecma-international.org/ecma-262 ECMA262 7.7 295 | def punctuator 296 | code0 = @codes[@pos] 297 | code1 = @codes[@pos+1] 298 | code2 = @codes[@pos+2] 299 | code3 = @codes[@pos+3] 300 | if false 301 | elsif code0 == 0x28 # ( 302 | @pos += 1 # ( 303 | return ECMA262::PUNC_LPARENTHESIS 304 | elsif code0 == 0x29 # ) 305 | @pos += 1 # ) 306 | return ECMA262::PUNC_RPARENTHESIS 307 | elsif code0 == 0x7b # { 308 | @pos += 1 # { 309 | return ECMA262::PUNC_LCURLYBRAC 310 | elsif code0 == 0x7d # } 311 | @pos += 1 # } 312 | return ECMA262::PUNC_RCURLYBRAC 313 | elsif code0 == 0x3b # ; 314 | @pos += 1 # ; 315 | return ECMA262::PUNC_SEMICOLON 316 | elsif code0 == 0x3d # = 317 | if code1 == 0x3d and code2 == 0x3d # === 318 | @pos += 3 319 | return ECMA262::PUNC_SEQ 320 | end 321 | if code1 == 0x3d # == 322 | @pos += 2 323 | return ECMA262::PUNC_EQ 324 | end 325 | @pos += 1 # = 326 | return ECMA262::PUNC_ASSIGN 327 | elsif code0 == 0x21 # ! 328 | if code1 == 0x3d and code2 == 0x3d # !== 329 | @pos += 3 330 | return ECMA262::PUNC_SNEQ 331 | end 332 | if code1 == 0x3d # != 333 | @pos += 2 334 | return ECMA262::PUNC_NEQ 335 | end 336 | @pos += 1 # ! 337 | return ECMA262::PUNC_LNOT 338 | elsif code0 == 0x25 # % 339 | if code1 == 0x3d # %= 340 | @pos += 2 341 | return ECMA262::PUNC_MODASSIGN 342 | end 343 | @pos += 1 # % 344 | return ECMA262::PUNC_MOD 345 | elsif code0 == 0x26 # & 346 | if code1 == 0x3d # &= 347 | @pos += 2 348 | return ECMA262::PUNC_ANDASSIGN 349 | end 350 | if code1 == 0x26 # && 351 | @pos += 2 352 | return ECMA262::PUNC_LAND 353 | end 354 | @pos += 1 # & 355 | return ECMA262::PUNC_AND 356 | elsif code0 == 0x2a # * 357 | if code1 == 0x3d # *= 358 | @pos += 2 359 | return ECMA262::PUNC_MULASSIGN 360 | end 361 | @pos += 1 # * 362 | return ECMA262::PUNC_MUL 363 | elsif code0 == 0x2b # + 364 | if code1 == 0x3d # += 365 | @pos += 2 366 | return ECMA262::PUNC_ADDASSIGN 367 | end 368 | if code1 == 0x2b # ++ 369 | @pos += 2 370 | return ECMA262::PUNC_INC 371 | end 372 | @pos += 1 # + 373 | return ECMA262::PUNC_ADD 374 | elsif code0 == 0x2c # , 375 | @pos += 1 # , 376 | return ECMA262::PUNC_COMMA 377 | elsif code0 == 0x2d # - 378 | if code1 == 0x3d # -= 379 | @pos += 2 380 | return ECMA262::PUNC_SUBASSIGN 381 | end 382 | if code1 == 0x2d # -- 383 | @pos += 2 384 | return ECMA262::PUNC_DEC 385 | end 386 | @pos += 1 # - 387 | return ECMA262::PUNC_SUB 388 | elsif code0 == 0x2e # . 389 | @pos += 1 # . 390 | return ECMA262::PUNC_PERIOD 391 | elsif code0 == 0x3a # : 392 | @pos += 1 # : 393 | return ECMA262::PUNC_COLON 394 | elsif code0 == 0x3c # < 395 | if code1 == 0x3d # <= 396 | @pos += 2 397 | return ECMA262::PUNC_LTEQ 398 | end 399 | if code1 == 0x3c and code2 == 0x3d # <<= 400 | @pos += 3 401 | return ECMA262::PUNC_LSHIFTASSIGN 402 | end 403 | if code1 == 0x3c # << 404 | @pos += 2 405 | return ECMA262::PUNC_LSHIFT 406 | end 407 | @pos += 1 # < 408 | return ECMA262::PUNC_LT 409 | elsif code0 == 0x3e # > 410 | if code1 == 0x3e and code2 == 0x3e and code3 == 0x3d # >>>= 411 | @pos += 4 412 | return ECMA262::PUNC_URSHIFTASSIGN 413 | end 414 | if code1 == 0x3e and code2 == 0x3e # >>> 415 | @pos += 3 416 | return ECMA262::PUNC_URSHIFT 417 | end 418 | if code1 == 0x3e and code2 == 0x3d # >>= 419 | @pos += 3 420 | return ECMA262::PUNC_RSHIFTASSIGN 421 | end 422 | if code1 == 0x3e # >> 423 | @pos += 2 424 | return ECMA262::PUNC_RSHIFT 425 | end 426 | if code1 == 0x3d # >= 427 | @pos += 2 428 | return ECMA262::PUNC_GTEQ 429 | end 430 | @pos += 1 # > 431 | return ECMA262::PUNC_GT 432 | elsif code0 == 0x3f # ? 433 | @pos += 1 # ? 434 | return ECMA262::PUNC_CONDIF 435 | elsif code0 == 0x5b # [ 436 | @pos += 1 # [ 437 | return ECMA262::PUNC_LSQBRAC 438 | elsif code0 == 0x5d # ] 439 | @pos += 1 # ] 440 | return ECMA262::PUNC_RSQBRAC 441 | elsif code0 == 0x5e # ^ 442 | if code1 == 0x3d # ^= 443 | @pos += 2 444 | return ECMA262::PUNC_XORASSIGN 445 | end 446 | @pos += 1 # ^ 447 | return ECMA262::PUNC_XOR 448 | elsif code0 == 0x7c # | 449 | if code1 == 0x7c # || 450 | @pos += 2 451 | return ECMA262::PUNC_LOR 452 | end 453 | if code1 == 0x3d # |= 454 | @pos += 2 455 | return ECMA262::PUNC_ORASSIGN 456 | end 457 | @pos += 1 # | 458 | return ECMA262::PUNC_OR 459 | elsif code0 == 0x7e # ~ 460 | @pos += 1 # ~ 461 | return ECMA262::PUNC_NOT 462 | end 463 | nil 464 | end 465 | 466 | # Tests next literal is DivPunctuator or not. 467 | # 468 | # If literal is DivPunctuator 469 | # return ECMA262::PUNC_DIV or ECMA262::PUNC_DIVASSIGN object and 470 | # forward lexical parser position. 471 | # Otherwise return nil and position is not changed. 472 | # 473 | # @see http://www.ecma-international.org/ecma-262 ECMA262 7.7 474 | def div_punctuator 475 | if @codes[@pos] == 0x2f 476 | if @codes[@pos+1] == 0x3d 477 | @pos += 2 478 | return ECMA262::PUNC_DIVASSIGN 479 | else 480 | @pos += 1 481 | return ECMA262::PUNC_DIV 482 | end 483 | end 484 | nil 485 | end 486 | 487 | # Tests next literal is RegExp or not. 488 | # 489 | # If literal is RegExp 490 | # return ECMA262::ECMA262RegExp object and 491 | # forward lexical parser position. 492 | # Otherwise return nil and position is not changed. 493 | # 494 | # @return [ECMA262::RegExp] 495 | # @see http://www.ecma-international.org/ecma-262 ECMA262 7.8.5 496 | def regexp_literal 497 | # RegularExpressionLiteral:: 498 | # / RegularExpressionBody / RegularExpressionFlags 499 | pos0 = @pos 500 | return nil unless @codes[@pos] == 0x2f 501 | 502 | body = regexp_body 503 | flags = regexp_flags 504 | return ECMA262::ECMA262RegExp.new(body, flags) 505 | end 506 | 507 | def regexp_body 508 | if @codes[@pos] == 0x2a 509 | raise ParseError.new("first character of regular expression is `*'", self) 510 | end 511 | pos0 = @pos 512 | @pos += 1 513 | while !(@codes[@pos] == 0x2f) 514 | if @codes[@pos].nil? 515 | raise ParseError.new("no `/' end of regular expression", self) 516 | end 517 | if line_terminator?(@codes[@pos]) 518 | raise ParseError.new("regular expression has line terminator in body", self) 519 | end 520 | if @codes[@pos] == 0x5c # \ 521 | @pos += 1 522 | if line_terminator?(@codes[@pos]) 523 | raise ParseError.new("regular expression has line terminator in body", self) 524 | end 525 | @pos += 1 526 | elsif @codes[@pos] == 0x5b # [ 527 | regexp_class 528 | else 529 | @pos += 1 530 | end 531 | end 532 | @pos += 1 533 | return @codes[(pos0+1)...(@pos-1)].pack("U*") 534 | end 535 | 536 | def regexp_class 537 | if @codes[@pos] != 0x5b 538 | raise ParseError.new('bad regular expression', self) 539 | end 540 | @pos += 1 541 | while !(@codes[@pos] == 0x5d) 542 | if @codes[@pos].nil? 543 | raise ParseError.new("no `]' end of regular expression class", self) 544 | end 545 | if line_terminator?(@codes[@pos]) 546 | raise ParseError.new("regular expression has line terminator in body", self) 547 | end 548 | if @codes[@pos] == 0x5c # \ 549 | @pos += 1 550 | if line_terminator?(@codes[@pos]) 551 | raise ParseError.new("regular expression has line terminator in body", self) 552 | end 553 | @pos += 1 554 | else 555 | @pos += 1 556 | end 557 | end 558 | @pos += 1 559 | end 560 | 561 | def regexp_flags 562 | pos0 = @pos 563 | while(identifier_part?(@codes[@pos])) 564 | @pos += 1 565 | end 566 | return @codes[pos0...@pos].pack("U*") 567 | end 568 | 569 | private :regexp_flags, :regexp_class, :regexp_body 570 | 571 | # Tests next literal is NumericLiteral or not. 572 | # 573 | # If literal is NumericLiteral 574 | # return ECMA262::ECMA262Numeric object and 575 | # forward lexical parser position. 576 | # Otherwise return nil and position is not changed. 577 | # 578 | # @return [ECMA262::ECMA262Numeric] 579 | # @see http://www.ecma-international.org/ecma-262 ECMA262 7.8.3 580 | def numeric_literal 581 | hex_integer_literal || octal_integer_literal || decimal_literal 582 | end 583 | 584 | #7.8.3 585 | # 586 | # HexIntegerLiteral :: 587 | # 0x HexDigit 588 | # 0X HexDigit 589 | # HexIntegerLiteral HexDigit 590 | # 591 | def hex_integer_literal 592 | code = @codes[@pos] 593 | if code.nil? 594 | return nil 595 | #0x / 0X 596 | elsif code == 0x30 and (@codes[@pos+1] == 0x78 || @codes[@pos+1] == 0x58) 597 | @pos += 2 598 | pos0 = @pos 599 | while code = @codes[@pos] and hex_digit?(code) 600 | @pos += 1; 601 | end 602 | if identifier_start?(code) 603 | raise ParseError.new("The source character immediately following a NumericLiteral must not be an IdentifierStart or DecimalDigit", self) 604 | else 605 | return ECMA262::ECMA262Numeric.new(@codes[pos0...@pos].pack("U*").to_i(16)) 606 | end 607 | else 608 | nil 609 | end 610 | end 611 | 612 | #B.1.1 613 | # OctalIntegerLiteral :: 614 | # 0 OctalDigit 615 | # OctalIntegerLiteral OctalDigit 616 | # 617 | def octal_integer_literal 618 | code = @codes[@pos] 619 | if code.nil? 620 | return nil 621 | elsif code == 0x30 and (code1 = @codes[@pos + 1]) >= 0x30 and code1 <= 0x37 622 | @pos += 1 623 | pos0 = @pos 624 | while code = @codes[@pos] and code >= 0x30 and code <= 0x37 625 | @pos += 1 626 | end 627 | if identifier_start?(code) 628 | raise ParseError.new("The source character immediately following a NumericLiteral must not be an IdentifierStart or DecimalDigit", self) 629 | else 630 | return ECMA262::ECMA262Numeric.new(@codes[pos0...@pos].pack("U*").to_i(8)) 631 | end 632 | else 633 | nil 634 | end 635 | end 636 | 637 | # 7.8.3 638 | # 639 | # DecimalLiteral :: 640 | # DecimalIntegerLiteral . DecimalDigitsopt ExponentPartopt 641 | # . DecimalDigits ExponentPartopt 642 | # DecimalIntegerLiteral ExponentPartopt 643 | # 644 | def decimal_literal 645 | pos0 = @pos 646 | code = @codes[@pos] 647 | 648 | if code.nil? 649 | return nil 650 | elsif code == 0x2e #. 651 | @pos += 1 652 | f = decimal_digits 653 | if f.nil? #=> this period is punctuator 654 | @pos = pos0 + 1 655 | return ECMA262::PUNC_PERIOD 656 | end 657 | if (code = @codes[@pos]) == 0x65 || code == 0x45 658 | @pos += 1 659 | e = exponent_part 660 | end 661 | if identifier_start?(@codes[@pos]) 662 | raise ParseError.new("The source character immediately following a NumericLiteral must not be an IdentifierStart or DecimalDigit", self) 663 | end 664 | 665 | return ECMA262::ECMA262Numeric.new('0', f, e) 666 | elsif code == 0x30 # zero 667 | i = "0" 668 | @pos += 1 669 | if @codes[@pos] == 0x2e #. 670 | @pos += 1 671 | f = decimal_digits 672 | if (code = @codes[@pos]) == 0x65 || code == 0x45 #e or E 673 | @pos += 1 674 | e = exponent_part 675 | end 676 | elsif (code = @codes[@pos]) == 0x65 || code == 0x45 #e or E 677 | @pos += 1 678 | e = exponent_part 679 | end 680 | if identifier_start?(@codes[@pos]) 681 | raise ParseError.new("The source character immediately following a NumericLiteral must not be an IdentifierStart or DecimalDigit", self) 682 | end 683 | 684 | return ECMA262::ECMA262Numeric.new(i, f, e) 685 | elsif code >= 0x31 and code <= 0x39 686 | i = decimal_digits 687 | if @codes[@pos] == 0x2e #. 688 | @pos += 1 689 | f = decimal_digits 690 | if (code = @codes[@pos]) == 0x65 || code == 0x45 #e or E 691 | @pos += 1 692 | e = exponent_part 693 | end 694 | elsif (code = @codes[@pos]) == 0x65 || code == 0x45 #e or E 695 | @pos += 1 696 | e = exponent_part 697 | end 698 | if identifier_start?(@codes[@pos]) 699 | raise ParseError.new("The source character immediately following a NumericLiteral must not be an IdentifierStart or DecimalDigit", self) 700 | end 701 | 702 | return ECMA262::ECMA262Numeric.new(i, f, e) 703 | end 704 | 705 | nil 706 | end 707 | 708 | # 7.8.3 709 | # 710 | # ExponentPart :: 711 | # ExponentIndicator SignedInteger 712 | # 713 | def exponent_part 714 | if (code = @codes[@pos]) == 0x2b 715 | @pos += 1 716 | elsif code == 0x2d 717 | @pos += 1 718 | neg = true 719 | end 720 | d = decimal_digits 721 | raise ParseError.new("unexpecting token", self) if d.nil? 722 | if neg 723 | e = "-#{d}" 724 | else 725 | e = d 726 | end 727 | e 728 | end 729 | 730 | #7.8.3 731 | # 732 | # DecimalDigit :: one of 733 | # 0 1 2 3 4 5 6 7 8 9 734 | # 735 | def decimal_digits 736 | pos0 = @pos 737 | if (code = @codes[@pos]) >= 0x30 and code <= 0x39 738 | @pos += 1 739 | while code = @codes[@pos] and code >= 0x30 and code <= 0x39 740 | @pos += 1 741 | end 742 | return @codes[pos0...@pos].pack("U*") 743 | else 744 | nil 745 | end 746 | end 747 | private :hex_integer_literal, :octal_integer_literal, :decimal_literal, 748 | :exponent_part, :decimal_digits 749 | 750 | # Tests next literal is StringLiteral or not. 751 | # 752 | # If literal is StringLiteral 753 | # return ECMA262::ECMA262String object and 754 | # forward lexical parser position. 755 | # Otherwise return nil and position is not changed. 756 | # 757 | # @return [ECMA262::ECMA262String] 758 | # @see http://www.ecma-international.org/ecma-262 ECMA262 7.8.4 759 | # 760 | def string_literal 761 | # StringLiteral :: 762 | # " DoubleStringCharactersopt " 763 | # ' SingleStringCharactersopt ' 764 | # 765 | # DoubleStringCharacters :: 766 | # DoubleStringCharacter DoubleStringCharactersopt 767 | # 768 | # SingleStringCharacters :: 769 | # SingleStringCharacter SingleStringCharactersopt 770 | # 771 | # DoubleStringCharacter :: 772 | # SourceCharacter but not one of " or \ or LineTerminator 773 | # \ EscapeSequence 774 | # LineContinuation 775 | # 776 | # SingleStringCharacter :: 777 | # SourceCharacter but not one of ' or \ or LineTerminator 778 | # \ EscapeSequence 779 | # LineContinuation 780 | # 781 | if (code = @codes[@pos]) == 0x27 #' 782 | term = 0x27 783 | elsif code == 0x22 #" 784 | term = 0x22 785 | else 786 | return nil 787 | end 788 | @pos += 1 789 | pos0 = @pos 790 | 791 | str = [] 792 | while (code = @codes[@pos]) 793 | if code.nil? 794 | raise ParseError.new("no `#{term}' at end of string", self) 795 | elsif line_terminator?(code) 796 | raise ParseError.new("string has line terminator in body", self) 797 | elsif code == 0x5c #\ 798 | @pos += 1 799 | str.push(escape_sequence) 800 | elsif code == term 801 | @pos += 1 802 | return ECMA262::ECMA262String.new(str.compact.pack("U*")) 803 | else 804 | @pos += 1 805 | str.push(code) 806 | end 807 | end 808 | nil 809 | end 810 | 811 | # 7.8.4 812 | # B.1.2 813 | # 814 | # EscapeSequence :: 815 | # CharacterEscapeSequence 816 | # 0 [lookahead ∉ DecimalDigit] 817 | # HexEscapeSequence 818 | # UnicodeEscapeSequence 819 | # OctalEscapeSequence 820 | 821 | def escape_sequence 822 | case (code = @codes[@pos]) 823 | # when 0x30 824 | # @pos += 1 825 | # 0 826 | when 0x27 #' 827 | @pos += 1 828 | 0x27 829 | when 0x22 #" 830 | @pos += 1 831 | 0x22 832 | when 0x5c #\ 833 | @pos += 1 834 | 0x5c 835 | when 0x62 #b 836 | @pos += 1 837 | 0x08 838 | when 0x74 #t 839 | @pos += 1 840 | 0x09 841 | when 0x6e #n 842 | @pos += 1 843 | 0x0a 844 | when 0x76 #v 845 | @pos += 1 846 | 0x0b 847 | when 0x66 #f 848 | @pos += 1 849 | 0x0c 850 | when 0x72 #r 851 | @pos += 1 852 | 0x0d 853 | when 0x78 #x 854 | #check 855 | t = @codes[(@pos+1)..(@pos+2)].pack("U*").to_i(16) 856 | @pos += 3 857 | t 858 | when 0x75 #u 859 | #check 860 | t = @codes[(@pos+1)..(@pos+4)].pack("U*").to_i(16) 861 | @pos += 5 862 | t 863 | else 864 | # line continuation 865 | if line_terminator?(code) 866 | @pos += 1 867 | nil 868 | # Annex B.1.2 869 | # 870 | # OctalEscapeSequence :: 871 | # OctalDigit [lookahead ∉ DecimalDigit] 872 | # ZeroToThree OctalDigit [lookahead ∉ DecimalDigit] 873 | # FourToSeven OctalDigit 874 | # ZeroToThree OctalDigit OctalDigit 875 | # 876 | # Note: 877 | # 878 | # A string such as the following is invalid 879 | # as a octal escape sequence. 880 | # 881 | # \19 or \319 882 | # 883 | # However, it is not to an error in most implementations. 884 | # Therefore, minjs also intepret it such way. 885 | # 886 | elsif octal_digit?(code) 887 | code1 = @codes[@pos+1] 888 | code2 = @codes[@pos+2] 889 | if code >= 0x30 and code <= 0x33 890 | if octal_digit?(code1) 891 | if octal_digit?(code2) 892 | @pos += 3 893 | (code - 0x30) * 64 + (code1 - 0x30) * 8 + (code2 - 0x30) 894 | else 895 | @pos += 2 896 | (code - 0x30) * 8 + (code1 - 0x30) 897 | end 898 | else 899 | @pos += 1 900 | code - 0x30 901 | end 902 | else #if code >= 0x34 and code <= 0x37 903 | if octal_digit?(code1) 904 | @pos += 2 905 | (code - 0x30) * 8 + (code1 - 0x30) 906 | else 907 | @pos += 1 908 | code - 0x30 909 | end 910 | end 911 | else 912 | @pos += 1 913 | code 914 | end 915 | end 916 | end 917 | private :escape_sequence 918 | 919 | # Returns true if posision is at end of file 920 | def eof? 921 | peek_lit(nil).nil? 922 | end 923 | 924 | # 925 | # check next literal is strictly equal to _l_ or not. 926 | # white spaces and line terminators are skipped and ignored. 927 | # 928 | # if next literal is not _l_, position is not forwarded 929 | # if next literal is _l_, position is forwarded 930 | # 931 | def eql_lit?(l, hint = nil) 932 | lit = peek_lit(hint) 933 | if lit.eql? l 934 | fwd_after_peek 935 | lit 936 | else 937 | nil 938 | end 939 | end 940 | 941 | # 942 | # check next literal is strictly equal to _l_ or not. 943 | # white spaces are skipped and ignored. 944 | # line terminators are not ignored. 945 | # 946 | # if next literal is not _l_, position is not forwarded 947 | # if next literal is _l_, position is forwarded 948 | # 949 | def eql_lit_nolt?(l, hint = nil) 950 | lit = peek_lit_nolt(hint) 951 | if lit.eql? l 952 | fwd_after_peek 953 | lit 954 | else 955 | nil 956 | end 957 | end 958 | 959 | # 960 | # check next literal is equal to _l_ or not. 961 | # white spaces and line terminators are skipped and ignored. 962 | # 963 | # if next literal is not _l_, position is not forwarded 964 | # if next literal is _l_, position is forwarded 965 | # 966 | def match_lit?(l, hint = nil) 967 | lit = peek_lit(hint) 968 | if lit == l 969 | fwd_after_peek 970 | lit 971 | else 972 | nil 973 | end 974 | end 975 | 976 | # 977 | # check next literal is equal to _l_ or not. 978 | # white spaces are skipped and ignored. 979 | # line terminators are not ignored. 980 | # 981 | # if next literal is not _l_, position is not forwarded 982 | # if next literal is _l_, position is forwarded 983 | # 984 | def match_lit_nolt?(l, hint = nil) 985 | lit = peek_lit_nolt(hint) 986 | if lit == l 987 | fwd_after_peek 988 | lit 989 | else 990 | nil 991 | end 992 | end 993 | 994 | # 995 | # fetch next literal. 996 | # position is not forwarded. 997 | # white spaces and line terminators are skipped and ignored. 998 | # 999 | def peek_lit(hint) 1000 | pos0 = @pos 1001 | while lit = next_input_element(hint) and (lit.ws? or lit.lt?) 1002 | end 1003 | @pos = pos0 1004 | lit 1005 | end 1006 | 1007 | # fetch next literal. 1008 | # 1009 | # position is not forwarded. 1010 | # white spaces are skipped and ignored. 1011 | # line terminators are not ignored. 1012 | # 1013 | def peek_lit_nolt(hint) 1014 | pos0 = @pos 1015 | while lit = next_input_element(hint) and lit.ws? 1016 | end 1017 | @pos = pos0 1018 | lit 1019 | end 1020 | 1021 | # Forwards position after calling peek_lit. 1022 | # 1023 | # This method quickly forward position after calling peek_lit. 1024 | def fwd_after_peek 1025 | @pos = @head_pos 1026 | end 1027 | 1028 | # 1029 | # fetch next literal. 1030 | # position is forwarded. 1031 | # white spaces and line terminators are skipped and ignored. 1032 | # 1033 | def fwd_lit(hint) 1034 | while lit = next_input_element(hint) and (lit.ws? or lit.lt?) 1035 | end 1036 | lit 1037 | end 1038 | 1039 | # 1040 | # fetch next literal. 1041 | # position is forwarded. 1042 | # white spaces are skipped and ignored. 1043 | # line terminators are not ignored. 1044 | # 1045 | def fwd_lit_nolt(hint) 1046 | while lit = next_input_element(hint) and lit.ws? 1047 | end 1048 | lit 1049 | end 1050 | 1051 | # 1052 | # break => position is rewind, then break with 1053 | # return => position is rewind, then return 1054 | # next => position is not rewind, then break with 1055 | # 1056 | def eval_lit(&block) 1057 | begin 1058 | saved_pos = @pos 1059 | @eval_nest += 1 1060 | ret = yield 1061 | ensure 1062 | @eval_nest -= 1 1063 | if ret.nil? 1064 | @pos = saved_pos 1065 | nil 1066 | else 1067 | if @eval_nest == 0 1068 | #STDERR.puts "clear_cache [#{saved_pos}..#{@pos}]" 1069 | clear_cache 1070 | end 1071 | end 1072 | end 1073 | end 1074 | 1075 | # 1076 | # position to [row, col] 1077 | # 1078 | def row_col(pos) 1079 | _pos = 0 1080 | row = 0 1081 | col = 1 1082 | @codes.each do |code| 1083 | break if _pos >= pos 1084 | if line_terminator?(code) 1085 | row += 1 1086 | col = 0 1087 | else 1088 | col += 1 1089 | end 1090 | _pos += 1 1091 | end 1092 | return [row+1, col+1] 1093 | end 1094 | 1095 | # 1096 | # position to line 1097 | # 1098 | def line(pos) 1099 | pos0 = pos1 = pos 1100 | while true 1101 | pos0 -= 1 1102 | break if line_terminator?(@codes[pos0]) 1103 | end 1104 | pos0 += 1 1105 | 1106 | while true 1107 | break if line_terminator?(@codes[pos1]) 1108 | pos1 += 1 1109 | end 1110 | 1111 | @codes[pos0..pos1].pack("U*") 1112 | end 1113 | 1114 | # Returns string of input data around _pos_ 1115 | # 1116 | # @param pos position 1117 | # @param row row 1118 | # @param col column 1119 | # @return [String] string 1120 | # 1121 | def debug_str(pos = nil, row = 0, col = 0) 1122 | if pos.nil? 1123 | pos = @head_pos or @pos 1124 | end 1125 | 1126 | t = '' 1127 | if col >= 80 1128 | t << @codes[(pos-80)..(pos+80)].pack("U*") 1129 | col = 81 1130 | else 1131 | t << line(pos) 1132 | end 1133 | 1134 | if col and col >= 1 1135 | col = col - 1; 1136 | end 1137 | t << "\n" 1138 | t << (' ' * col) + "^" 1139 | t 1140 | end 1141 | end 1142 | end 1143 | -------------------------------------------------------------------------------- /lib/minjs/lex/program.rb: -------------------------------------------------------------------------------- 1 | module Minjs::Lex 2 | # 3 | # 14 Program 4 | # 5 | module Program 6 | include Minjs 7 | 8 | # Tests next literals sequence is Program or not. 9 | # 10 | # If sequence is Program 11 | # return ECMA262::Prog object and 12 | # forward lexical parser position. 13 | # Otherwise return nil and position is not changed. 14 | # 15 | # @return [ECMA262::Prog] element 16 | # 17 | # @see http://www.ecma-international.org/ecma-262 ECMA262 14 18 | def program(var_env) 19 | prog = source_elements(var_env) 20 | if eof? 21 | return prog 22 | else 23 | raise ParseError.new("unexpceted token", self) 24 | end 25 | end 26 | 27 | # Tests next literals sequence is SourceElements or not. 28 | # 29 | # If sequence is SourceElements 30 | # return ECMA262::SourceElements object and 31 | # forward lexical parser position. 32 | # Otherwise return nil and position is not changed. 33 | # 34 | # @return [ECMA262::SourceElements] element 35 | # 36 | # @see http://www.ecma-international.org/ecma-262 ECMA262 14 37 | def source_elements(var_env) 38 | prog = [] 39 | while t = source_element(var_env) 40 | prog.push(t) 41 | end 42 | ECMA262::Prog.new(var_env, ECMA262::SourceElements.new(prog)) 43 | end 44 | 45 | def source_element(var_env) 46 | #eval_lit{ 47 | statement(var_env) 48 | #} or eval_lit{ => statement 49 | # func_declaration(var_env) 50 | #} 51 | end 52 | 53 | private :source_element 54 | end 55 | end 56 | 57 | -------------------------------------------------------------------------------- /lib/minjs/lex/statement.rb: -------------------------------------------------------------------------------- 1 | module Minjs::Lex 2 | # 3 | # 12 4 | # 5 | module Statement 6 | include Minjs 7 | # Tests next literal is ';' or '}' or LT 8 | def semicolon(var_env) 9 | a = peek_lit_nolt(nil) 10 | # ; ? 11 | if a == ECMA262::PUNC_SEMICOLON 12 | fwd_after_peek 13 | a 14 | # } ? 15 | elsif a == ECMA262::PUNC_RCURLYBRAC 16 | a 17 | # line feed? 18 | elsif a == ECMA262::LIT_LINE_TERMINATOR 19 | fwd_after_peek 20 | a 21 | # end of program 22 | elsif a.nil? 23 | fwd_after_peek 24 | ECMA262::LIT_LINE_TERMINATOR 25 | # line terminator? 26 | elsif a.lt? 27 | fwd_after_peek 28 | a 29 | else 30 | nil 31 | end 32 | end 33 | 34 | # Tests next literals sequence is Statement or not. 35 | def statement(var_env) 36 | ( 37 | block(var_env) or #12.1 38 | var_statement(var_env) or #12.2 39 | if_statement(var_env) or #12.5 40 | iteration_statement(var_env) or #12.6 41 | continue_statement(var_env) or #12.7 42 | break_statement(var_env) or #12.8 43 | return_statement(var_env) or #12.9 44 | with_statement(var_env) or #12.10 45 | switch_statement(var_env) or #12.11 46 | labelled_statement(var_env) or #12.12 47 | throw_statement(var_env) or #12.13 48 | try_statement(var_env) or #12.14 49 | debugger_statement(var_env) or #12.15 50 | func_declaration(var_env) or #13 => func.rb 51 | exp_statement(var_env) or #12.4 52 | empty_statement(var_env) #12.3 53 | ) 54 | end 55 | # Tests next literals sequence is Block or not. 56 | # 57 | # @see http://www.ecma-international.org/ecma-262 ECMA262 12.1 58 | def block(var_env) 59 | pos0 = pos 60 | return nil unless eql_lit?(ECMA262::PUNC_LCURLYBRAC) 61 | if eql_lit?(ECMA262::PUNC_RCURLYBRAC) 62 | return ECMA262::StBlock.new(ECMA262::StatementList.new([])) 63 | end 64 | 65 | if s = statement_list(var_env) and eql_lit?(ECMA262::PUNC_RCURLYBRAC) 66 | ECMA262::StBlock.new(s) 67 | else 68 | raise ParseError.new('no "}" end of block', lex) 69 | end 70 | end 71 | 72 | def statement_list(var_env) 73 | t = [] 74 | while !eof? and s = statement(var_env) 75 | t.push(s) 76 | end 77 | ECMA262::StatementList.new(t) 78 | end 79 | private :statement_list 80 | 81 | # Tests next literals sequence is VariableStatement or not. 82 | # 83 | # @see http://www.ecma-international.org/ecma-262 ECMA262 12.2 84 | def var_statement(var_env) 85 | return nil unless eql_lit?(ECMA262::ID_VAR) 86 | 87 | if vl = var_decl_list(var_env, {}) and semicolon(var_env) 88 | #10.5 89 | vl.each do |v| 90 | dn = v[0] 91 | var_env.record.create_mutable_binding(dn, nil) 92 | var_env.record.set_mutable_binding(dn, :undefined, nil) 93 | end 94 | ECMA262::StVar.new(var_env, vl) 95 | else 96 | raise Minjs::ParseError.new("unexpected token", lex) 97 | end 98 | end 99 | # 12.2 100 | # 101 | # VariableDeclarationList : 102 | # VariableDeclaration 103 | # VariableDeclarationList , VariableDeclaration 104 | # 105 | def var_decl_list(var_env, options) 106 | list = [] 107 | list.push(var_decl(var_env, options)) 108 | 109 | while eql_lit?(ECMA262::PUNC_COMMA) and b = var_decl(var_env, options) 110 | list.push(b) 111 | end 112 | list 113 | end 114 | 115 | # 12.2 116 | # 117 | # VariableDeclaration : 118 | # Identifier Initialiser[opt] 119 | # 120 | # return tuple of [name, initialiser] 121 | # 122 | def var_decl(var_env, options) 123 | a = identifier(var_env) 124 | if !a 125 | raise ParseError.new("bad identifier", lex); 126 | else 127 | b = initialiser(var_env, options) 128 | [a, b] 129 | end 130 | end 131 | 132 | # 12.2 133 | # 134 | # Initialiser : 135 | # = AssignmentExpression 136 | # 137 | def initialiser(var_env, options) 138 | if eql_lit?(ECMA262::PUNC_ASSIGN) 139 | if a = assignment_exp(var_env, options) 140 | return a 141 | else 142 | raise ParseError.new("unexpceted token", self); 143 | end 144 | end 145 | nil 146 | end 147 | private :var_decl_list, :var_decl, :initialiser 148 | 149 | # Tests next literals sequence is EmptyStatement or not. 150 | # 151 | # @see http://www.ecma-international.org/ecma-262 ECMA262 12.3 152 | def empty_statement(var_env) 153 | a = peek_lit(nil) 154 | if a == ECMA262::PUNC_SEMICOLON 155 | fwd_after_peek 156 | ECMA262::StEmpty.new 157 | else 158 | nil 159 | end 160 | end 161 | # Tests next literals sequence is ExpressionStatement or not. 162 | # 163 | # @see http://www.ecma-international.org/ecma-262 ECMA262 12.4 164 | def exp_statement(var_env) 165 | if (a = peek_lit(nil)).eql? ECMA262::PUNC_LCURLYBRAC 166 | return block(var_env) 167 | end 168 | if a.eql? ECMA262::ID_FUNCTION 169 | return func_declaration(var_env) 170 | end 171 | 172 | 173 | if a = exp(var_env, {}) 174 | if semicolon(var_env) 175 | ECMA262::StExp.new(a) 176 | # There is a possibility of labelled statemet if 177 | # exp_statement call before labelled_statement 178 | else 179 | raise ParseError.new("no semicolon at end of expression statement", self) 180 | end 181 | else 182 | nil 183 | end 184 | end 185 | # Tests next literals sequence is IfStatement or not. 186 | # 187 | # @see http://www.ecma-international.org/ecma-262 ECMA262 12.5 188 | def if_statement(var_env) 189 | return nil unless eql_lit?(ECMA262::ID_IF) 190 | unless(eql_lit?(ECMA262::PUNC_LPARENTHESIS) and cond=exp(var_env, {}) and 191 | eql_lit?(ECMA262::PUNC_RPARENTHESIS) and s = statement(var_env)) 192 | raise ParseError.new("unexpected token", self) 193 | end 194 | if(eql_lit?(ECMA262::ID_ELSE) and e = statement(var_env)) 195 | ECMA262::StIf.new(cond, s, e) 196 | else 197 | ECMA262::StIf.new(cond, s, nil) 198 | end 199 | end 200 | # Tests next literals sequence is IterationStatement or not. 201 | # 202 | # @see http://www.ecma-international.org/ecma-262 ECMA262 12.6 203 | def iteration_statement(var_env) 204 | for_statement(var_env) or while_statement(var_env) or do_while_statement(var_env) 205 | end 206 | 207 | def while_statement(var_env) 208 | return nil unless eql_lit?(ECMA262::ID_WHILE) 209 | if eql_lit?(ECMA262::PUNC_LPARENTHESIS) and e=exp(var_env, {}) and eql_lit?(ECMA262::PUNC_RPARENTHESIS) and s=statement(var_env) 210 | ECMA262::StWhile.new(e, s) 211 | else 212 | raise ParseError.new("unexpected token", self) 213 | end 214 | end 215 | 216 | def do_while_statement(var_env) 217 | return nil unless eql_lit?(ECMA262::ID_DO) 218 | if s=statement(var_env) and eql_lit?(ECMA262::ID_WHILE) and eql_lit?(ECMA262::PUNC_LPARENTHESIS) and e=exp(var_env, {}) and eql_lit?(ECMA262::PUNC_RPARENTHESIS) and semicolon(var_env) 219 | ECMA262::StDoWhile.new(e, s) 220 | else 221 | raise ParseError.new("unexpected token", self) 222 | end 223 | end 224 | 225 | #12.6 226 | # 227 | # for ( ExpressionNoInopt ; Expressionopt ; Expressionopt ) Statement 228 | # for ( var VariableDeclarationListNoIn ; Expressionopt ; Expressionopt ) Statement 229 | # for ( LeftHandSideExpression in Expression ) Statement 230 | # for ( var VariableDeclarationNoIn in Expression ) Statement 231 | # 232 | def for_statement(var_env) 233 | return nil unless eql_lit?(ECMA262::ID_FOR) 234 | raise ParseError('unexpected token', self) unless eql_lit?(ECMA262::PUNC_LPARENTHESIS) 235 | eval_lit{ 236 | # for(var i in a) 237 | if eql_lit?(ECMA262::ID_VAR) 238 | eval_lit{ 239 | if v=var_decl(var_env, :no_in => true) and eql_lit?(ECMA262::ID_IN) 240 | if e=exp(var_env, {}) and eql_lit?(ECMA262::PUNC_RPARENTHESIS) and s = statement(var_env) 241 | #10.5 242 | var_env.record.create_mutable_binding(v[0], nil) 243 | var_env.record.set_mutable_binding(v[0], :undefined, nil) 244 | ECMA262::StForInVar.new(var_env, v, e, s) 245 | else 246 | raise ParseError.new("unexpected token", self) 247 | end 248 | end 249 | } or eval_lit { 250 | # for(var i ; cond ; exp) 251 | if vl=var_decl_list(var_env, :no_in =>true) and s1=eql_lit?(ECMA262::PUNC_SEMICOLON) and (e=exp(var_env, {})||true) and s2=eql_lit?(ECMA262::PUNC_SEMICOLON) and (e2=exp(var_env, {})||true) and eql_lit?(ECMA262::PUNC_RPARENTHESIS) and s=statement(var_env) 252 | e = nil if e == true 253 | e2 = nil if e2 == true 254 | #10.5 255 | vl.each do |v| 256 | dn = v[0] 257 | var_env.record.create_mutable_binding(dn, nil) 258 | var_env.record.set_mutable_binding(dn, :undefined, nil) 259 | end 260 | ECMA262::StForVar.new(var_env, vl, e, e2, s) 261 | else 262 | if !s1 263 | raise ParseError.new("no semicolon", self) 264 | elsif !s2 265 | raise ParseError.new("no semicolon", self) 266 | else 267 | raise ParseError.new("unexpected token", self) 268 | end 269 | end 270 | } 271 | else # => for(i in exp) / for(i ; cond; exp) 272 | eval_lit{ 273 | # for(i in exp) 274 | if v=left_hand_side_exp(var_env) and eql_lit?(ECMA262::ID_IN) 275 | if e=exp(var_env, {}) and eql_lit?(ECMA262::PUNC_RPARENTHESIS) and s=statement(var_env) 276 | ECMA262::StForIn.new(v, e, s) 277 | else 278 | raise ParseError.new("unexpected token", self) 279 | end 280 | end 281 | } or eval_lit{ 282 | # for(i ; cond; exp) 283 | if (v=exp(var_env, :no_in => true) || true) and s1=eql_lit?(ECMA262::PUNC_SEMICOLON) and (e=exp(var_env, {}) || true) and s2=eql_lit?(ECMA262::PUNC_SEMICOLON) and (e2=exp(var_env, {})||true) and eql_lit?(ECMA262::PUNC_RPARENTHESIS) and s=statement(var_env) 284 | v = nil if v == true 285 | e = nil if e == true 286 | e2 = nil if e2 == true 287 | ECMA262::StFor.new(v, e, e2, s) 288 | else 289 | raise ParseError.new("unexpected token", self) 290 | end 291 | } 292 | end 293 | } 294 | end 295 | private :while_statement, :do_while_statement, :for_statement 296 | 297 | # Tests next literals sequence is ContinueStatement or not. 298 | # 299 | # @see http://www.ecma-international.org/ecma-262 ECMA262 12.7 300 | def continue_statement(var_env) 301 | return nil unless eql_lit?(ECMA262::ID_CONTINUE) 302 | 303 | if semicolon(var_env) 304 | ECMA262::StContinue.new 305 | elsif e=identifier(var_env) and semicolon(var_env) 306 | ECMA262::StContinue.new(e) 307 | else 308 | if e 309 | raise ParseError.new("no semicolon at end of continue statement", self) 310 | else 311 | raise ParseError.new("unexpected token", self) 312 | end 313 | end 314 | end 315 | 316 | # Tests next literals sequence is BreakStatement or not. 317 | # 318 | # @see http://www.ecma-international.org/ecma-262 ECMA262 12.8 319 | def break_statement(var_env) 320 | return nil unless eql_lit?(ECMA262::ID_BREAK) 321 | 322 | if semicolon(var_env) 323 | ECMA262::StBreak.new 324 | elsif e=identifier(var_env) and semicolon(var_env) 325 | ECMA262::StBreak.new(e) 326 | else 327 | if e 328 | raise ParseError.new("no semicolon at end of break statement", self) 329 | else 330 | raise ParseError.new("unexpected token", self) 331 | end 332 | end 333 | end 334 | # Tests next literals sequence is ReturnStatement or not. 335 | # 336 | # @see http://www.ecma-international.org/ecma-262 ECMA262 12.9 337 | def return_statement(var_env) 338 | return nil unless eql_lit?(ECMA262::ID_RETURN) 339 | 340 | if semicolon(var_env) 341 | ECMA262::StReturn.new 342 | elsif e=exp(var_env, {}) and semicolon(var_env) 343 | ECMA262::StReturn.new(e) 344 | else 345 | raise ParseError.new("unexpected token", self) 346 | end 347 | end 348 | # Tests next literals sequence is WithStatement or not. 349 | # 350 | # @see http://www.ecma-international.org/ecma-262 ECMA262 12.10 351 | def with_statement(var_env) 352 | return nil unless eql_lit?(ECMA262::ID_WITH) 353 | 354 | if eql_lit?(ECMA262::PUNC_LPARENTHESIS) and e=exp(var_env, {}) and eql_lit?(ECMA262::PUNC_RPARENTHESIS) and s=statement(var_env) 355 | ECMA262::StWith.new(var_env, e, s) 356 | else 357 | raise ParseError.new("unexpected token", self) 358 | end 359 | end 360 | # Tests next literals sequence is SwitchStatement or not. 361 | # 362 | # @see http://www.ecma-international.org/ecma-262 ECMA262 12.11 363 | def switch_statement(var_env) 364 | return nil unless eql_lit?(ECMA262::ID_SWITCH) 365 | 366 | if eql_lit?(ECMA262::PUNC_LPARENTHESIS) and e=exp(var_env, {}) and eql_lit?(ECMA262::PUNC_RPARENTHESIS) and c = case_block(var_env) 367 | ECMA262::StSwitch.new(e, c) 368 | else 369 | raise ParseError.new("unexpected token", self) 370 | end 371 | end 372 | 373 | def case_block(var_env) 374 | return nil unless eql_lit?(ECMA262::PUNC_LCURLYBRAC) 375 | _case_block = [] 376 | while true 377 | if eql_lit?(ECMA262::ID_CASE) 378 | if e = exp(var_env, {}) and eql_lit?(ECMA262::PUNC_COLON) 379 | sl = statement_list(var_env) 380 | _case_block.push [e, sl] 381 | else 382 | raise ParseError.new("unexpected token", self) 383 | end 384 | elsif eql_lit?(ECMA262::ID_DEFAULT) 385 | if eql_lit?(ECMA262::PUNC_COLON) 386 | sl = statement_list(var_env) 387 | _case_block.push [nil, sl] 388 | else 389 | raise ParseError.new("unexpected token", self) 390 | end 391 | elsif eql_lit?(ECMA262::PUNC_RCURLYBRAC) 392 | break 393 | end 394 | end 395 | _case_block 396 | end 397 | private :case_block 398 | 399 | # Tests next literals sequence is LabelledStatement or not. 400 | # 401 | # @see http://www.ecma-international.org/ecma-262 ECMA262 12.12 402 | def labelled_statement(var_env) 403 | eval_lit { 404 | if i=identifier(var_env) and s1=eql_lit?(ECMA262::PUNC_COLON) 405 | if s=statement(var_env) 406 | ECMA262::StLabelled.new(i, s) 407 | else 408 | raise ParseError.new("unexpected token", self) 409 | end 410 | else 411 | nil 412 | end 413 | } 414 | end 415 | # Tests next literals sequence is ThrowStatement or not. 416 | # 417 | # @see http://www.ecma-international.org/ecma-262 ECMA262 12.13 418 | def throw_statement(var_env) 419 | return nil unless eql_lit?(ECMA262::ID_THROW) 420 | 421 | if semicolon(var_env) 422 | raise ParseError.new("no line terminator here", self) 423 | elsif e=exp(var_env, {}) and semi = semicolon(var_env) 424 | ECMA262::StThrow.new(e) 425 | else 426 | if e 427 | raise ParseError.new("no semicolon at end of throw statement", self) 428 | else 429 | raise ParseError.new("unexpected token", self) 430 | end 431 | end 432 | end 433 | # Tests next literals sequence is TryStatement or not. 434 | # 435 | # @see http://www.ecma-international.org/ecma-262 ECMA262 12.14 436 | def try_statement(var_env) 437 | return nil unless eql_lit?(ECMA262::ID_TRY) 438 | # 439 | # The catch argument var_env must be executable lexical environment. 440 | # See compress_var 441 | # 442 | t = block(var_env) 443 | return nil unless t 444 | 445 | c = try_catch(var_env) 446 | f = try_finally(var_env) 447 | ECMA262::StTry.new(var_env, t, c, f) 448 | end 449 | # 12.14 450 | # 451 | # Catch : 452 | # catch ( Identifier ) Block 453 | # 454 | # return [identigier, block] 455 | # 456 | def try_catch(var_env) 457 | return nil unless eql_lit?(ECMA262::ID_CATCH) 458 | 459 | if eql_lit?(ECMA262::PUNC_LPARENTHESIS) and i=identifier(var_env) and eql_lit?(ECMA262::PUNC_RPARENTHESIS) and b=block(var_env) 460 | new_var_env = ECMA262::LexEnv.new(outer: var_env) 461 | ECMA262::StTryCatch.new(new_var_env, i, b) 462 | else 463 | raise ParseError.new("unexpected token", self) 464 | end 465 | end 466 | 467 | def try_finally(var_env) 468 | return nil unless eql_lit?(ECMA262::ID_FINALLY) 469 | b = block(var_env) 470 | raise ParseError.new("unexpected token", self) if b.nil? 471 | b 472 | end 473 | 474 | private :try_catch, :try_finally 475 | 476 | # Tests next literals sequence is DebuggerStatement or not. 477 | # 478 | # @see http://www.ecma-international.org/ecma-262 ECMA262 12.15 479 | def debugger_statement(var_env) 480 | return nil unless eql_lit?(ECMA262::ID_DEBUGGER) 481 | if semicolon(var_env) 482 | ECMA262::StDebugger.new 483 | else 484 | raise ParseError.new("no semicolon at end of debugger statement", self) 485 | end 486 | end 487 | 488 | end 489 | end 490 | 491 | -------------------------------------------------------------------------------- /lib/minjs/minjs_compressor.rb: -------------------------------------------------------------------------------- 1 | require 'tilt' 2 | require 'logger' 3 | 4 | module Minjs 5 | class MinjsCompressor < Tilt::Template 6 | attr_reader :logger 7 | 8 | def self.engine_initialized? 9 | defined?(::Minjs) 10 | end 11 | 12 | def initialize_engine 13 | end 14 | 15 | def prepare 16 | @logger = Logger.new(STDERR) 17 | @logger.level = Logger::WARN 18 | end 19 | 20 | def evaluate(context, locals, &block) 21 | case context.content_type 22 | when 'application/javascript' 23 | if logger.info? 24 | @@c = 0 unless defined?(@@c) 25 | puts "start: compressing" 26 | file = "tmp#{@@c}.js" 27 | output = "tmp#{@@c}.js.min" 28 | @@c += 1 29 | puts "source: #{file}" 30 | puts "output: #{output}" 31 | tmp = open(file, "w") 32 | tmp.write(data) 33 | tmp.close 34 | end 35 | #TODO 36 | t = Minjs::Compressor::Compressor.new(:logger => logger).compress(data).to_js 37 | if logger.info? 38 | tmp = open(output, "w") 39 | tmp.write(t) 40 | tmp.close 41 | end 42 | t 43 | else 44 | data 45 | end 46 | end 47 | end 48 | 49 | end 50 | 51 | 52 | -------------------------------------------------------------------------------- /lib/minjs/version.rb: -------------------------------------------------------------------------------- 1 | module Minjs 2 | # Version of minjs 3 | VERSION = "0.4.2" 4 | end 5 | -------------------------------------------------------------------------------- /minjs.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'minjs/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "minjs" 8 | spec.version = Minjs::VERSION 9 | spec.authors = ["Issei Numata"] 10 | spec.email = ["issei@heart-s.com"] 11 | 12 | # if spec.respond_to?(:metadata) 13 | # spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com' to prevent pushes to rubygems.org, or delete to allow pushes to any server." 14 | # end 15 | 16 | spec.summary = %q{JavaScript compressor in pure Ruby} 17 | spec.description = %q{Minjs is a JavaScript compressor written in pure Ruby} 18 | spec.homepage = "https://github.com/i10a/minjs" 19 | spec.license = "MIT" 20 | 21 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 22 | spec.bindir = "exe" 23 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 24 | spec.require_paths = ["lib"] 25 | 26 | spec.add_development_dependency "bundler" 27 | spec.add_development_dependency "rake" 28 | spec.add_development_dependency "rspec" 29 | spec.add_development_dependency "yard" 30 | spec.add_dependency 'tilt' 31 | end 32 | -------------------------------------------------------------------------------- /spec/compress/assignment_after_var_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Compression' do 5 | describe 'AssignmentAfterVar' do 6 | it 'move assignment expression to var statement' do 7 | c = test_compressor 8 | c.parse <<-EOS 9 | a=1;b=1; 10 | var c,b,a; 11 | EOS 12 | js = c.reorder_var.assignment_after_var.to_js 13 | expect(js).to eq "var a=1,b=1,c;" 14 | end 15 | 16 | it 'move assignment expression to var statement' do 17 | c = test_compressor 18 | c.parse <<-EOS 19 | break; 20 | a=1;b=1; 21 | var c,b,a; 22 | EOS 23 | js = c.reorder_var.assignment_after_var.to_js 24 | expect(js).to eq "break;var a=1,b=1,c;" 25 | end 26 | 27 | it 'move assignment expression to var statement' do 28 | c = test_compressor 29 | c.parse <<-EOS 30 | var a,b,c,d,e,f,g; 31 | 32 | c = x; 33 | l = y,f=(z,g=y),m=time(); 34 | a = x+z; 35 | n = k, ++i, j=1; 36 | 37 | var h,i,j,k,l,m,n 38 | EOS 39 | js = c.reorder_var.assignment_after_var.to_js 40 | expect(js).to eq "var c=x,l=y,f=(z,g=y),m=time(),a=x+z,n=k,b,d,e,g,h,i,j,k;++i,j=1;" 41 | end 42 | it 'move assignment expression to var statement' do 43 | c = test_compressor 44 | c.parse <<-EOS 45 | var $ = function() 46 | { 47 | var a=0,b=0,c=0,d;//function expression is in the var's initializer 48 | } 49 | EOS 50 | js = c.reorder_var.assignment_after_var.to_js 51 | expect(js).to eq "var $=function(){var a=0,b=0,c=0,d};" 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /spec/compress/block_to_statement_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Compression' do 5 | describe 'BlockToStatement' do 6 | it 'convert block to statement' do 7 | c = test_compressor 8 | c.parse <<-EOS 9 | if(true){;a=1} 10 | while(true){break} 11 | // try-catch require Block 12 | try{'try'}catch(e){'catch'}finally{'fin'} 13 | EOS 14 | js = c.block_to_statement.to_js 15 | expect(js).to eq "if(true)a=1;while(true)break;try{\"try\"}catch(e){\"catch\"}finally{\"fin\"}" 16 | end 17 | 18 | it 'does not convert block to statement' do 19 | c = test_compressor 20 | c.parse <<-EOS 21 | if(a){ 22 | while(true) 23 | if(b){ 24 | d(); 25 | } 26 | } 27 | else{ 28 | c(); 29 | } 30 | EOS 31 | js = c.block_to_statement.to_js 32 | expect(js).to eq "if(a){while(true)if(b)d()}else c();" 33 | end 34 | 35 | it 'convert block to statement' do 36 | c = test_compressor 37 | c.parse <<-EOS 38 | if(a) //<- add block here 39 | while(b){ // <- remove block here 40 | if(c) 41 | break d; 42 | } 43 | else 44 | e; 45 | EOS 46 | js = c.block_to_statement.to_js 47 | expect(js).to eq "if(a){while(b)if(c)break d}else e;" 48 | end 49 | end 50 | end 51 | 52 | -------------------------------------------------------------------------------- /spec/compress/compress_var_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Compression' do 5 | describe 'CompressVar' do 6 | it 'compress var name' do 7 | c = test_compressor 8 | c.parse <<-EOS 9 | function xxxx() 10 | { 11 | var aaaa, bbbb;// => a,b 12 | function yyyy(){//=>d 13 | var cccc, dddd, a, b; // => a,b,c,e 14 | } 15 | function wwww(c, d){//=>d(a,b) 16 | var cccc, dddd, a, b; // => c,d,e,f 17 | } 18 | function eeee(c, d){//=>e(a,b) 19 | var aaaa, bbbb; // => c,d 20 | } 21 | function rrrr(c, d){//=>f(c,d) 22 | aaaa, bbbb; // => a,b 23 | function i(){ 24 | } 25 | aaaa:while(true) 26 | continue aaaa; 27 | } 28 | } 29 | EOS 30 | js = c.compress_var.to_js 31 | 32 | expect(js).to eq "function xxxx(){var a,b;function d(){var a,b,c,e}function c(a,b){var d,e,f,g}function e(a,b){var c,d}function f(c,d){a,b;function i(){}a:while(true)continue a}}" 33 | end 34 | it 'compress try-catch var name' do 35 | c = test_compressor 36 | c.parse <<-EOS 37 | function zzz(){ 38 | var aaaa; 39 | try{ 40 | } 41 | catch(aaaa){ 42 | var bbb; 43 | console.log(aaaa); 44 | } 45 | finally{ 46 | } 47 | } 48 | EOS 49 | js = c.compress_var.to_js 50 | expect(js).to eq "function zzz(){var a;try{}catch(a){var b;console.log(a)}finally{}}" 51 | end 52 | 53 | it 'compress var name' do 54 | c = test_compressor 55 | c.parse <<-EOS 56 | function x() 57 | { 58 | var a; 59 | function b(xxx,yyy,zzz){ 60 | var b; 61 | } 62 | } 63 | EOS 64 | js = c.compress_var.to_js 65 | expect(js).to eq "function x(){var a;function b(a,c,d){var b}}" 66 | end 67 | 68 | it 'compress var name' do 69 | c = test_compressor 70 | c.parse <<-EOS 71 | function zz() 72 | { 73 | var a = function b(){ 74 | console.log(b); 75 | } 76 | console.log(b); 77 | } 78 | EOS 79 | js = c.compress_var.to_js 80 | expect(js).to eq "function zz(){var a=function b(){console.log(b)};console.log(b)}" 81 | end 82 | 83 | it 'compress without exception' do 84 | c = test_compressor 85 | c.parse <<-EOS 86 | function zz() 87 | { 88 | try{ 89 | }//no catch-clause 90 | finally{ 91 | } 92 | } 93 | EOS 94 | js = c.compress_var.to_js 95 | expect(js).to eq "function zz(){try{}finally{}}" 96 | end 97 | end 98 | end 99 | 100 | -------------------------------------------------------------------------------- /spec/compress/grouping_statement_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Compression' do 5 | describe 'GroupingStatement' do 6 | it 'convert sequence of statement to single expression statement' do 7 | c = test_compressor 8 | c.parse <<-EOS 9 | a=1;b=2;c=3;while(true);new a();f(g);this.a()?c:d;d=1,e=1; 10 | EOS 11 | js = c.grouping_statement.to_js 12 | expect(js).to eq "a=1,b=2,c=3;while(true);new a(),f(g),this.a()?c:d,(d=1,e=1);" 13 | end 14 | 15 | it 'convert sequence of statement to single expression statement' do 16 | c = test_compressor 17 | c.parse <<-EOS 18 | a=1;b=2;c=3;return a; 19 | EOS 20 | js = c.grouping_statement.to_js 21 | expect(js).to eq "return a=1,b=2,c=3,a;" 22 | end 23 | 24 | end 25 | end 26 | 27 | -------------------------------------------------------------------------------- /spec/compress/if_to_cond_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Compression' do 5 | describe 'IfToCond' do 6 | it 'convert if statement to conditonal expression' do 7 | c = test_compressor 8 | c.parse <<-EOS 9 | if(a)b;else c; 10 | if(a)b; 11 | if(!a)b;else c; 12 | if(!a)b; 13 | EOS 14 | js = c.if_to_cond.to_js 15 | expect(js).to eq "a?b:c;a&&b;a?c:b;a||b;" 16 | end 17 | it 'convert if statement to return statement' do 18 | c = test_compressor 19 | c.parse <<-EOS 20 | if(a)return b;else return c; 21 | EOS 22 | js = c.if_to_cond.to_js 23 | expect(js).to eq "return a?b:c;" 24 | end 25 | it 'prefer conditonal operator than logical and/or' do 26 | c = test_compressor 27 | c.parse <<-EOS 28 | if(x)a=b?c=d:e=f; 29 | EOS 30 | js = c.if_to_cond.to_js 31 | expect(js).to eq "x?a=b?c=d:e=f:0;" 32 | end 33 | end 34 | end 35 | 36 | -------------------------------------------------------------------------------- /spec/compress/if_to_return2_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Compression' do 5 | describe 'IfToReturn2' do 6 | it 'convert if statment to return statement' do 7 | c = test_compressor 8 | c.parse <<-EOS 9 | if(a)return b;return c; 10 | EOS 11 | js = c.if_to_return2.to_js 12 | expect(js).to eq "return a?b:c;" 13 | end 14 | 15 | it 'convert if statment to return statement' do 16 | c = test_compressor 17 | c.parse <<-EOS 18 | if(a)return b; 19 | if(c)return d; 20 | if(e)return f; 21 | EOS 22 | js = c.if_to_return2.to_js 23 | expect(js).to eq "return a?b:c?d:e?f:void 0;" 24 | end 25 | end 26 | end 27 | 28 | -------------------------------------------------------------------------------- /spec/compress/reduce_exp_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Compression' do 5 | describe 'ReduceExpression' do 6 | it 'reduces strict equles operators to non-strict operators' do 7 | c = test_compressor 8 | c.parse <<-EOS 9 | typeof A === "string"; 10 | typeof A !== "string"; 11 | +A === 0; 12 | (a="0") === "0" 13 | EOS 14 | js = c.reduce_exp.to_js 15 | expect(js).to eq "typeof A==\"string\";typeof A!=\"string\";+A==0;(a=\"0\")==\"0\";" 16 | end 17 | 18 | it 'reduces to assignment expression' do 19 | c = test_compressor 20 | c.parse <<-EOS 21 | a = a / 2 22 | a = a * 2 23 | a = a % 2 24 | a = a + 2 25 | a = a - 2 26 | a = a << 2 27 | a = a >> 2 28 | a = a >>> 2 29 | a = a & 2 30 | a = a | 2 31 | a = a ^ 2 32 | EOS 33 | js = c.reduce_exp.to_js 34 | expect(js).to eq "(a/=2);(a*=2);(a%=2);(a+=2);(a-=2);(a<<=2);(a>>=2);(a>>>=2);(a&=2);(a|=2);(a^=2);" 35 | end 36 | 37 | it 'reduces logical not expression' do 38 | c = test_compressor 39 | c.parse "! ! ! !a; ! ! ! ! 0; ! ! ! 1234;! ! ! ! !{}" 40 | js = c.reduce_exp.to_js 41 | expect(js).to eq "!!a;!1;!1;!{};" 42 | end 43 | 44 | it 'reduces addtive expression' do 45 | c = test_compressor 46 | c.parse <<-EOS 47 | 1+0; 48 | 0+2; 49 | 1+3; 50 | 1.4+3.5; 51 | "a"+"b" 52 | EOS 53 | js = c.reduce_exp.to_js 54 | expect(js).to eq "1;2;4;4.9;\"ab\";" 55 | end 56 | 57 | it 'reduces addtive expression and results are string' do 58 | c = test_compressor 59 | c.parse <<-EOS 60 | "a"+undefined 61 | "a"+null 62 | "a"+false 63 | "a"+true 64 | "a"+0.4 65 | "a"+{} 66 | undefined+'A' 67 | null+'A' 68 | false+'A' 69 | true+'A' 70 | 0.4+'A' 71 | {}+'A' 72 | EOS 73 | js = c.reduce_exp.to_js 74 | expect(js).to eq ('"a"+undefined;"anull";"afalse";"atrue";"a0.4";"a"+{};' + 75 | 'undefined+"A";"nullA";"falseA";"trueA";"0.4A";{}+"A";') 76 | end 77 | 78 | it 'reduces expression and results are number' do 79 | c = test_compressor 80 | c.parse <<-EOS 81 | 1+4; 82 | 1e3+4.4e4; 83 | 1+true; 84 | 2+null; 85 | 3+false; 86 | true+false; 87 | true*true; 88 | 3.14*2.718; 89 | true-null; 90 | EOS 91 | js = c.reduce_exp.to_js 92 | expect(js).to eq ('5;45e3;2;2;3;1;1;8.53452;1;') 93 | end 94 | 95 | it 'reduces expression with string and results are number' do 96 | c = test_compressor 97 | c.parse <<-'EOS' 98 | "1"-true //0 99 | " 3.14"-true //=>2.14 preceded by white space 100 | "2.71\n "-true //=>1.71 followed by white space 101 | " 0001000 "-true //=>999 leading 0 digits 102 | "+50"-true //=>49 plus sign 103 | "-50"-true //=>51 minus sign 104 | "1.23e4"*"10" //=>123e3 105 | "1.23e+4"*"10" //=>123e3 106 | "1.23e-4"-"10" //=>-9.999877; 107 | " " - "1" //-1 108 | "" - "1" //-1 109 | "A" - 1 // NaN 110 | EOS 111 | js = c.reduce_exp.to_js 112 | expect(js).to eq ('0;2.14;1.71;999;49;-51;123e3;123e3;-9.999877;-1;-1;"A"-1;') 113 | end 114 | end 115 | end 116 | 117 | -------------------------------------------------------------------------------- /spec/compress/reduce_if_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Compression' do 5 | describe 'ReduceIf' do 6 | it 'reduce nested "if" statement' do 7 | c = test_compressor 8 | c.parse <<-EOS 9 | if(a) 10 | if(b) 11 | break; 12 | EOS 13 | js = c.reduce_if.to_js 14 | expect(js).to eq "if(a&&b)break;" 15 | end 16 | it 'reduce if' do 17 | c = test_compressor 18 | c.parse <<-EOS 19 | if(a); 20 | if(b){} 21 | if(c);else; 22 | if(d){}else{} 23 | if(e)aa;else; 24 | if(f)bb;else{} 25 | if(g) 26 | if(h) 27 | hh; 28 | else 29 | ; 30 | else 31 | gg; 32 | EOS 33 | js = c.reduce_if.to_js 34 | expect(js).to eq "a;b;c;d;if(e)aa;if(f)bb;if(g){if(h)hh}else gg;" 35 | end 36 | it 'reduce if' do 37 | c = test_compressor 38 | c.parse <<-EOS 39 | if(a);else aaa; 40 | if(a){}else aaa; 41 | EOS 42 | js = c.reduce_if.to_js 43 | expect(js).to eq "if(!a)aaa;if(!a)aaa;" 44 | end 45 | it 'reduce if' do 46 | c = test_compressor 47 | c.parse <<-EOS 48 | if((a))z; 49 | if(!!a)z; 50 | EOS 51 | js = c.reduce_if.to_js 52 | expect(js).to eq "if(a)z;if(a)z;" 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec/compress/remove_paren_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Compression' do 5 | describe 'RemoveParen' do 6 | it 'remove paren' do 7 | c = test_compressor 8 | c.parse <<-EOS 9 | (!0)+a 10 | EOS 11 | js = c.add_remove_paren.to_js 12 | expect(js).to eq "!0+a;" 13 | end 14 | 15 | it 'remove paren of for statemetns' do 16 | c = test_compressor 17 | c.parse <<-'EOS' 18 | for((a,b);(c,d);(e,f)) 19 | ; 20 | for(var a=(1);(c,d);(e,f)) 21 | ; 22 | for((a) in (a,b)) 23 | ; 24 | for(var a=(1) in (a,b)) 25 | ; 26 | EOS 27 | js = c.add_remove_paren.to_js 28 | expect(js).to eq "for(a,b;c,d;e,f);for(var a=1;c,d;e,f);for(a in a,b);for(var a=1 in a,b);" 29 | end 30 | it 'remove paren of switch statements' do 31 | c = test_compressor 32 | c.parse <<-'EOS' 33 | switch((1,2)){ 34 | case (1,2): 35 | ; 36 | } 37 | EOS 38 | js = c.add_remove_paren.to_js 39 | expect(js).to eq "switch(1,2){case 1,2:}" 40 | end 41 | 42 | it 'remove paren of statements' do 43 | c = test_compressor 44 | c.parse <<-'EOS' 45 | var a=(1),b=(1,2); 46 | if((1,2)); 47 | do{}while((1,2)); 48 | while((1,2)){}; 49 | return (1,2); 50 | with((1,2)){}; 51 | throw (1,2); 52 | EOS 53 | js = c.add_remove_paren.to_js 54 | expect(js).to eq "var a=1,b=(1,2);if(1,2);do{}while(1,2);while(1,2){}return 1,2;with(1,2){}throw(1,2);"; 55 | end 56 | 57 | it 'remove paren of primary expression' do 58 | c = test_compressor 59 | c.parse <<-EOS 60 | (this); 61 | (foo); 62 | (0); 63 | (3.14); 64 | ("aaa"); 65 | (/regexp/); 66 | (null); 67 | (true); 68 | (false); 69 | ([1,2,3]); 70 | //({a:b, c:d}); => not remove 71 | a=({a:b, c:d}); 72 | ((((('a'))))); 73 | EOS 74 | js = c.add_remove_paren.to_js 75 | expect(js).to eq "this;foo;0;3.14;\"aaa\";/regexp/;null;true;false;[1,2,3];a={a:b,c:d};\"a\";" 76 | end 77 | 78 | it 'remove paren of left-hand-side operators' do 79 | c = test_compressor 80 | c.parse <<-EOS 81 | (a)[0]; 82 | (a).b; 83 | new (A); 84 | new (A)(a,b,c); 85 | new (A)(a,(b?c:d),(c,d)); 86 | EOS 87 | js = c.add_remove_paren.to_js 88 | expect(js).to eq "a[0];a.b;new A;new A(a,b,c);new A(a,b?c:d,(c,d));"; 89 | end 90 | 91 | it 'remove paren of postfix operators' do 92 | c = test_compressor 93 | c.parse <<-EOS 94 | (a)++; 95 | (b[0])--; 96 | EOS 97 | js = c.add_remove_paren.to_js 98 | expect(js).to eq "a++;b[0]--;" 99 | end 100 | 101 | it 'remove paren of unary operators' do 102 | c = test_compressor 103 | c.parse <<-EOS 104 | +(a*b); 105 | +(a++);//remove 106 | +(++a);//remove 107 | (+ a)++; 108 | +(a[0]);//remove 109 | EOS 110 | js = c.add_remove_paren.to_js 111 | expect(js).to eq "+(a*b);+a++;+ ++a;(+a)++;+a[0];" 112 | end 113 | 114 | it 'remove paren of multiplicative operators ' do 115 | c = test_compressor 116 | c.parse <<-EOS 117 | (a*b)*c; 118 | a*(b*c);// does not remove 119 | a*(!b); 120 | (a/b)/c; 121 | a/(b/c);// does not remove 122 | a/(!b); 123 | (a%b)%c; 124 | a%(b%c);// does not remove 125 | a%(!b); 126 | EOS 127 | js = c.add_remove_paren.to_js 128 | expect(js).to eq "a*b*c;a*(b*c);a*!b;a/b/c;a/(b/c);a/!b;a%b%c;a%(b%c);a%!b;" 129 | end 130 | 131 | it 'remove paren of additive operators ' do 132 | c = test_compressor 133 | c.parse <<-EOS 134 | (a+b)+c; 135 | a+(b+c);// does not remove 136 | a+(b*c); 137 | (a-b)-c; 138 | a-(b-c);// does not remove 139 | a-(b*c); 140 | EOS 141 | js = c.add_remove_paren.to_js 142 | expect(js).to eq "a+b+c;a+(b+c);a+b*c;a-b-c;a-(b-c);a-b*c;" 143 | end 144 | 145 | it 'remove paren of relational operators' do 146 | c = test_compressor 147 | c.parse <<-EOS 148 | (a+b)<<(c+d)>>(e+f)>>>(g+h); 149 | (a<(c<=(c<>e+f>>>g+h;a<c<=c<b)==(cb)!=(cb)===(cb)!==(cb==cb!=cb===cb!==c is expression. 11 | function cc(){} // => is declaration, move to top. 12 | var b=function (){} // => is expression. 13 | function dd(){} // => is declaration, move to top too. 14 | EOS 15 | js = c.reorder_function_decl.to_js 16 | expect(js).to eq "function cc(){}function dd(){}foo;var a=function aa(){};var b=function(){};" 17 | end 18 | end 19 | end 20 | 21 | -------------------------------------------------------------------------------- /spec/compress/reorder_var_decl_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Compression' do 5 | describe 'ReorderVarDeclaration' do 6 | it 'reorder var declaration' do 7 | c = test_compressor 8 | c.parse <<-EOS 9 | function z() 10 | { 11 | a=1; 12 | b=2; 13 | c=3; 14 | d=4; 15 | var b; 16 | } 17 | EOS 18 | js = c.reorder_var.to_js 19 | expect(js).to eq "function z(){a=1;var b;b=2;c=3;d=4}" 20 | end 21 | it 'reorder var declaration' do 22 | c = test_compressor 23 | c.parse <<-EOS 24 | function z() 25 | { 26 | t={ 27 | get getter(){a=1;b=2;c=3;var b;},// 'b' is in the getter scope 28 | set setter(val){a=1;b=2;c=3;var b;}// 'b' is in the setter scope 29 | } 30 | } 31 | EOS 32 | js = c.reorder_var.to_js 33 | expect(js).to eq "function z(){t={get getter(){a=1;var b;b=2;c=3},set setter(val){a=1;var b;b=2;c=3}}}" 34 | end 35 | end 36 | end 37 | 38 | -------------------------------------------------------------------------------- /spec/compress/simple_replacement_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Compression' do 5 | describe 'SimpleReplacement' do 6 | it 'replace to shorter one' do 7 | c = test_compressor 8 | c.parse <<-EOS 9 | true; 10 | false; 11 | if(1)a; 12 | if(0)a; 13 | if(0)a;else b; 14 | while(1); 15 | EOS 16 | js = c.simple_replacement.to_js 17 | expect(js).to eq "(!0);(!1);a;b;for(;;);" 18 | end 19 | 20 | it 'replace to shorter one' do 21 | c = test_compressor 22 | c.parse <<-EOS 23 | if(1&&"0"&&/a/&&[]&&{})a; 24 | if(""||1)b; 25 | if(1&&false)c; 26 | if(""||null||0)d; 27 | EOS 28 | js = c.simple_replacement.to_js 29 | expect(js).to eq "a;b;" 30 | end 31 | 32 | it 'replace array argument to prop' do 33 | c = test_compressor 34 | c.parse <<-EOS 35 | A["B"]; 36 | A["$"]; 37 | A["あ"]; 38 | A["3.14"]; 39 | A[""]; 40 | EOS 41 | js = c.simple_replacement.to_js 42 | expect(js).to eq 'A.B;A.$;A.あ;A[3.14];A[""];' 43 | end 44 | it 'add and remove paren' do 45 | c = test_compressor 46 | c.parse <<-EOS 47 | new a()() // mean => (new a()) () => (new a)() 48 | EOS 49 | js = c.simple_replacement.to_js 50 | expect(js).to eq "(new a)();" 51 | end 52 | end 53 | end 54 | 55 | -------------------------------------------------------------------------------- /spec/compress/use_strcit_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Compression' do 4 | describe 'UseStrict' do 5 | it 'remains "use strict" at the top of program' do 6 | c = test_compressor 7 | c.parse <<-EOS 8 | "use strict" 9 | function zz(){} 10 | EOS 11 | js = c.reduce_if.to_js 12 | expect(js).to eq "\"use strict\";function zz(){}" 13 | end 14 | it 'remains "use strict" at the top of program' do 15 | c = test_compressor 16 | c.parse <<-EOS 17 | "use strict" 18 | var a=1; 19 | EOS 20 | js = c.reduce_if.to_js 21 | expect(js).to eq "\"use strict\";var a=1;" 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/expression/additive_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Expression' do 5 | describe 'Additive' do 6 | it 'is additive operator' do 7 | js = test_parse <<-EOS 8 | 1+2 9 | 2-3 10 | EOS 11 | expect(js).to eq "1+2;2-3;" 12 | end 13 | it 'cause syntax error' do 14 | expect { 15 | js = test_parse '1+' 16 | }.to raise_error(Minjs::Lex::ParseError) 17 | expect { 18 | js = test_parse '1-1-' 19 | }.to raise_error(Minjs::Lex::ParseError) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/expression/assignment_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Expression' do 5 | describe 'Assignment' do 6 | it 'is assignment operator' do 7 | js = test_parse <<-EOS 8 | a=1 9 | a*=2 10 | a/=3 11 | a%=4 12 | a+=5 13 | a-=6 14 | a<<=7 15 | a>>=8 16 | a>>>=9 17 | a&=10 18 | a^=11 19 | a|=12 20 | EOS 21 | expect(js).to eq "a=1;a*=2;a/=3;a%=4;a+=5;a-=6;a<<=7;a>>=8;a>>>=9;a&=10;a^=11;a|=12;" 22 | end 23 | it 'cause syntax error' do 24 | expect { 25 | js = test_parse 'a=' 26 | }.to raise_error(Minjs::Lex::ParseError) 27 | expect { 28 | js = test_parse 'a*=' 29 | }.to raise_error(Minjs::Lex::ParseError) 30 | expect { 31 | js = test_parse 'a/=' 32 | }.to raise_error(Minjs::Lex::ParseError) 33 | expect { 34 | js = test_parse 'a%=' 35 | }.to raise_error(Minjs::Lex::ParseError) 36 | expect { 37 | js = test_parse 'a+=' 38 | }.to raise_error(Minjs::Lex::ParseError) 39 | expect { 40 | js = test_parse 'a-=' 41 | }.to raise_error(Minjs::Lex::ParseError) 42 | expect { 43 | js = test_parse 'a<<=' 44 | }.to raise_error(Minjs::Lex::ParseError) 45 | expect { 46 | js = test_parse 'a>>=' 47 | }.to raise_error(Minjs::Lex::ParseError) 48 | expect { 49 | js = test_parse 'a>>>=' 50 | }.to raise_error(Minjs::Lex::ParseError) 51 | expect { 52 | js = test_parse 'a&=' 53 | }.to raise_error(Minjs::Lex::ParseError) 54 | expect { 55 | js = test_parse 'a|=' 56 | }.to raise_error(Minjs::Lex::ParseError) 57 | expect { 58 | js = test_parse 'a^=' 59 | }.to raise_error(Minjs::Lex::ParseError) 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/expression/binary_bitwise_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Expression' do 5 | describe 'BinaryBitwise' do 6 | it 'is binary bitwise operator' do 7 | js = test_parse <<-EOS 8 | 123 & 456; 9 | 456 ^ 123; 10 | 789 | 123; 11 | for(var i=(123&456);false;); 12 | for(var i=(123^456);false;); 13 | for(var i=(123|456);false;); 14 | EOS 15 | expect(js).to eq "123&456;456^123;789|123;for(var i=(123&456);false;);for(var i=(123^456);false;);for(var i=(123|456);false;);" 16 | end 17 | 18 | it 'cause syntax error' do 19 | expect { 20 | js = test_parse '1&' 21 | }.to raise_error(Minjs::Lex::ParseError) 22 | expect { 23 | js = test_parse '1^' 24 | }.to raise_error(Minjs::Lex::ParseError) 25 | expect { 26 | js = test_parse '1|' 27 | }.to raise_error(Minjs::Lex::ParseError) 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/expression/binary_logical_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Expression' do 5 | describe 'BinaryLogical' do 6 | it 'is binary logical operator' do 7 | js = test_parse <<-EOS 8 | 123 && 456; 9 | 456 || 123; 10 | for(var i=(123&&456);false;); 11 | for(var i=(123||456);false;); 12 | EOS 13 | expect(js).to eq "123&&456;456||123;for(var i=(123&&456);false;);for(var i=(123||456);false;);" 14 | end 15 | it 'cause syntax error' do 16 | expect { 17 | js = test_parse '1&&' 18 | }.to raise_error(Minjs::Lex::ParseError) 19 | expect { 20 | js = test_parse '1||' 21 | }.to raise_error(Minjs::Lex::ParseError) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/expression/bitwise_shift_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Expression' do 5 | describe 'BitwiseShift' do 6 | it 'is bitwise shift operator' do 7 | js = test_parse <<-EOS 8 | 123 << 1; 9 | 456 >> 2; 10 | 789 >>> 3; 11 | EOS 12 | expect(js).to eq "123<<1;456>>2;789>>>3;" 13 | end 14 | it 'cause syntax error' do 15 | expect { 16 | js = test_parse '1<<' 17 | }.to raise_error(Minjs::Lex::ParseError) 18 | expect { 19 | js = test_parse '1>>' 20 | }.to raise_error(Minjs::Lex::ParseError) 21 | expect { 22 | js = test_parse '1>>>' 23 | }.to raise_error(Minjs::Lex::ParseError) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/expression/comma_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Expression' do 5 | describe 'Comma' do 6 | it 'is comma operator' do 7 | js = test_parse <<-EOS 8 | a=1,b=a+1,c=b*2 9 | EOS 10 | expect(js).to eq "a=1,b=a+1,c=b*2;" 11 | end 12 | it 'cause syntax error' do 13 | expect { 14 | js = test_parse '1,' 15 | }.to raise_error(Minjs::Lex::ParseError) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/expression/conditional_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Expression' do 5 | describe 'Conditional' do 6 | it 'is conditional operator' do 7 | js = test_parse <<-EOS 8 | a=1?b=2:c=3 9 | EOS 10 | expect(js).to eq "a=1?b=2:c=3;" 11 | end 12 | it 'cause syntax error' do 13 | expect { 14 | js = test_parse '1?a:' 15 | }.to raise_error(Minjs::Lex::ParseError) 16 | expect { 17 | js = test_parse '1?' 18 | }.to raise_error(Minjs::Lex::ParseError) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/expression/equality_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Expression' do 5 | describe 'Equality' do 6 | it 'is equality operator' do 7 | js = test_parse <<-EOS 8 | a=0 9 | b=false 10 | a == b; 11 | a != b; 12 | a === b; 13 | a !== b; 14 | for(var i=(a==b);false;); 15 | for(var i=(a!=b);false;); 16 | for(var i=(a===b);false;); 17 | for(var i=(a!==b);false;); 18 | EOS 19 | expect(js).to eq "a=0;b=false;a==b;a!=b;a===b;a!==b;for(var i=(a==b);false;);for(var i=(a!=b);false;);for(var i=(a===b);false;);for(var i=(a!==b);false;);" 20 | end 21 | it 'cause syntax error' do 22 | expect { 23 | js = test_parse 'a==' 24 | }.to raise_error(Minjs::Lex::ParseError) 25 | expect { 26 | js = test_parse 'a!=' 27 | }.to raise_error(Minjs::Lex::ParseError) 28 | expect { 29 | js = test_parse 'a===' 30 | }.to raise_error(Minjs::Lex::ParseError) 31 | expect { 32 | js = test_parse 'a!==' 33 | }.to raise_error(Minjs::Lex::ParseError) 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/expression/left_hand_side_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Expression' do 5 | describe 'LeftHandSide' do 6 | it 'is left hand side expression' do 7 | js = test_parse <<-EOS 8 | a;//primary expression 9 | b();//function expression 10 | a[0]; 11 | a.b; 12 | new a 13 | new a() 14 | new a(0,1,2) 15 | new new new a 16 | a[0][1][2] 17 | a.b.c.d.e.f 18 | a[0].b.c[2].e 19 | a(1)(2)(3)[4] 20 | EOS 21 | expect(js).to eq "a;b();a[0];a.b;new a;new a();new a(0,1,2);new new new a;a[0][1][2];a.b.c.d.e.f;a[0].b.c[2].e;a(1)(2)(3)[4];" 22 | end 23 | it 'cause syntax error' do 24 | expect { 25 | js = test_parse 'a[]' 26 | }.to raise_error(Minjs::Lex::ParseError) 27 | expect { 28 | js = test_parse 'a.' 29 | }.to raise_error(Minjs::Lex::ParseError) 30 | expect { 31 | js = test_parse 'new' 32 | }.to raise_error(Minjs::Lex::ParseError) 33 | expect { 34 | js = test_parse 'a[' 35 | }.to raise_error(Minjs::Lex::ParseError) 36 | expect { 37 | js = test_parse 'a(' 38 | }.to raise_error(Minjs::Lex::ParseError) 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/expression/multiplicative_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Expression' do 5 | describe 'Multiplicative' do 6 | it 'is multiplicative operator' do 7 | js = test_parse <<-EOS 8 | 1*2 9 | 2/3 10 | 2%3 11 | EOS 12 | expect(js).to eq "1*2;2/3;2%3;" 13 | end 14 | it 'cause syntax error' do 15 | expect { 16 | js = test_parse 'a*' 17 | }.to raise_error(Minjs::Lex::ParseError) 18 | expect { 19 | js = test_parse 'a/' 20 | }.to raise_error(Minjs::Lex::ParseError) 21 | expect { 22 | js = test_parse 'a%' 23 | }.to raise_error(Minjs::Lex::ParseError) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/expression/postfix_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Expression' do 5 | describe 'Postfix' do 6 | it 'is postinc or postdec operator' do 7 | js = test_parse <<-EOS 8 | a++ 9 | b-- 10 | c 11 | EOS 12 | expect(js).to eq "a++;b--;c;" 13 | end 14 | it 'cause syntax error' do 15 | expect { 16 | js = test_parse '++' 17 | }.to raise_error(Minjs::Lex::ParseError) 18 | expect { 19 | js = test_parse '--' 20 | }.to raise_error(Minjs::Lex::ParseError) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/expression/primary_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Expression' do 5 | describe 'Primary' do 6 | it 'is primary expression' do 7 | js = test_parse <<-EOS 8 | this; 9 | hoge; 10 | 123; 11 | "456"; 12 | [1,2,3]; 13 | +{a:b, c:d, e:f} 14 | (1+2+3) 15 | EOS 16 | expect(js).to eq "this;hoge;123;\"456\";[1,2,3];+{a:b,c:d,e:f}(1+2+3);" 17 | end 18 | it 'cause syntax error' do 19 | expect { 20 | js = test_parse '()' 21 | }.to raise_error(Minjs::Lex::ParseError) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/expression/relational_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Expression' do 5 | describe 'Relational' do 6 | it 'is relational operator' do 7 | js = test_parse <<-EOS 8 | a=0 9 | b=10 10 | a < b; 11 | a > b; 12 | a <= b; 13 | a >= b; 14 | a instanceof b; 15 | a in b; 16 | for(var i=(ab);false;); 18 | for(var i=(a<=b);false;); 19 | for(var i=(a>=b);false;); 20 | for(var i=(a instanceof b);false;); 21 | EOS 22 | expect(js).to eq "a=0;b=10;ab;a<=b;a>=b;a instanceof b;a in b;for(var i=(ab);false;);for(var i=(a<=b);false;);for(var i=(a>=b);false;);for(var i=(a instanceof b);false;);" 23 | end 24 | it 'cause syntax error' do 25 | expect { 26 | js = test_parse 'a<' 27 | }.to raise_error(Minjs::Lex::ParseError) 28 | expect { 29 | js = test_parse 'b>' 30 | }.to raise_error(Minjs::Lex::ParseError) 31 | expect { 32 | js = test_parse 'a<=' 33 | }.to raise_error(Minjs::Lex::ParseError) 34 | expect { 35 | js = test_parse 'b>=' 36 | }.to raise_error(Minjs::Lex::ParseError) 37 | expect { 38 | js = test_parse 'a instanceof' 39 | }.to raise_error(Minjs::Lex::ParseError) 40 | expect { 41 | js = test_parse 'b in' 42 | }.to raise_error(Minjs::Lex::ParseError) 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/expression/unary_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Expression' do 5 | describe 'Unary' do 6 | it 'is unary operator' do 7 | js = test_parse <<-EOS 8 | delete a; 9 | void a; 10 | typeof a; 11 | ++a; 12 | --a; 13 | +a; 14 | -a; 15 | ~a; 16 | !a; 17 | EOS 18 | expect(js).to eq "delete a;void a;typeof a;++a;--a;+a;-a;~a;!a;" 19 | end 20 | 21 | it 'is preinc or predec operator' do 22 | js = test_parse <<-EOS 23 | ++a 24 | --b 25 | 26 | b 27 | ++ 28 | c 29 | EOS 30 | expect(js).to eq "++a;--b;b;++c;" 31 | end 32 | 33 | it 'cause syntax error' do 34 | expect { 35 | js = test_parse 'delete' 36 | }.to raise_error(Minjs::Lex::ParseError) 37 | expect { 38 | js = test_parse 'void' 39 | }.to raise_error(Minjs::Lex::ParseError) 40 | expect { 41 | js = test_parse 'typeof' 42 | }.to raise_error(Minjs::Lex::ParseError) 43 | expect { 44 | js = test_parse '++' 45 | }.to raise_error(Minjs::Lex::ParseError) 46 | expect { 47 | js = test_parse '--' 48 | }.to raise_error(Minjs::Lex::ParseError) 49 | expect { 50 | js = test_parse '+' 51 | }.to raise_error(Minjs::Lex::ParseError) 52 | expect { 53 | js = test_parse '-' 54 | }.to raise_error(Minjs::Lex::ParseError) 55 | expect { 56 | js = test_parse '!' 57 | }.to raise_error(Minjs::Lex::ParseError) 58 | expect { 59 | js = test_parse '~' 60 | }.to raise_error(Minjs::Lex::ParseError) 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /spec/literal/array_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Literal' do 5 | describe 'Array' do 6 | it 'is array literal' do 7 | js = test_parse <<-EOS 8 | a=[1,2,3] 9 | b=["a","b","c"] 10 | c=[1,,3] 11 | d=[1,2,3,] 12 | EOS 13 | expect(js).to eq "a=[1,2,3];b=[\"a\",\"b\",\"c\"];c=[1,,3];d=[1,2,3];" 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/literal/comment_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Literal' do 5 | describe 'Comment' do 6 | it 'is singleline comment literal' do 7 | js = test_parse <<-EOS 8 | // singleline 9 | // singleline (no lt) 10 | EOS 11 | expect(js).to eq ("") 12 | end 13 | 14 | it 'is multiline comment literal' do 15 | js = test_parse <<-EOS 16 | /* single line */ 17 | /* 18 | multiline 19 | */ 20 | EOS 21 | expect(js).to eq ("") 22 | end 23 | 24 | it 'is multiline comment and treated as white space' do 25 | js = test_parse <<-EOS 26 | return /* multiline */1+2 27 | EOS 28 | expect(js).to eq("return 1+2;") 29 | end 30 | 31 | it 'is multiline comment and treated as line terminator' do 32 | js = test_parse <<-EOS 33 | return /* multiline 34 | */1+2 35 | EOS 36 | expect(js).to eq("return;1+2;") 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/literal/identifier_name_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Literal' do 5 | describe 'IdentifierName' do 6 | it 'is identifier name' do 7 | js = test_parse <<-'EOS' 8 | a=1; 9 | $=2; 10 | _=3; 11 | console.log(\u0061);//same as 'a' 12 | ab0=1; 13 | $$$=2; 14 | __0=3; 15 | console.log(\u0061\u0062\u0030);//same as 'ab0' 16 | EOS 17 | expect(js).to eq "a=1;$=2;_=3;console.log(a);ab0=1;$$$=2;__0=3;console.log(ab0);" 18 | end 19 | 20 | it 'is identifier name' do 21 | js = test_parse <<-"EOS" 22 | \u0100=4;// Unicode(0100) Lu 23 | \u0101=5;// Unicode(0101) Ll 24 | \u01c5=6;// Unicode(01c5) Lt 25 | \u02b0=7;// Unicode(02b0) Lm 26 | \u3042=6;// unicode(3042) Lo 27 | \u16ee=8;// Unicode(16ee) Nl 28 | console.log(あ) 29 | EOS 30 | expect(js).to eq "Ā=4;ā=5;Dž=6;ʰ=7;あ=6;ᛮ=8;console.log(あ);" 31 | end 32 | 33 | it 'is identifier name' do 34 | js = test_parse <<-"EOS" 35 | \u3042\u309a=9;// Unicode(309a) Mn 36 | \u3042\u0903=10;// Unicode(0903) Mc 37 | \u3042\u0903=11;// Unicode(0903) Mc 38 | \u3042\u0660=12;// Unicode(0903) Nd 39 | \u3042\u203f=13;// Unicode(203f) Pc 40 | EOS 41 | expect(js).to eq "あ゚=9;あः=10;あः=11;あ٠=12;あ‿=13;" 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/literal/numeric_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Literal' do 5 | describe 'Numeric' do 6 | it 'is numeric literal' do 7 | js = test_parse <<-EOS 8 | console.log(.123) 9 | console.log(.456e10) 10 | console.log(.456e+10) 11 | console.log(.789e-10) 12 | console.log(1.123) 13 | console.log(1.456e10) 14 | console.log(1.456e+10) 15 | console.log(1.789e-10) 16 | console.log(2.) 17 | console.log(2.e10) 18 | console.log(2e10) 19 | console.log(0xabc) 20 | console.log(0XABC) 21 | EOS 22 | expect(js).to eq "console.log(.123);console.log(.456e10);console.log(.456e10);console.log(.789e-10);console.log(1.123);console.log(1.456e10);console.log(1.456e10);console.log(1.789e-10);console.log(2);console.log(2e10);console.log(2e10);console.log(2748);console.log(2748);" 23 | end 24 | 25 | it 'is numeric literal which integer part is zero' do 26 | js = test_parse "a=0;b=0.1;c=0e1;d=0.2e-2;" 27 | expect(js).to eq "a=0;b=.1;c=0;d=.002;" 28 | end 29 | 30 | it 'is octal integer' do 31 | js = test_parse "a=017;" 32 | expect(js).to eq "a=15;" 33 | end 34 | 35 | it 'raise error' do 36 | expect { 37 | js = test_parse "a=018" 38 | }.to raise_error(Minjs::Lex::ParseError) 39 | end 40 | 41 | it 'raise error' do 42 | expect { 43 | js = test_parse "3.1415e" 44 | }.to raise_error(Minjs::Lex::ParseError) 45 | end 46 | 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/literal/object_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Literal' do 5 | describe 'Object' do 6 | it 'is object literal' do 7 | js = test_parse <<-EOS 8 | a={a:1, b:2, c:3} 9 | b={a:1, b:2, c:3,} 10 | c={"a":1, "b":2, "c":3} 11 | d={"a":1, "b":2, "c":3,} 12 | e={"":1} 13 | c={'a':1, 'b':2, 'c':3} 14 | d={'a':1, 'b':2, 'c':3,} 15 | e={'':1} 16 | f={あ:'a'} 17 | EOS 18 | expect(js).to eq "a={a:1,b:2,c:3};b={a:1,b:2,c:3};c={a:1,b:2,c:3};d={a:1,b:2,c:3};e={\"\":1};c={a:1,b:2,c:3};d={a:1,b:2,c:3};e={\"\":1};f={あ:\"a\"};" 19 | end 20 | 21 | it 'is object literal with numeric key' do 22 | js = test_parse <<-EOS 23 | g={365: "decimal", 24 | 0xff: "hex", 25 | 3.14: "float", 26 | 1e10: "exp", 27 | 123456789012345678901: "big e<=21", 28 | 1234567890123456789012: "big e>=21", 29 | 123.4567890123456789012: "e=0", 30 | 0.000001234567890123456789012: "e>=-6", 31 | 0.0000000001234567890123456789012: "e<=-6", 32 | 1e500: "+Inf", 33 | 1e-500: "-Inf", 34 | } 35 | console.log(g) 36 | EOS 37 | expect(js).to eq "g={365:\"decimal\",255:\"hex\",3.14:\"float\",10000000000:\"exp\",123456789012345680000:\"big e<=21\",1.2345678901234568e+21:\"big e>=21\",123.45678901234568:\"e=0\",0.0000012345678901234567:\"e>=-6\",1.2345678901234568e-10:\"e<=-6\",Infinity:\"+Inf\",0:\"-Inf\"};console.log(g);" 38 | 39 | end 40 | 41 | it 'is object literal' do 42 | js = test_parse <<-EOS 43 | h={if:'if',true:'true',null:'null'} 44 | console.log(h) 45 | EOS 46 | expect(js).to eq "h={if:\"if\",true:\"true\",null:\"null\"};console.log(h);" 47 | end 48 | 49 | it 'is object literal' do 50 | js = test_parse <<-EOS 51 | h={ 52 | a: 'a', 53 | a: 'b', 54 | a: 'c', 55 | } 56 | console.log(h) 57 | EOS 58 | expect(js).to eq "h={a:\"a\",a:\"b\",a:\"c\"};console.log(h);" 59 | end 60 | 61 | it 'is object literal with getter/setter' do 62 | js = test_parse <<-'EOS' 63 | h ={ 64 | get a(){return new Date()}, 65 | set a(v){val=v}, 66 | get: a,//get and set are not reserved word 67 | set: b 68 | } 69 | console.log(h.a) 70 | EOS 71 | expect(js).to eq "h={get a(){return new Date()},set a(v){val=v},get:a,set:b};console.log(h.a);" 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /spec/literal/regexp_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Literal' do 5 | describe 'Regexp' do 6 | it 'is regexp literal' do 7 | js = test_parse <<-'EOS' 8 | a=/abc/; 9 | b=/a\bc/; 10 | c=/a\/c/; 11 | d=/a[bc]d/; 12 | e=/a[\bc]d/; 13 | f=/a[\bc/]d/; 14 | a=/abc/g; 15 | b=/a\bc/g; 16 | c=/a\/c/g; 17 | d=/a[bc]d/g; 18 | e=/a[\bc]d/g; 19 | f=/a[\bc/]d/g; 20 | EOS 21 | expect(js).to eq "a=/abc/;b=/a\\bc/;c=/a\\/c/;d=/a[bc]d/;e=/a[\\bc]d/;f=/a[\\bc/]d/;a=/abc/g;b=/a\\bc/g;c=/a\\/c/g;d=/a[bc]d/g;e=/a[\\bc]d/g;f=/a[\\bc/]d/g;" 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/literal/string_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Literal' do 5 | describe 'String' do 6 | it 'is string literal' do 7 | js = test_parse <<-'EOS' 8 | a="" 9 | a="abc" 10 | a="a\nbc" 11 | a="a\u0042c" 12 | a="a\x42c" 13 | a="a\102c" 14 | a="あいうえお" 15 | EOS 16 | expect(js).to eq('a="";a="abc";a="a\nbc";a="aBc";a="aBc";a="aBc";a="あいうえお";') 17 | end 18 | 19 | it 'is string literal' do 20 | js = test_parse <<-'EOS' 21 | a='' 22 | a='abc' 23 | a='a\nbc' 24 | a='a\u0042c' 25 | a='a\x42c' 26 | a='a\102c' 27 | a='あいうえお' 28 | EOS 29 | expect(js).to eq('a="";a="abc";a="a\nbc";a="aBc";a="aBc";a="aBc";a="あいうえお";') 30 | end 31 | 32 | it 'handles octal literals correctly' do 33 | js = test_parse <<-'EOS' 34 | a = '\0' 35 | a = "\10" 36 | a = "\100" 37 | a = "\1000" 38 | EOS 39 | expect(js).to eq("a=\"\\0\";a=\"\\b\";a=\"@\";a=\"@0\";") 40 | end 41 | 42 | it 'handles literals correctly' do 43 | js = test_parse <<-'EOS' 44 | '\2459' //9 is source character 45 | '\412' //2 is source character 46 | '\128' //8 is source character 47 | EOS 48 | expect(js).to eq('"¥9";"!2";"\n8";') 49 | 50 | end 51 | 52 | it 'is line continuation' do 53 | js = test_parse <<-'EOS' 54 | a="a\ 55 | b" 56 | EOS 57 | expect(js).to eq("a=\"ab\";") 58 | end 59 | 60 | it 'prefer to single quote ' do 61 | js = test_parse <<-'EOS' 62 | a="\"\"\"\'" 63 | EOS 64 | expect(js).to eq("a='\"\"\"\\\'';") 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /spec/literal/white_space_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe 'Literal' do 5 | describe 'White space' do 6 | it 'is white space' do 7 | js = test_parse "a\u00a0=\u205f1;b\u3000=\t2;" 8 | expect(js).to eq ("a=1;b=2;") 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/minjs_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'spec_helper' 3 | 4 | describe Minjs do 5 | it 'has a version number' do 6 | expect(Minjs::VERSION).not_to be nil 7 | end 8 | end 9 | 10 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 2 | require 'minjs' 3 | require 'minjs/compressor/compressor' 4 | require 'logger' 5 | 6 | def test_compressor 7 | Minjs::Compressor::Compressor.new(:debug_level => Logger::WARN) 8 | end 9 | 10 | def test_parse(str) 11 | test_compressor.parse(str).to_js 12 | end 13 | -------------------------------------------------------------------------------- /spec/statement/block_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Statement' do 4 | describe 'Block' do 5 | it 'is empty block' do 6 | js = test_parse <<-EOS 7 | {} 8 | EOS 9 | expect(js).to eq "{}" 10 | end 11 | it 'is simple block' do 12 | js = test_parse <<-EOS 13 | { 14 | console.log('1'); 15 | console.log('2'); 16 | } 17 | EOS 18 | expect(js).to eq "{console.log(\"1\");console.log(\"2\")}" 19 | end 20 | it 'is block in block' do 21 | js = test_parse <<-EOS 22 | {{ 23 | console.log('1'); 24 | console.log('2'); 25 | }} 26 | EOS 27 | expect(js).to eq "{{console.log(\"1\");console.log(\"2\")}}" 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/statement/break_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Statement' do 4 | describe 'Break' do 5 | it 'is break statement' do 6 | js = test_parse <<-EOS 7 | i=0; 8 | while(i<10){ 9 | i+=1 10 | if(i>5) 11 | break; 12 | console.log(i) 13 | } 14 | EOS 15 | expect(js).to eq "i=0;while(i<10){i+=1;if(i>5)break;console.log(i)}" 16 | end 17 | 18 | it 'is break statement with label' do 19 | js = test_parse <<-EOS 20 | j=0 21 | undefined: 22 | while(j<10){ 23 | ++j; 24 | i=0; 25 | while(i<10){ 26 | ++i; 27 | if(i<5) 28 | break undefined; 29 | console.log(i, j) 30 | } 31 | } 32 | EOS 33 | expect(js).to eq "j=0;undefined:while(j<10){++j;i=0;while(i<10){++i;if(i<5)break undefined;console.log(i,j)}}" 34 | end 35 | 36 | it 'is break statement with automatic semicolon insertion' do 37 | js = test_parse <<-EOS 38 | j=0; 39 | undefined: 40 | while(j<10){ 41 | ++j; 42 | i=0; 43 | while(i<10){ 44 | ++i; 45 | if(i<5) 46 | break 47 | undefined;//ignored 48 | console.log(j) 49 | } 50 | } 51 | EOS 52 | expect(js).to eq "j=0;undefined:while(j<10){++j;i=0;while(i<10){++i;if(i<5)break;undefined;console.log(j)}}" 53 | end 54 | 55 | it 'cause syntax error' do 56 | expect { 57 | js = test_parse <<-EOS 58 | break 0//0 is not identifier 59 | EOS 60 | }.to raise_error(Minjs::Lex::ParseError) 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /spec/statement/continue_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Statement' do 4 | describe 'Continue' do 5 | it 'is continue statement' do 6 | js = test_parse <<-EOS 7 | i=0; 8 | while(i<10){ 9 | i+=1 10 | if(i>5) 11 | continue; 12 | console.log(i) 13 | } 14 | EOS 15 | expect(js).to eq "i=0;while(i<10){i+=1;if(i>5)continue;console.log(i)}" 16 | end 17 | 18 | it 'is continue statement with label' do 19 | js = test_parse <<-EOS 20 | j=0 21 | undefined: 22 | while(j<10){ 23 | ++j; 24 | i=0; 25 | while(i<10){ 26 | ++i; 27 | if(i<5) 28 | continue undefined; 29 | console.log(i, j) 30 | } 31 | } 32 | EOS 33 | expect(js).to eq "j=0;undefined:while(j<10){++j;i=0;while(i<10){++i;if(i<5)continue undefined;console.log(i,j)}}" 34 | end 35 | 36 | it 'is continue statement with automatic semicolon insertion' do 37 | js = test_parse <<-EOS 38 | j=0; 39 | undefined: 40 | while(j<10){ 41 | ++j; 42 | i=0; 43 | while(i<10){ 44 | ++i; 45 | if(i<5) 46 | continue 47 | undefined;//ignored 48 | console.log(j) 49 | } 50 | } 51 | EOS 52 | expect(js).to eq "j=0;undefined:while(j<10){++j;i=0;while(i<10){++i;if(i<5)continue;undefined;console.log(j)}}" 53 | end 54 | it 'cause syntax error' do 55 | expect { 56 | js = test_parse <<-EOS 57 | continue this//this is not identifier 58 | EOS 59 | }.to raise_error(Minjs::Lex::ParseError) 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/statement/debugger_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Statement' do 4 | describe 'Debugger' do 5 | it 'is debugger statement' do 6 | js = test_parse <<-EOS 7 | debugger; 8 | EOS 9 | expect(js).to eq "debugger;" 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/statement/empty_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Statement' do 4 | describe 'Empty' do 5 | it 'is empty statement' do 6 | js = test_parse <<-EOS 7 | ; 8 | EOS 9 | expect(js).to eq "" 10 | end 11 | it 'is empty statement' do 12 | js = test_parse <<-EOS 13 | while(a) 14 | if(a) 15 | ; 16 | else 17 | ; 18 | EOS 19 | expect(js).to eq "while(a)if(a);else;" 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/statement/if_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Statement' do 4 | describe 'If' do 5 | it 'is if-then statement' do 6 | js = test_parse <<-EOS 7 | if(true) 8 | console.log(true) 9 | EOS 10 | expect(js).to eq "if(true)console.log(true);" 11 | end 12 | it 'is if-then-block statement' do 13 | js = test_parse <<-EOS 14 | if(true){ 15 | console.log(true) 16 | } 17 | EOS 18 | expect(js).to eq "if(true){console.log(true)}" 19 | end 20 | it 'is if-then-else statement' do 21 | js = test_parse <<-EOS 22 | if(false) 23 | console.log(true) 24 | else 25 | console.log(false) 26 | EOS 27 | expect(js).to eq "if(false)console.log(true);else console.log(false);" 28 | end 29 | it 'is if-then-else-block statement' do 30 | js = test_parse <<-EOS 31 | if(false) 32 | console.log(true) 33 | else{ 34 | console.log(false) 35 | } 36 | EOS 37 | expect(js).to eq "if(false)console.log(true);else{console.log(false)}" 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/statement/iteration_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Statement' do 4 | describe 'Iteration' do 5 | it 'is do-while statement' do 6 | js = test_parse <<-EOS 7 | i=0 8 | do{ 9 | console.log(i) 10 | }while(++i<10); 11 | EOS 12 | expect(js).to eq "i=0;do console.log(i);while(++i<10);" 13 | end 14 | 15 | it 'is while statement' do 16 | js = test_parse <<-EOS 17 | i=0 18 | while(++i<10){ 19 | console.log(i) 20 | } 21 | EOS 22 | expect(js).to eq "i=0;while(++i<10)console.log(i);" 23 | end 24 | 25 | it 'is for statement' do 26 | js = test_parse <<-EOS 27 | for(i=0;i<10;++i){ 28 | console.log(i) 29 | } 30 | for(;i<10;++i){ 31 | console.log(i) 32 | } 33 | for(i=0;;++i){ 34 | if(i<10) 35 | break; 36 | console.log(i) 37 | } 38 | for(i=0;i<10;){ 39 | console.log(i) 40 | ++i 41 | } 42 | EOS 43 | expect(js).to eq "for(i=0;i<10;++i)console.log(i);for(;i<10;++i)console.log(i);for(i=0;;++i){if(i<10)break;console.log(i)}for(i=0;i<10;){console.log(i);++i}" 44 | end 45 | 46 | it 'is for_var statement' do 47 | js = test_parse <<-EOS 48 | for(var i=0;i<10;++i){ 49 | console.log(i) 50 | } 51 | for(var i=0;;++i){ 52 | if(i<10) 53 | break; 54 | console.log(i) 55 | } 56 | for(var i=0,j=0;i<10;){ 57 | console.log(i) 58 | ++i 59 | } 60 | EOS 61 | expect(js).to eq "for(var i=0;i<10;++i)console.log(i);for(var i=0;;++i){if(i<10)break;console.log(i)}for(var i=0,j=0;i<10;){console.log(i);++i}" 62 | end 63 | 64 | it 'is for-in statement' do 65 | js = test_parse <<-EOS 66 | for(i in [1,2,3]){ 67 | console.log(i) 68 | } 69 | EOS 70 | expect(js).to eq "for(i in[1,2,3])console.log(i);" 71 | end 72 | 73 | it 'is for var-in statement' do 74 | js = test_parse <<-EOS 75 | for(var i in [1,2,3]){ 76 | console.log(i) 77 | } 78 | EOS 79 | expect(js).to eq "for(var i in[1,2,3])console.log(i);" 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /spec/statement/labelled_statement_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Statement' do 4 | describe 'Labelled' do 5 | it 'is labelled statement' do 6 | js = test_parse 'aaa:while(true);' 7 | expect(js).to eq "aaa:while(true);" 8 | end 9 | 10 | it 'cause syntax error' do 11 | expect { 12 | js = test_parse <<-'EOS' 13 | this://this is reserved word 14 | while(true); 15 | EOS 16 | }.to raise_error(Minjs::Lex::ParseError) 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/statement/return_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Statement' do 4 | describe 'Return' do 5 | it 'is return statement' do 6 | js = test_parse <<-EOS 7 | function a(){ 8 | return 9 | } 10 | EOS 11 | expect(js).to eq "function a(){return}" 12 | end 13 | 14 | it 'is return statement with value' do 15 | js = test_parse <<-EOS 16 | function a(){ 17 | return 1 18 | } 19 | EOS 20 | expect(js).to eq "function a(){return 1}" 21 | end 22 | 23 | it 'is return statement without automatic semicolon insertion' do 24 | js = test_parse <<-EOS 25 | function a(){ 26 | return 1 27 | +2 28 | } 29 | EOS 30 | expect(js).to eq "function a(){return 1+2}" 31 | end 32 | 33 | it 'is return statement with automatic semicolon insertion' do 34 | js = test_parse <<-EOS 35 | function a(){ 36 | return 37 | 1+2 38 | } 39 | EOS 40 | expect(js).to eq "function a(){return;1+2}" 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/statement/switch_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Statement' do 4 | describe 'Switch' do 5 | it 'is switch statement' do 6 | js = test_parse <<-EOS 7 | $=0; 8 | switch($) 9 | { 10 | case 0: 11 | console.log(0); 12 | break; 13 | 14 | case 1: 15 | console.log(1); 16 | break; 17 | 18 | case 2: 19 | default: 20 | console.log("default"); 21 | break; 22 | } 23 | EOS 24 | expect(js).to eq "$=0;switch($){case 0:console.log(0);break;case 1:console.log(1);break;case 2:default:console.log(\"default\");break}"; 25 | end 26 | 27 | it 'is switch statement' do 28 | js = test_parse <<-EOS 29 | switch($) 30 | { 31 | case 0: 32 | default: 33 | case 1: 34 | } 35 | EOS 36 | expect(js).to eq "switch($){case 0:default:case 1:}" 37 | end 38 | 39 | it 'is empty switch statement' do 40 | js = test_parse <<-EOS 41 | switch($) 42 | { 43 | } 44 | EOS 45 | expect(js).to eq "switch($){}" 46 | end 47 | 48 | it 'raise exception' do 49 | expect { 50 | js = test_parse <<-EOS 51 | switch($)// {} are required 52 | ; 53 | EOS 54 | }.to raise_error(Minjs::Lex::ParseError) 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /spec/statement/throw_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Statement' do 4 | describe 'Throw' do 5 | it 'is throw statement' do 6 | js = test_parse <<-EOS 7 | try{ 8 | throw 'a' 9 | } 10 | catch(e){ 11 | console.log(e) 12 | } 13 | EOS 14 | expect(js).to eq "try{throw\"a\"}catch(e){console.log(e)}" 15 | end 16 | 17 | it 'cause syntax error' do 18 | expect { 19 | js = test_parse <<-EOS 20 | throw 21 | EOS 22 | }.to raise_error(Minjs::Lex::ParseError) 23 | end 24 | 25 | it 'cause syntax error' do 26 | expect { 27 | js = test_parse <<-EOS 28 | throw//no line terminator here 29 | a 30 | EOS 31 | }.to raise_error(Minjs::Lex::ParseError) 32 | end 33 | 34 | it 'cause syntax error' do 35 | expect { 36 | js = test_parse <<-EOS 37 | throw 1+;// bad expression 38 | EOS 39 | }.to raise_error(Minjs::Lex::ParseError) 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/statement/try_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Statement' do 4 | describe 'Try' do 5 | it 'is try-catch statement' do 6 | js = test_parse <<-EOS 7 | try{ 8 | throw 'a' 9 | } 10 | catch(e){ 11 | console.log(e) 12 | } 13 | EOS 14 | expect(js).to eq "try{throw\"a\"}catch(e){console.log(e)}" 15 | end 16 | 17 | it 'is try-finally statement' do 18 | js = test_parse <<-EOS 19 | try{ 20 | try{ 21 | throw "a" 22 | } 23 | finally{ 24 | console.log("f") 25 | } 26 | } 27 | catch(e){ 28 | } 29 | EOS 30 | expect(js).to eq "try{try{throw\"a\"}finally{console.log(\"f\")}}catch(e){}" 31 | end 32 | 33 | it 'is try-catch-finally statement' do 34 | js = test_parse <<-EOS 35 | try{ 36 | throw 'a'; 37 | var a=1; 38 | } 39 | catch(e){ 40 | console.log(e) 41 | } 42 | finally{ 43 | console.log(a)//undefined 44 | } 45 | EOS 46 | expect(js).to eq "try{throw\"a\";var a=1}catch(e){console.log(e)}finally{console.log(a)}" 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/statement/var_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Statement' do 4 | describe 'Var' do 5 | it 'is var statement' do 6 | js = test_parse <<-EOS 7 | var a; 8 | var b=1; 9 | var c,d=2,e; 10 | console.log(a,b,c,d,e) 11 | EOS 12 | expect(js).to eq "var a;var b=1;var c,d=2,e;console.log(a,b,c,d,e);" 13 | end 14 | 15 | it 'is raise exception' do 16 | expect { 17 | js = test_parse "var x=" 18 | }.to raise_error(Minjs::Lex::ParseError) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/statement/with_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Statement' do 4 | describe 'With' do 5 | it 'is with statement' do 6 | js = test_parse <<-EOS 7 | o={a:1}; 8 | with(o){ 9 | console.log(a) 10 | a=2; 11 | console.log(a) 12 | } 13 | console.log(o)// => {a:2} 14 | EOS 15 | expect(js).to eq "o={a:1};with(o){console.log(a);a=2;console.log(a)}console.log(o);" 16 | end 17 | end 18 | end 19 | --------------------------------------------------------------------------------