From 558715698b7a24a438f286881d93e1f70d8ab985 Mon Sep 17 00:00:00 2001 From: Ramb Memburg <46289413+memburg@users.noreply.github.com> Date: Fri, 12 Jun 2026 15:27:21 -0400 Subject: [PATCH] Code quality improvements: style, performance, and documentation Style fixes: - switch_statement_parser: while true -> loop do - function_runtime: remove duplicate FUNCTION_NAME_REGEX, use .try(&.parameters.size) - interpreter: fix constant spacing, condense truthy?/strict_equals_values? guard clauses - expression_evaluator: condense truthy?/strict_equality_result guard clauses - giavascript_cli: break ternary into named variable, fix .ls -> .js in usage Performance improvements: - interpreter: single-pass String.build for escape_string (replaces chained .gsub) - runtime_types: Schwartzian transform for array_sort to avoid O(n log n) string conversions - runtime_types: in-place array_splice and array_unshift to reduce allocations - interpreter: bail-early in split_assignment_statement when no '=' present - interpreter: cache FallbackRawStatement results - interpreter: use String.build for value_to_s array/hash serialization - expression_parser: use Set for parameter duplicate detection (O(n) instead of O(n^2)) Documentation: - README: fix broken logo fallback, update examples list, improve CLI section - Language.md: document arrow functions, typeof semantics, unary plus, comments, semicolons, console.log - Math.md: add edge case notes for Math.max()/Math.min() - Types.md: fix grammar - CONTRIBUTING.md: clarify CI verification and improve wording - Add CHANGELOG.md, PR template, and issue templates Closes #40 --- .github/ISSUE_TEMPLATE/bug_report.md | 35 +++++++ .github/ISSUE_TEMPLATE/feature_request.md | 23 ++++ .github/PULL_REQUEST_TEMPLATE.md | 11 ++ CHANGELOG.md | 22 ++++ CONTRIBUTING.md | 4 +- README.md | 11 +- reference/Language.md | 22 +++- reference/Math.md | 2 + reference/REFERENCE.md | 26 ++++- reference/Types.md | 2 +- src/giavascript/expression_evaluator.cr | 66 +++--------- src/giavascript/expression_parser.cr | 6 +- src/giavascript/function_runtime.cr | 10 +- src/giavascript/interpreter.cr | 116 +++++++++------------ src/giavascript/runtime_types.cr | 55 ++++------ src/giavascript/switch_statement_parser.cr | 2 +- src/giavascript_cli.cr | 5 +- 17 files changed, 236 insertions(+), 182 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 CHANGELOG.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..db20938 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,35 @@ +--- +name: Bug report +about: Report a problem with GiavaScript +title: '' +labels: bug +assignees: '' +--- + +### Description + + + +### Steps to reproduce + + + +### Expected behavior + + + +### Actual behavior + + + +### Environment + +- GiavaScript version: +- Crystal version: +- OS: diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..b1d4db6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,23 @@ +--- +name: Feature request +about: Suggest a new feature for GiavaScript +title: '' +labels: enhancement +assignees: '' +--- + +### Feature description + + + +### Use case + + + +### Examples + + + +### Additional context + + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..0ac2ed4 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,11 @@ +### Summary + + + +### Checklist + +- [ ] Changes are focused and scoped +- [ ] Docs updated for any user-facing behavior changes +- [ ] `crystal spec` passes +- [ ] `reference/REFERENCE.md` regenerated when reference docs changed (`python3 scripts/generate_reference.py`) +- [ ] PR description includes a short summary of behavior changes diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b0d1cb6 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,22 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.1.0] - 2025-06-12 + +### Added +- JavaScript runtime implemented in Crystal +- Tokenizer and expression parser +- Statement parsing for if, for, while, do...while, switch, try/catch/finally +- Function declarations, expressions, and calls +- Arrow function support +- Template literal support +- Built-in types: String, Array, Object, Number, Bool, Date, Math, JSON +- `console.log` built-in global function +- `typeof` and `void` operators +- `parseInt`, `parseFloat`, `isNaN` global functions +- REPL mode with `:quit` command +- File execution mode diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f1cf86f..347d775 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,9 +47,7 @@ If you change any reference source file in `reference/` (`Language.md`, `Types.m python3 scripts/generate_reference.py ``` -Before opening a pull request, make sure this file is clean in `git diff` unless your change intentionally updates it: - -- `reference/REFERENCE.md` +Before opening a pull request, make sure `reference/REFERENCE.md` shows no unintended changes in `git diff`. If your PR intentionally updates the reference docs, run the generator first. CI checks that this file matches the generator output; a mismatch will fail the build. ## Pull request checklist diff --git a/README.md b/README.md index fb786b1..92cee41 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ - GiavaScript Logo + GiavaScript Logo

GiavaScript

@@ -114,8 +114,13 @@ CI verifies that `reference/REFERENCE.md` matches generated output. Sample programs are in `examples/`: - `examples/templateLiterals.js` - string interpolation and expression formatting -- `examples/matrixMultiply.js` - nested loops and array indexing -- `examples/sievePrimes.js` - control flow and simple algorithm implementation +- `examples/arrayFlatFlatMapSplice.js` - array manipulation methods +- `examples/dateBasics.js` - Date object usage +- `examples/functionExpressionsAndOperators.js` - function expressions and operators +- `examples/multilineMethodChaining.js` - method chaining patterns +- `examples/objectKeysValuesEntries.js` - Object.keys, values, entries +- `examples/random.js` - random number generation +- `examples/tryCatchFinally.js` - error handling with try/catch/finally Run any example with: diff --git a/reference/Language.md b/reference/Language.md index 5188533..979373d 100644 --- a/reference/Language.md +++ b/reference/Language.md @@ -29,12 +29,26 @@ Status of core JavaScript language features in GiavaScript. | Logical operators (`&&`, `\|\|`, `!`) | Available | | `typeof` operator | Available | | `void` operator | Available | +| Unary plus (`+`) | Available | +| Comments (`//`, `/* */`) | Available | +| Template literals | Available | ### Equality operator semantics - `==` and `!=` use coercive (loose) equality behavior. - `===` and `!==` use strict (non-coercive) equality behavior. +### `typeof` semantics + +`typeof` returns string representations of value types: + +- `"number"` for `Int32` and `Float64` +- `"string"` for `String` +- `"boolean"` for `Bool` +- `"object"` for `Array`, `Hash`, and `null` +- `"function"` for callable values (user-defined and built-in) +- `"undefined"` for `undefined` and undeclared identifiers (does not throw) + ### Logical operator semantics - `a && b`: evaluates `a` first; if `a` is falsy, returns `a` and does not evaluate `b`; otherwise evaluates and returns `b`. @@ -48,11 +62,13 @@ Status of core JavaScript language features in GiavaScript. | --- | --- | | Function declarations (`function name(...) { ... }`) | Available | | Function expressions (`var f = function(...) { ... }`) | Available | +| Named function expressions (`var f = function name(...) { ... }`) | Available | +| Arrow functions (`() => expr`, `x => expr`, `() => { ... }`) | Available | | Function calls | Available | | Returning values with `return` | Available | | First-class function values | Available | | `if`, `else if`, `else` | Available | -| `for (...)` loops | Available | +| C-style `for` loops (`for (init; condition; update)`) | Available | | `break` / `continue` inside loops | Available | | `while` / `do...while` loops | Available | | `switch` statements | Available | @@ -79,9 +95,11 @@ Status of core JavaScript language features in GiavaScript. | `isNaN()` | Available | | `Date.now()` | Available | | `new Date()` | Available | +| `console.log()` | Available | ## Notes -- This reflects current behavior in the interpreter and specs. +- This reflects the current behavior in the interpreter and specs. - `let` and `const` declarations return explicit errors: `Error: unsupported declaration 'let'` and `Error: unsupported declaration 'const'`. - Use `var` for variable declarations. +- Statements can be separated by newlines without requiring semicolons. A semicolon is not required when two statements are on separate lines. diff --git a/reference/Math.md b/reference/Math.md index 758bfaf..343df6a 100644 --- a/reference/Math.md +++ b/reference/Math.md @@ -54,3 +54,5 @@ Status of the JavaScript `Math` global object in GiavaScript. - `Math.random()` returns a pseudo-random number in the range `[0, 1)`. - `Math.random()` is not cryptographically secure. +- `Math.max()` with zero arguments returns `-Infinity`. `Math.min()` with zero arguments returns `Infinity`. +- All Math methods that accept numeric arguments coerce non-numeric values to numbers. Invalid coercions produce `NaN` or `Infinity` as appropriate. diff --git a/reference/REFERENCE.md b/reference/REFERENCE.md index 4f5dbee..d3fd20b 100644 --- a/reference/REFERENCE.md +++ b/reference/REFERENCE.md @@ -53,12 +53,26 @@ Status of core JavaScript language features in GiavaScript. | Logical operators (`&&`, `\|\|`, `!`) | Available | | `typeof` operator | Available | | `void` operator | Available | +| Unary plus (`+`) | Available | +| Comments (`//`, `/* */`) | Available | +| Template literals | Available | #### Equality operator semantics - `==` and `!=` use coercive (loose) equality behavior. - `===` and `!==` use strict (non-coercive) equality behavior. +#### `typeof` semantics + +`typeof` returns string representations of value types: + +- `"number"` for `Int32` and `Float64` +- `"string"` for `String` +- `"boolean"` for `Bool` +- `"object"` for `Array`, `Hash`, and `null` +- `"function"` for callable values (user-defined and built-in) +- `"undefined"` for `undefined` and undeclared identifiers (does not throw) + #### Logical operator semantics - `a && b`: evaluates `a` first; if `a` is falsy, returns `a` and does not evaluate `b`; otherwise evaluates and returns `b`. @@ -72,11 +86,13 @@ Status of core JavaScript language features in GiavaScript. | --- | --- | | Function declarations (`function name(...) { ... }`) | Available | | Function expressions (`var f = function(...) { ... }`) | Available | +| Named function expressions (`var f = function name(...) { ... }`) | Available | +| Arrow functions (`() => expr`, `x => expr`, `() => { ... }`) | Available | | Function calls | Available | | Returning values with `return` | Available | | First-class function values | Available | | `if`, `else if`, `else` | Available | -| `for (...)` loops | Available | +| C-style `for` loops (`for (init; condition; update)`) | Available | | `break` / `continue` inside loops | Available | | `while` / `do...while` loops | Available | | `switch` statements | Available | @@ -103,12 +119,14 @@ Status of core JavaScript language features in GiavaScript. | `isNaN()` | Available | | `Date.now()` | Available | | `new Date()` | Available | +| `console.log()` | Available | ### Notes -- This reflects current behavior in the interpreter and specs. +- This reflects the current behavior in the interpreter and specs. - `let` and `const` declarations return explicit errors: `Error: unsupported declaration 'let'` and `Error: unsupported declaration 'const'`. - Use `var` for variable declarations. +- Statements can be separated by newlines without requiring semicolons. A semicolon is not required when two statements are on separate lines. ## Type Methods and Properties @@ -246,7 +264,7 @@ Status of built-in methods and properties on GiavaScript runtime types. ### Notes -- This reflects current behavior in the interpreter and specs. +- This reflects the current behavior in the interpreter and specs. ## Math @@ -304,6 +322,8 @@ Status of the JavaScript `Math` global object in GiavaScript. - `Math.random()` returns a pseudo-random number in the range `[0, 1)`. - `Math.random()` is not cryptographically secure. +- `Math.max()` with zero arguments returns `-Infinity`. `Math.min()` with zero arguments returns `Infinity`. +- All Math methods that accept numeric arguments coerce non-numeric values to numbers. Invalid coercions produce `NaN` or `Infinity` as appropriate. ## JSON diff --git a/reference/Types.md b/reference/Types.md index 35191d7..fc3a657 100644 --- a/reference/Types.md +++ b/reference/Types.md @@ -134,4 +134,4 @@ Status of built-in methods and properties on GiavaScript runtime types. ## Notes -- This reflects current behavior in the interpreter and specs. +- This reflects the current behavior in the interpreter and specs. diff --git a/src/giavascript/expression_evaluator.cr b/src/giavascript/expression_evaluator.cr index 611d7e7..3b616db 100644 --- a/src/giavascript/expression_evaluator.cr +++ b/src/giavascript/expression_evaluator.cr @@ -491,44 +491,18 @@ module GiavaScript private def strict_equality_result(left : Value, right : Value) : Bool if left.is_a?(Int32) || left.is_a?(Float64) return false unless right.is_a?(Int32) || right.is_a?(Float64) - - left_number = left.to_f64 - right_number = right.to_f64 - return false if left_number.nan? || right_number.nan? - return left_number == right_number - end - - if left.is_a?(String) - return right.is_a?(String) && left == right - end - - if left.is_a?(Bool) - return right.is_a?(Bool) && left == right - end - - if left.nil? - return right.nil? + return false if left.to_f64.nan? || right.to_f64.nan? + return left.to_f64 == right.to_f64 end - if left.is_a?(UndefinedValue) - return right.is_a?(UndefinedValue) - end - - if left.is_a?(Array(Value)) - return right.is_a?(Array(Value)) && left.object_id == right.object_id - end - - if left.is_a?(Hash(String, Value)) - return right.is_a?(Hash(String, Value)) && left.object_id == right.object_id - end - - if left.is_a?(BuiltinFunction) - return right.is_a?(BuiltinFunction) && left.object_id == right.object_id - end - - if left.is_a?(UserFunction) - return right.is_a?(UserFunction) && left.object_id == right.object_id - end + return right.is_a?(String) && left == right if left.is_a?(String) + return right.is_a?(Bool) && left == right if left.is_a?(Bool) + return right.nil? if left.nil? + return right.is_a?(UndefinedValue) if left.is_a?(UndefinedValue) + return right.is_a?(Array(Value)) && left.object_id == right.object_id if left.is_a?(Array(Value)) + return right.is_a?(Hash(String, Value)) && left.object_id == right.object_id if left.is_a?(Hash(String, Value)) + return right.is_a?(BuiltinFunction) && left.object_id == right.object_id if left.is_a?(BuiltinFunction) + return right.is_a?(UserFunction) && left.object_id == right.object_id if left.is_a?(UserFunction) false end @@ -618,22 +592,10 @@ module GiavaScript private def truthy?(value : Value) : Bool return false if value.nil? return false if value.is_a?(UndefinedValue) - - if value.is_a?(Bool) - return value - end - - if value.is_a?(String) - return !value.empty? - end - - if value.is_a?(Int32) - return value != 0 - end - - if value.is_a?(Float64) - return value != 0.0 - end + return value if value.is_a?(Bool) + return !value.empty? if value.is_a?(String) + return value != 0 if value.is_a?(Int32) + return value != 0.0 if value.is_a?(Float64) true end diff --git a/src/giavascript/expression_parser.cr b/src/giavascript/expression_parser.cr index 5c16214..2555009 100644 --- a/src/giavascript/expression_parser.cr +++ b/src/giavascript/expression_parser.cr @@ -322,11 +322,12 @@ module GiavaScript advance_token parameters = [] of String + param_set = Set(String).new unless @current.kind == Tokenizer::TokenKind::RParen loop do raise invalid_rhs_error unless @current.kind == Tokenizer::TokenKind::Identifier parameter = @current.lexeme - raise invalid_rhs_error if parameters.includes?(parameter) + raise invalid_rhs_error unless param_set.add?(parameter) parameters << parameter advance_token @@ -362,6 +363,7 @@ module GiavaScript advance_token parameters = [] of String + param_set = Set(String).new if @current.kind == Tokenizer::TokenKind::RParen advance_token @@ -372,7 +374,7 @@ module GiavaScript elsif @current.kind == Tokenizer::TokenKind::Identifier loop do param = @current.lexeme - return restore_and_nil(saved_cursor, saved_token) if parameters.includes?(param) + return restore_and_nil(saved_cursor, saved_token) unless param_set.add?(param) parameters << param advance_token diff --git a/src/giavascript/function_runtime.cr b/src/giavascript/function_runtime.cr index 9d83437..8fa5fd5 100644 --- a/src/giavascript/function_runtime.cr +++ b/src/giavascript/function_runtime.cr @@ -1,7 +1,6 @@ module GiavaScript class FunctionRuntime - IDENTIFIER_REGEX = /^[A-Za-z_][A-Za-z0-9_]*$/ - FUNCTION_NAME_REGEX = /^[A-Za-z_][A-Za-z0-9_]*$/ + IDENTIFIER_REGEX = /^[A-Za-z_][A-Za-z0-9_]*$/ record FunctionDefinition, parameters : Array(String), statements : Array(String) @@ -25,7 +24,7 @@ module GiavaScript param_list = match[2].strip body = match[3].strip - raise ExpressionError.new("Error: invalid function name '#{function_name}'") unless function_name.matches?(FUNCTION_NAME_REGEX) + raise ExpressionError.new("Error: invalid function name '#{function_name}'") unless function_name.matches?(IDENTIFIER_REGEX) parameters = parse_function_parameters(param_list) statements = StatementSplitter.new(body).split @@ -37,10 +36,7 @@ module GiavaScript end def function_parameter_count(name : String) : Int32? - function = @functions[name]? - return nil unless function - - function.parameters.size + @functions[name]?.try(&.parameters.size) end def invoke_function(name : String, args : Array(Value), outer_env : Environment, &evaluate_statement : String, Environment, Bool, Bool -> String?) : Value diff --git a/src/giavascript/interpreter.cr b/src/giavascript/interpreter.cr index a95e84d..f647c15 100644 --- a/src/giavascript/interpreter.cr +++ b/src/giavascript/interpreter.cr @@ -3,7 +3,7 @@ module GiavaScript include InterpreterBuiltins IDENTIFIER_REGEX = /^[A-Za-z_][A-Za-z0-9_]*$/ - MAX_JSON_STRINGIFY_DEPTH = 1000 + MAX_JSON_STRINGIFY_DEPTH = 1_000 MAX_EXPRESSION_CACHE_SIZE = 8_192 MAX_RAW_STATEMENT_CACHE_SIZE = 8_192 MAX_EVALUATOR_CACHE_SIZE = 1_024 @@ -411,6 +411,8 @@ module GiavaScript end private def split_assignment_statement(stmt : String) : NamedTuple(lhs: String, rhs: String, operator: String)? + return nil unless stmt.includes?('=') + current = 0 string_delimiter = nil.as(Char?) escaping = false @@ -876,10 +878,8 @@ module GiavaScript end end - unless compiled.is_a?(FallbackRawStatement) - @raw_statement_cache.clear if @raw_statement_cache.size >= MAX_RAW_STATEMENT_CACHE_SIZE - @raw_statement_cache[key] = compiled - end + @raw_statement_cache.clear if @raw_statement_cache.size >= MAX_RAW_STATEMENT_CACHE_SIZE + @raw_statement_cache[key] = compiled compiled end @@ -887,22 +887,10 @@ module GiavaScript private def truthy?(value : Value) : Bool return false if value.nil? return false if value.is_a?(UndefinedValue) - - if value.is_a?(Bool) - return value - end - - if value.is_a?(String) - return !value.empty? - end - - if value.is_a?(Int32) - return value != 0 - end - - if value.is_a?(Float64) - return value != 0.0 - end + return value if value.is_a?(Bool) + return !value.empty? if value.is_a?(String) + return value != 0 if value.is_a?(Int32) + return value != 0.0 if value.is_a?(Float64) true end @@ -910,48 +898,19 @@ module GiavaScript private def strict_equals_values?(left : Value, right : Value) : Bool if left.is_a?(Int32) || left.is_a?(Float64) return false unless right.is_a?(Int32) || right.is_a?(Float64) - - left_number = left.to_f64 - right_number = right.to_f64 - return false if left_number.nan? || right_number.nan? - return left_number == right_number - end - - if left.is_a?(String) - return right.is_a?(String) && left == right - end - - if left.is_a?(Bool) - return right.is_a?(Bool) && left == right - end - - if left.nil? - return right.nil? - end - - if left.is_a?(UndefinedValue) - return right.is_a?(UndefinedValue) + return false if left.to_f64.nan? || right.to_f64.nan? + return left.to_f64 == right.to_f64 end - if left.is_a?(Array(Value)) - return right.is_a?(Array(Value)) && left.object_id == right.object_id - end - - if left.is_a?(Hash(String, Value)) - return right.is_a?(Hash(String, Value)) && left.object_id == right.object_id - end - - if left.is_a?(BuiltinFunction) - return right.is_a?(BuiltinFunction) && left.object_id == right.object_id - end - - if left.is_a?(UserFunction) - return right.is_a?(UserFunction) && left.object_id == right.object_id - end - - if left.is_a?(DateValue) - return right.is_a?(DateValue) && left.object_id == right.object_id - end + return right.is_a?(String) && left == right if left.is_a?(String) + return right.is_a?(Bool) && left == right if left.is_a?(Bool) + return right.nil? if left.nil? + return right.is_a?(UndefinedValue) if left.is_a?(UndefinedValue) + return right.is_a?(Array(Value)) && left.object_id == right.object_id if left.is_a?(Array(Value)) + return right.is_a?(Hash(String, Value)) && left.object_id == right.object_id if left.is_a?(Hash(String, Value)) + return right.is_a?(BuiltinFunction) && left.object_id == right.object_id if left.is_a?(BuiltinFunction) + return right.is_a?(UserFunction) && left.object_id == right.object_id if left.is_a?(UserFunction) + return right.is_a?(DateValue) && left.object_id == right.object_id if left.is_a?(DateValue) false end @@ -1015,22 +974,41 @@ module GiavaScript if value.is_a?(String) "\"#{escape_string(value)}\"" elsif value.is_a?(Array) - "[#{value.map { |item| value_to_s(item) }.join(", ")}]" + String.build do |io| + io << '[' + value.each_with_index do |item, i| + io << ", " if i > 0 + io << value_to_s(item) + end + io << ']' + end elsif value.is_a?(Hash(String, Value)) - properties = value.map do |key, property_value| - "\"#{escape_string(key)}\": #{value_to_s(property_value)}" + String.build do |io| + io << '{' + first = true + value.each do |key, property_value| + first ? (first = false) : (io << ", ") + io << '"' << escape_string(key) << "\": " << value_to_s(property_value) + end + io << '}' end - "{#{properties.join(", ")}}" else value.to_s end end private def escape_string(value : String) : String - value.gsub('\\', "\\\\") - .gsub('"', "\\\"") - .gsub('\n', "\\n") - .gsub('\t', "\\t") + String.build do |io| + value.each_char do |char| + case char + when '\\' then io << "\\\\" + when '"' then io << "\\\"" + when '\n' then io << "\\n" + when '\t' then io << "\\t" + else io << char + end + end + end end private def json_any_to_value(value : ::JSON::Any) : Value diff --git a/src/giavascript/runtime_types.cr b/src/giavascript/runtime_types.cr index 6ab6891..5c455df 100644 --- a/src/giavascript/runtime_types.cr +++ b/src/giavascript/runtime_types.cr @@ -920,7 +920,19 @@ module GiavaScript private def array_sort(receiver : Value, args : Array(Value)) : Value assert_arity(args, 0, "Array.sort") array_receiver = receiver_array(receiver, "Array.sort") - array_receiver.sort! { |left, right| runtime_to_string(left) <=> runtime_to_string(right) } + return array_receiver if array_receiver.size <= 1 + + keys = Array(String).new(array_receiver.size) + array_receiver.each { |v| keys << runtime_to_string(v) } + + indices = (0...array_receiver.size).to_a + indices.sort! { |a, b| keys[a] <=> keys[b] } + + sorted = Array(Value).new(array_receiver.size) + indices.each { |i| sorted << array_receiver[i] } + + array_receiver.clear + sorted.each { |v| array_receiver << v } array_receiver end @@ -939,38 +951,12 @@ module GiavaScript size - start end - removed = Array(Value).new(delete_count) - index = 0 - while index < delete_count - removed << array_receiver[start + index] - index += 1 - end + removed = array_receiver[start, delete_count] - replacement = [] of Value - index = 2 - while index < args.size - replacement << args[index] - index += 1 - end + replacement = Array(Value).new(Math.max(0, args.size - 2)) + (2...args.size).each { |i| replacement << args[i] } - rebuilt = Array(Value).new(size - delete_count + replacement.size) - - index = 0 - while index < start - rebuilt << array_receiver[index] - index += 1 - end - - replacement.each { |value| rebuilt << value } - - tail_index = start + delete_count - while tail_index < size - rebuilt << array_receiver[tail_index] - tail_index += 1 - end - - array_receiver.clear - rebuilt.each { |value| array_receiver << value } + array_receiver[start, delete_count] = replacement removed end @@ -985,12 +971,7 @@ module GiavaScript array_receiver = receiver_array(receiver, "Array.unshift") return array_receiver.size if args.empty? - result = Array(Value).new(array_receiver.size + args.size) - args.each { |arg| result << arg } - array_receiver.each { |value| result << value } - - array_receiver.clear - result.each { |value| array_receiver << value } + args.reverse_each { |arg| array_receiver.unshift(arg) } array_receiver.size end diff --git a/src/giavascript/switch_statement_parser.cr b/src/giavascript/switch_statement_parser.cr index 18616c3..2f6fa23 100644 --- a/src/giavascript/switch_statement_parser.cr +++ b/src/giavascript/switch_statement_parser.cr @@ -47,7 +47,7 @@ module GiavaScript current = 0 saw_default = false - while true + loop do current = skip_whitespace_in_source(source, current) break if current >= source.size diff --git a/src/giavascript_cli.cr b/src/giavascript_cli.cr index 42da892..19e8854 100644 --- a/src/giavascript_cli.cr +++ b/src/giavascript_cli.cr @@ -27,7 +27,8 @@ def run_file(path : String) : Int32 end end - messages.any? { |message| message.starts_with?("Error:") } ? 1 : 0 + has_errors = messages.any? { |message| message.starts_with?("Error:") } + has_errors ? 1 : 0 end if ARGV.empty? @@ -35,6 +36,6 @@ if ARGV.empty? elsif ARGV.size == 1 exit run_file(ARGV[0]) else - STDERR.puts "Usage: crystal run src/giavascript_cli.cr -- [path/to/file.ls]" + STDERR.puts "Usage: crystal run src/giavascript_cli.cr -- [path/to/file.js]" exit 1 end