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
@@ -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