diff --git a/.circleci/config.yml b/.circleci/config.yml
deleted file mode 100644
index 5370f5dd..00000000
--- a/.circleci/config.yml
+++ /dev/null
@@ -1,66 +0,0 @@
-# Javascript Node CircleCI 2.0 configuration file
-#
-# Check https://circleci.com/docs/2.0/language-javascript/ for more details
-#
-version: 2
-jobs:
- build:
- docker:
- # specify the version you desire here
- - image: circleci/node:11.6
-
- # Specify service dependencies here if necessary
- # CircleCI maintains a library of pre-built images
- # documented at https://circleci.com/docs/2.0/circleci-images/
- # - image: circleci/mongo:3.4.4
-
- working_directory: ~/repo/satriani
-
- steps:
- - checkout:
- path: ~/repo
- # Download and cache dependencies
- - restore_cache:
- keys:
- - v1-dependencies-{{ checksum "package.json" }}
- # fallback to using the latest cache if no exact match is found
- - v1-dependencies-
- - run: yarn install
- - save_cache:
- paths:
- - node_modules
- key: v1-dependencies-{{ checksum "package.json" }}
- - run: yarn pegjs
- - run: yarn test
- deploy:
- docker:
- # specify the version you desire here
- - image: circleci/node:11.6
-
- # Specify service dependencies here if necessary
- # CircleCI maintains a library of pre-built images
- # documented at https://circleci.com/docs/2.0/circleci-images/
- # - image: circleci/mongo:3.4.4
-
- working_directory: ~/repo/satriani
-
- steps:
- - checkout:
- path: ~/repo
- - run: yarn install
- - run: yarn browserify
- - run: yarn deploy
-workflows:
- version: 2
- build_and_test:
- jobs:
- - build
- build-and-deploy:
- jobs:
- - build
- - deploy:
- requires:
- - build
- filters:
- branches:
- only: master
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..41d1009e
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,395 @@
+root = true
+# Remove the line below if you want to inherit .editorconfig settings from higher directories
+
+[*.*]
+indent_style = tab
+indent_size = tab
+tab_width = 4
+charset = utf-8
+line_ending = lf
+trim_trailing_whitespace = true
+insert_final_newline = false
+
+[*.yml]
+indent_style = space
+indent_size = 2
+
+[*.cs]
+# C# files
+[*.cs]
+# Organize usings
+dotnet_separate_import_directive_groups = false
+
+# Resharper doesn't understand the special value unset
+# https://youtrack.jetbrains.com/issue/RIDER-56029
+# file_header_template = unset
+
+# this. and Me. preferences
+
+# Language keywords vs BCL types preferences
+dotnet_style_predefined_type_for_locals_parameters_members = false:suggestion
+dotnet_style_predefined_type_for_member_access = false:suggestion
+
+# Parentheses preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none
+dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:none
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary
+dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none
+
+# Modifier preferences
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
+
+# Expression-level preferences
+dotnet_style_coalesce_expression = true
+dotnet_style_collection_initializer = true
+dotnet_style_explicit_tuple_names = true
+dotnet_style_null_propagation = true
+dotnet_style_object_initializer = true
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+dotnet_style_prefer_auto_properties = true
+dotnet_style_prefer_compound_assignment = true
+dotnet_style_prefer_conditional_expression_over_assignment = true
+dotnet_style_prefer_conditional_expression_over_return = true
+dotnet_style_prefer_inferred_anonymous_type_member_names = true
+dotnet_style_prefer_inferred_tuple_names = true
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true
+dotnet_style_prefer_simplified_boolean_expressions = true
+dotnet_style_prefer_simplified_interpolation = true
+
+# Field preferences
+dotnet_style_readonly_field = true
+
+# Parameter preferences
+dotnet_code_quality_unused_parameters = all
+
+# Suppression preferences
+dotnet_remove_unnecessary_suppression_exclusions = none
+
+#### C# Coding Conventions ####
+
+# var preferences
+csharp_style_var_elsewhere = true:suggestion
+csharp_style_var_for_built_in_types = true:suggestion
+csharp_style_var_when_type_is_apparent = true:suggestion
+
+# Expression-bodied members
+csharp_style_expression_bodied_accessors = true:suggestion
+csharp_style_expression_bodied_indexers = true
+csharp_style_expression_bodied_lambdas = true
+csharp_style_expression_bodied_local_functions = false
+csharp_style_expression_bodied_operators = false
+csharp_style_expression_bodied_properties = true:suggestion
+
+# Pattern matching preferences
+csharp_style_pattern_matching_over_as_with_null_check = true
+csharp_style_pattern_matching_over_is_with_cast_check = true
+csharp_style_prefer_not_pattern = true
+csharp_style_prefer_pattern_matching = true:suggestion
+csharp_style_prefer_switch_expression = true
+
+# Null-checking preferences
+csharp_style_conditional_delegate_call = true
+
+# Modifier preferences
+csharp_prefer_static_local_function = true
+csharp_preferred_modifier_order = public, private, protected, internal, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async:suggestion
+
+# Code-block preferences
+csharp_prefer_braces = true
+csharp_prefer_simple_using_statement = true
+
+# Expression-level preferences
+csharp_prefer_simple_default_expression = true
+csharp_style_deconstructed_variable_declaration = true
+csharp_style_implicit_object_creation_when_type_is_apparent = true
+csharp_style_inlined_variable_declaration = true
+csharp_style_pattern_local_over_anonymous_function = true
+csharp_style_prefer_index_operator = true
+csharp_style_prefer_range_operator = true
+csharp_style_throw_expression = true
+csharp_style_unused_value_assignment_preference = discard_variable
+csharp_style_unused_value_expression_statement_preference = discard_variable
+
+# 'using' directive preferences
+
+#### C# Formatting Rules ####
+
+# New line preferences
+csharp_new_line_before_else = false
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_before_members_in_object_initializers = false
+csharp_new_line_before_open_brace = none
+csharp_new_line_between_query_expression_clauses = true
+
+# Indentation preferences
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = true
+csharp_indent_labels = one_less_than_current
+csharp_indent_switch_labels = true
+
+# Space preferences
+csharp_space_after_cast = true
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
+
+# Wrapping preferences
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = true
+
+#### Naming styles ####
+
+# Naming rules
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+# Symbol specifications
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types.required_modifiers =
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers =
+
+# Naming styles
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+dotnet_naming_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix =
+dotnet_naming_style.begins_with_i.word_separator =
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
+
+# CS8625: Cannot convert null literal to non-nullable reference type.
+# ReSharper properties
+resharper_allow_comment_after_lbrace = true
+resharper_braces_for_for = not_required
+resharper_braces_for_foreach = not_required
+resharper_braces_for_ifelse = not_required
+resharper_braces_for_while = not_required
+resharper_braces_redundant = true
+resharper_csharp_empty_block_style = together
+resharper_csharp_insert_final_newline = true
+resharper_csharp_max_line_length = 140
+resharper_csharp_naming_rule.event = AaBb
+resharper_csharp_naming_rule.interfaces = I + AaBb
+resharper_csharp_naming_rule.method = AaBb
+resharper_csharp_naming_rule.private_constants = AA_BB
+resharper_csharp_naming_rule.private_static_fields = aaBb
+resharper_csharp_naming_rule.private_static_readonly = aaBb
+resharper_csharp_naming_rule.property = AaBb
+resharper_csharp_naming_rule.types_and_namespaces = AaBb
+resharper_css_insert_final_newline = false
+resharper_default_internal_modifier = implicit
+resharper_html_insert_final_newline = true
+resharper_html_linebreaks_inside_tags_for_multiline_elements = false
+resharper_html_space_before_self_closing = true
+resharper_js_insert_final_newline = false
+resharper_keep_existing_declaration_block_arrangement = false
+resharper_keep_existing_enum_arrangement = false
+resharper_new_line_before_catch = false
+resharper_new_line_before_else = false
+resharper_new_line_before_finally = false
+resharper_outdent_statement_labels = true
+resharper_place_attribute_on_same_line = false
+resharper_place_simple_embedded_statement_on_same_line = true
+resharper_protobuf_insert_final_newline = false
+resharper_resx_insert_final_newline = false
+resharper_use_heuristics_for_body_style = true
+resharper_vb_insert_final_newline = false
+resharper_xmldoc_insert_final_newline = false
+resharper_xml_insert_final_newline = false
+
+# Microsoft .NET properties
+dotnet_naming_rule.constants_rule.import_to_resharper = as_predefined
+dotnet_naming_rule.constants_rule.severity = warning
+dotnet_naming_rule.constants_rule.style = all_upper_style
+dotnet_naming_rule.constants_rule.symbols = constants_symbols
+dotnet_naming_rule.event_rule.import_to_resharper = as_predefined
+dotnet_naming_rule.event_rule.severity = warning
+dotnet_naming_rule.event_rule.style = pascal_case
+dotnet_naming_rule.event_rule.symbols = event_symbols
+dotnet_naming_rule.interfaces_rule.import_to_resharper = as_predefined
+dotnet_naming_rule.interfaces_rule.severity = warning
+dotnet_naming_rule.interfaces_rule.style = begins_with_i
+dotnet_naming_rule.interfaces_rule.symbols = interfaces_symbols
+dotnet_naming_rule.interface_should_be_begins_with_i.import_to_resharper = True
+dotnet_naming_rule.interface_should_be_begins_with_i.resharper_description = interface_should_be_begins_with_i
+dotnet_naming_rule.interface_should_be_begins_with_i.resharper_guid = d55ec7d6-eb77-429d-8670-335916a17a57
+dotnet_naming_rule.local_constants_rule.import_to_resharper = as_predefined
+dotnet_naming_rule.local_constants_rule.severity = warning
+dotnet_naming_rule.local_constants_rule.style = all_upper_style
+dotnet_naming_rule.local_constants_rule.symbols = local_constants_symbols
+dotnet_naming_rule.method_rule.import_to_resharper = as_predefined
+dotnet_naming_rule.method_rule.severity = warning
+dotnet_naming_rule.method_rule.style = pascal_case
+dotnet_naming_rule.method_rule.symbols = method_symbols
+dotnet_naming_rule.non_field_members_should_be_pascal_case.import_to_resharper = True
+dotnet_naming_rule.non_field_members_should_be_pascal_case.resharper_description = non_field_members_should_be_pascal_case
+dotnet_naming_rule.non_field_members_should_be_pascal_case.resharper_guid = 21d958be-2191-4ce6-81fb-2470149ed4f0
+dotnet_naming_rule.private_constants_rule.import_to_resharper = as_predefined
+dotnet_naming_rule.private_constants_rule.severity = warning
+dotnet_naming_rule.private_constants_rule.style = all_upper_style
+dotnet_naming_rule.private_constants_rule.symbols = private_constants_symbols
+dotnet_naming_rule.private_instance_fields_rule.import_to_resharper = as_predefined
+dotnet_naming_rule.private_instance_fields_rule.severity = warning
+dotnet_naming_rule.private_instance_fields_rule.style = lower_camel_case_style
+dotnet_naming_rule.private_instance_fields_rule.symbols = private_instance_fields_symbols
+dotnet_naming_rule.private_static_fields_rule.import_to_resharper = as_predefined
+dotnet_naming_rule.private_static_fields_rule.severity = warning
+dotnet_naming_rule.private_static_fields_rule.style = lower_camel_case_style
+dotnet_naming_rule.private_static_fields_rule.symbols = private_static_fields_symbols
+dotnet_naming_rule.private_static_readonly_rule.import_to_resharper = as_predefined
+dotnet_naming_rule.private_static_readonly_rule.severity = warning
+dotnet_naming_rule.private_static_readonly_rule.style = lower_camel_case_style
+dotnet_naming_rule.private_static_readonly_rule.symbols = private_static_readonly_symbols
+dotnet_naming_rule.property_rule.import_to_resharper = as_predefined
+dotnet_naming_rule.property_rule.severity = warning
+dotnet_naming_rule.property_rule.style = pascal_case
+dotnet_naming_rule.property_rule.symbols = property_symbols
+dotnet_naming_rule.types_and_namespaces_rule.import_to_resharper = as_predefined
+dotnet_naming_rule.types_and_namespaces_rule.severity = warning
+dotnet_naming_rule.types_and_namespaces_rule.style = pascal_case
+dotnet_naming_rule.types_and_namespaces_rule.symbols = types_and_namespaces_symbols
+dotnet_naming_rule.types_should_be_pascal_case.import_to_resharper = True
+dotnet_naming_rule.types_should_be_pascal_case.resharper_description = types_should_be_pascal_case
+dotnet_naming_rule.types_should_be_pascal_case.resharper_guid = 65fe5778-0a22-4ca7-9eae-964e43db0552
+dotnet_naming_style.all_upper_style.capitalization = all_upper
+dotnet_naming_style.all_upper_style.word_separator = _
+dotnet_naming_style.lower_camel_case_style.capitalization = camel_case
+dotnet_naming_symbols.constants_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
+dotnet_naming_symbols.constants_symbols.applicable_kinds = field
+dotnet_naming_symbols.constants_symbols.required_modifiers = const
+dotnet_naming_symbols.event_symbols.applicable_accessibilities = *
+dotnet_naming_symbols.event_symbols.applicable_kinds = event
+dotnet_naming_symbols.interfaces_symbols.applicable_accessibilities = *
+dotnet_naming_symbols.interfaces_symbols.applicable_kinds = interface
+dotnet_naming_symbols.local_constants_symbols.applicable_accessibilities = *
+dotnet_naming_symbols.local_constants_symbols.applicable_kinds = local
+dotnet_naming_symbols.local_constants_symbols.required_modifiers = const
+dotnet_naming_symbols.method_symbols.applicable_accessibilities = *
+dotnet_naming_symbols.method_symbols.applicable_kinds = method
+dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private
+dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field
+dotnet_naming_symbols.private_constants_symbols.required_modifiers = const
+dotnet_naming_symbols.private_instance_fields_symbols.applicable_accessibilities = private
+dotnet_naming_symbols.private_instance_fields_symbols.applicable_kinds = field
+dotnet_naming_symbols.private_static_fields_symbols.applicable_accessibilities = private
+dotnet_naming_symbols.private_static_fields_symbols.applicable_kinds = field
+dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static
+dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private
+dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field
+dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static,readonly
+dotnet_naming_symbols.property_symbols.applicable_accessibilities = *
+dotnet_naming_symbols.property_symbols.applicable_kinds = property
+dotnet_naming_symbols.types_and_namespaces_symbols.applicable_accessibilities = *
+dotnet_naming_symbols.types_and_namespaces_symbols.applicable_kinds = namespace,class,struct,enum,delegate
+dotnet_style_qualification_for_event = true:none
+dotnet_style_qualification_for_field = true:none
+dotnet_style_qualification_for_method = true:none
+dotnet_style_qualification_for_property = true:none
+
+# ReSharper inspection severities
+resharper_arrange_redundant_parentheses_highlighting = hint
+resharper_arrange_this_qualifier_highlighting = hint
+resharper_arrange_type_member_modifiers_highlighting = hint
+resharper_arrange_type_modifiers_highlighting = hint
+resharper_built_in_type_reference_style_for_member_access_highlighting = hint
+resharper_built_in_type_reference_style_highlighting = hint
+resharper_redundant_base_qualifier_highlighting = warning
+resharper_suggest_var_or_type_built_in_types_highlighting = hint
+resharper_suggest_var_or_type_elsewhere_highlighting = hint
+resharper_suggest_var_or_type_simple_types_highlighting = hint
+
+[*.{cs,vb}]
+dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
+dotnet_style_predefined_type_for_member_access = false:suggestion
+dotnet_style_qualification_for_field = false:silent
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
+
+[*.cshtml]
+always_use_end_of_line_brace_style = true
+
+# Standard properties
+insert_final_newline = false
+
+# Microsoft .NET properties
+csharp_new_line_before_members_in_object_initializers = false
+csharp_preferred_modifier_order = public, private, protected, internal, file, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:suggestion
+csharp_style_prefer_utf8_string_literals = true:suggestion
+csharp_style_var_elsewhere = true:suggestion
+csharp_style_var_for_built_in_types = true:suggestion
+csharp_style_var_when_type_is_apparent = true:suggestion
+dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none
+dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none
+dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
+dotnet_style_predefined_type_for_member_access = true:suggestion
+dotnet_style_qualification_for_event = false:suggestion
+dotnet_style_qualification_for_field = false:suggestion
+dotnet_style_qualification_for_method = false:suggestion
+dotnet_style_qualification_for_property = false:suggestion
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
+
+# ReSharper properties
+resharper_always_use_end_of_line_brace_style = true
+resharper_object_creation_when_type_not_evident = target_typed
+resharper_razor_remove_blank_lines_near_braces = true
+resharper_use_indent_from_vs = false
+
+# ReSharper inspection severities
+resharper_arrange_redundant_parentheses_highlighting = hint
+resharper_arrange_this_qualifier_highlighting = hint
+resharper_arrange_type_member_modifiers_highlighting = hint
+resharper_arrange_type_modifiers_highlighting = hint
+resharper_built_in_type_reference_style_for_member_access_highlighting = hint
+resharper_built_in_type_reference_style_highlighting = hint
+resharper_redundant_base_qualifier_highlighting = warning
+resharper_suggest_var_or_type_built_in_types_highlighting = hint
+resharper_suggest_var_or_type_elsewhere_highlighting = hint
+resharper_suggest_var_or_type_simple_types_highlighting = hint
+
+[*.{appxmanifest,asax,ascx,aspx,axaml,axml,build,c,c++,cc,cginc,compute,config,cp,cpp,cs,cshtml,csproj,css,cu,cuh,cxx,dbml,discomap,dtd,fx,fxh,h,hh,hlsl,hlsli,hlslinc,hpp,htm,html,hxx,inc,inl,ino,ipp,ixx,jsproj,lsproj,master,mpp,mq4,mq5,mqh,njsproj,nuspec,paml,proj,props,razor,resw,resx,skin,StyleCop,targets,tasks,tpp,usf,ush,vb,vbproj,xaml,xamlx,xml,xoml,xsd}]
+indent_style = tab
+indent_size = tab
+tab_width = 4
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..ff1a3254
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,8 @@
+version: 2
+updates:
+ - package-ecosystem: bundler
+ directory: /
+ schedule:
+ interval: daily
+ allow:
+ - dependency-type: direct
diff --git a/.github/workflows/build-and-deploy-website.yml b/.github/workflows/build-and-deploy-website.yml
new file mode 100644
index 00000000..4c891866
--- /dev/null
+++ b/.github/workflows/build-and-deploy-website.yml
@@ -0,0 +1,77 @@
+name: build-and-deploy-codewithrockstar.com
+on:
+ # Run after the engine is rebuilt
+ workflow_run:
+ workflows: ['release-rockstar-engine']
+ types: [completed]
+ branches: ['main']
+ # Run after any website source files are changed
+ push:
+ branches: ["main"]
+ paths:
+ - 'codewithrockstar.com/**'
+ - 'codemirror-lang-rockstar/**'
+ - '.github/workflows/publish-codewithrockstar.com.yml'
+ # Allows you to run this workflow manually from the Actions tab
+ workflow_dispatch:
+# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+# Allow one concurrent deployment
+concurrency:
+ group: "pages"
+ cancel-in-progress: true
+jobs:
+ build-main-site:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Setup Ruby
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: '3.3' # Not needed with a .ruby-version file
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
+ cache-version: 0 # Increment this number if you need to re-download cached gems
+ working-directory: ./codewithrockstar.com
+ - name: Setup Pages
+ id: pages
+ uses: actions/configure-pages@v4
+ - name: fetch rockstar-wasm-binary from cache
+ uses: actions/cache/restore@v4
+ id: restore-cached-wasm-binary
+ with:
+ enableCrossOsArchive: true
+ path: rockstar-wasm-binary
+ key: rockstar-wasm-binary
+ - name: Copy WASM binaries into site source
+ run: cp -r rockstar-wasm-binary/ codewithrockstar.com/wasm/
+ - name: build CodeMirror editor bundle with Rockstar language support
+ run: |
+ cd cm-lang-rockstar
+ npm install
+ npm run test
+ npm run prepare
+ - name: Build codewithrockstar.com site
+ run: |
+ cd codewithrockstar.com
+ ls -l /home/runner/work/rockstar2/rockstar2/codewithrockstar.com/examples/01-getting-started/hello-world.rock
+ bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}"
+ env:
+ JEKYLL_ENV: production
+ - name: Upload artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: ./codewithrockstar.com/_site
+ deploy:
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ runs-on: ubuntu-latest
+ needs: build-main-site
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/.github/workflows/build-macos-binary.yml b/.github/workflows/build-macos-binary.yml
new file mode 100644
index 00000000..e5bb1510
--- /dev/null
+++ b/.github/workflows/build-macos-binary.yml
@@ -0,0 +1,45 @@
+name: build-macos-binary
+on:
+ workflow_dispatch:
+ workflow_call:
+jobs:
+ build:
+ runs-on: macos-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Set up .NET 8 SDK
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '8.x'
+ - name: Get Next Version
+ id: semver
+ uses: ietf-tools/semver-action@v1
+ with:
+ token: ${{ github.token }}
+ branch: main
+ - name: "Display build version"
+ run: echo "tag ${{ steps.semver.outputs.next }}"
+# - name: Patch VERSION constant inside Starship/Rockstar/Program.cs
+# run: sed -i "" 's/__VERSION__/${{ steps.semver.outputs.next }}/g' Starship/Rockstar/Program.cs
+ - name: dotnet publish macos x64 binary
+ run: dotnet publish Starship/Rockstar -o rockstar-macos-x64-binary -r osx-x64 -c Release
+ - name: dotnet publish macos ARM64 binary
+ run: dotnet publish Starship/Rockstar -o rockstar-macos-arm64-binary -r osx-arm64 -c Release
+ - name: Test ARM64 binary
+ run:
+ rockstar-macos-arm64-binary/rockstar --version
+ - uses: actions/cache/save@v4
+ name: Save rockstar-macos-x64-binary to cache
+ id: save-rockstar-macos-x64-binary-to-cache
+ with:
+ enableCrossOsArchive: true
+ path: rockstar-macos-x64-binary
+ key: rockstar-macos-x64-binary-${{ github.run_id }}
+ - uses: actions/cache/save@v4
+ name: Save rockstar-macos-arm64-binary to cache
+ id: save-rockstar-macos-arm64-binary-to-cache
+ with:
+ enableCrossOsArchive: true
+ path: rockstar-macos-arm64-binary
+ key: rockstar-macos-arm64-binary-${{ github.run_id }}
diff --git a/.github/workflows/build-wasm-and-linux-binaries.yml b/.github/workflows/build-wasm-and-linux-binaries.yml
new file mode 100644
index 00000000..cfc6bf7d
--- /dev/null
+++ b/.github/workflows/build-wasm-and-linux-binaries.yml
@@ -0,0 +1,79 @@
+name: build-wasm-and-linux-binaries
+on:
+ push:
+ branches: ["main"]
+ paths:
+ - 'Starship/**'
+ - '.github/workflows/build-wasm-and-linux-binaries.yml'
+ - '.github/workflows/build-windows-binary.yml'
+ - '.github/workflows/build-macos-binary.yml'
+ workflow_dispatch:
+ workflow_call:
+concurrency:
+ group: "build-wasm-and-linux-binaries"
+ cancel-in-progress: true
+jobs:
+ build-engine:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Set up .NET 8 SDK
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '8.x'
+ - name: Install .NET WASM workloads
+ working-directory: ./Starship
+ run: dotnet workload install wasm-tools
+ - name: Get Next Version
+ id: semver
+ uses: ietf-tools/semver-action@v1
+ with:
+ token: ${{ github.token }}
+ branch: main
+ - name: "Display build version"
+ run: echo "tag ${{ steps.semver.outputs.next }}"
+ - name: Patch VERSION constant inside Starship/Rockstar.Engine/RockstarEnvironment.cs
+ run: sed -i 's/VERSION = "[^"]*";/VERSION = "${{ steps.semver.outputs.next }}";/g' Starship/Rockstar.Engine/RockstarEnvironment.cs
+ - name: Build Rockstar
+ run: dotnet build Starship -c Release
+ - name: Test Rockstar
+ run: dotnet test Starship -c Release
+ # - name: Update CHANGELOG
+ # id: changelog
+ # uses: requarks/changelog-action@v1
+ # with:
+ # token: ${{ github.token }}
+ # tag: ${{ github.ref_name }}
+ - name: Commit changes with new version
+ run: |
+ git config --global user.name 'workflows/build-wasm-and-linux-binaries.yml'
+ git config --global user.email 'dylanbeattie@users.noreply.github.com'
+ git commit -am "[skip actions] set VERSION = ${{ steps.semver.outputs.next }} in RockstarEnvironment.cs"
+ git push
+ - name: dotnet publish WASM engine
+ run: dotnet publish Starship/Rockstar.Wasm -o rockstar-wasm-binary -c Release
+ - name: dotnet publish Linux binary
+ run: dotnet publish Starship/Rockstar -o rockstar-linux-x64-binary -c Release
+ - uses: actions/cache/save@v4
+ name: save rockstar-wasm-binary to cache
+ id: save-rockstar-wasm-binary-to-cache
+ with:
+ enableCrossOsArchive: true
+ path: rockstar-wasm-binary
+ key: rockstar-wasm-binary-${{ github.run_id }}
+ - uses: actions/cache/save@v4
+ name: save rockstar-linux-x64-binary to cache
+ id: save-rockstar-linux-x64-binary-to-cache
+ with:
+ enableCrossOsArchive: true
+ path: rockstar-linux-x64-binary
+ key: rockstar-linux-x64-binary-${{ github.run_id }}
+ rockstar-macos-binary:
+ needs: build-engine
+ uses: ./.github/workflows/build-macos-binary.yml
+ rockstar-windows-binary:
+ needs: build-engine
+ uses: ./.github/workflows/build-windows-binary.yml
+
diff --git a/.github/workflows/build-windows-binary.yml b/.github/workflows/build-windows-binary.yml
new file mode 100644
index 00000000..fc99d303
--- /dev/null
+++ b/.github/workflows/build-windows-binary.yml
@@ -0,0 +1,36 @@
+name: build-windows-binary
+on:
+ workflow_dispatch:
+ workflow_call:
+jobs:
+ build:
+ runs-on: windows-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Get Next Version
+ id: semver
+ uses: ietf-tools/semver-action@v1
+ with:
+ token: ${{ github.token }}
+ branch: main
+ - name: "Display build version"
+ run: echo "tag ${{ steps.semver.outputs.next }}"
+ - name: Set up .NET 8 SDK
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '8.x'
+# - name: Patch VERSION constant inside Starship/Rockstar/Program.cs
+# run: (Get-content Starship/Rockstar/Program.cs) | Foreach-Object {$_ -replace "__VERSION__", "${{ steps.semver.outputs.next }}"} | Set-Content Starship/Rockstar/Program.cs
+ - name: dotnet publish Windows x64 binary
+ run: dotnet publish Starship/Rockstar -o rockstar-windows-x64-binary -c Release
+ - name: Test Windows x64 binary
+ run:
+ rockstar-windows-x64-binary/rockstar.exe --version
+ - uses: actions/cache/save@v4
+ name: save rockstar-windows-x64-binary to cache
+ id: save-rockstar-windows-x64-binary-to-cache
+ with:
+ enableCrossOsArchive: true
+ path: rockstar-windows-x64-binary
+ key: rockstar-windows-x64-binary-${{ github.run_id }}
diff --git a/.github/workflows/pegjs-and-test.yml b/.github/workflows/pegjs-and-test.yml
deleted file mode 100644
index ee2a34b6..00000000
--- a/.github/workflows/pegjs-and-test.yml
+++ /dev/null
@@ -1,32 +0,0 @@
-# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
-# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
-
-name: Build parser and run tests
-
-on:
- push:
- branches: [ main ]
- pull_request:
- branches: [ main ]
-
-jobs:
- build:
- runs-on: ubuntu-latest
- strategy:
- matrix:
- # node-version: [12.x, 14.x, 16.x]
- node-version: [16.x]
- # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
- steps:
- - uses: actions/checkout@v2
- - name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v2
- with:
- node-version: ${{ matrix.node-version }}
- # cache: 'npm'
- - name: Yarn all the things
- working-directory: ./satriani
- run: |
- yarn install
- yarn pegjs
- yarn test
diff --git a/.github/workflows/release-rockstar-engine.yml b/.github/workflows/release-rockstar-engine.yml
new file mode 100644
index 00000000..9b1259d0
--- /dev/null
+++ b/.github/workflows/release-rockstar-engine.yml
@@ -0,0 +1,125 @@
+name: release-rockstar-engine
+on:
+ workflow_run:
+ workflows: ['build-wasm-and-linux-binaries']
+ types: [completed]
+ branches: ['main']
+ # push:
+ # branches: ["main"]
+ # paths:
+ # - 'Starship/**'
+ # - '.github/workflows/**'
+ workflow_dispatch:
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+concurrency:
+ group: "engine"
+ cancel-in-progress: true
+jobs:
+ release-rockstar-engine:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ steps:
+ - name: restore macOS ARM64 binary from cache
+ uses: actions/cache/restore@v4
+ id: restore-cached-macos-arm64-binary
+ with:
+ enableCrossOsArchive: true
+ path: rockstar-macos-arm64-binary
+ key: rockstar-macos-arm64-binary
+ - name: restore macOS x64 binary from cache
+ uses: actions/cache/restore@v4
+ id: restore-cached-macos-x64-binary
+ with:
+ enableCrossOsArchive: true
+ path: rockstar-macos-x64-binary
+ key: rockstar-macos-x64-binary
+ - name: restore Linux binary from cache
+ uses: actions/cache/restore@v4
+ id: restore-cached-linux-binary
+ with:
+ enableCrossOsArchive: true
+ path: rockstar-linux-x64-binary
+ key: rockstar-linux-x64-binary
+ - name: restore Windows binary from cache
+ uses: actions/cache/restore@v4
+ id: restore-cached-windows-x64-binary
+ with:
+ enableCrossOsArchive: true
+ path: rockstar-windows-x64-binary
+ key: rockstar-windows-x64-binary
+ - name: restore WASM binary from cache
+ uses: actions/cache/restore@v4
+ id: restore-cached-rockstar-wasm-binary
+ with:
+ enableCrossOsArchive: true
+ path: rockstar-wasm-binary
+ key: rockstar-wasm-binary
+ - name: create download assets
+ run: |
+ ls -lR
+ - name: extract version from binary
+ id: version
+ run: |
+ ./rockstar-linux-x64-binary/rockstar --version
+ echo "version=$(./rockstar-linux-x64-binary/rockstar --version)" >> $GITHUB_OUTPUT
+ - name: create download assets
+ run: |
+ ls -lR
+ zip rockstar-${{ steps.version.outputs.version }}-windows-x64.zip rockstar-windows-x64-binary/*
+ tar -czf rockstar-${{ steps.version.outputs.version }}-macos-arm64.tar.gz rockstar-macos-arm64-binary/*
+ tar -czf rockstar-${{ steps.version.outputs.version }}-macos-x64.tar.gz rockstar-macos-x64-binary/*
+ tar -czf rockstar-${{ steps.version.outputs.version }}-linux-x64.tar.gz rockstar-linux-x64-binary/*
+
+ - name: create release
+ uses: actions/create-release@v1
+ id: create_release
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
+ with:
+ draft: false
+ prerelease: true
+ release_name: rockstar-${{ steps.version.outputs.version }}
+ tag_name: ${{ steps.version.outputs.version }}
+ body: |
+ Rockstar ${{ steps.version.outputs.version }}
+
+ - name: upload windows artifact
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ with:
+ upload_url: ${{ steps.create_release.outputs.upload_url }}
+ asset_path: ./rockstar-${{ steps.version.outputs.version }}-windows-x64.zip
+ asset_name: rockstar-${{ steps.version.outputs.version }}-windows-x64.zip
+ asset_content_type: application/zip
+ - name: upload macos-x64 artifact
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ with:
+ upload_url: ${{ steps.create_release.outputs.upload_url }}
+ asset_path: ./rockstar-${{ steps.version.outputs.version }}-macos-x64.tar.gz
+ asset_name: rockstar-${{ steps.version.outputs.version }}-macos-x64.tar.gz
+ asset_content_type: application/gzip
+ - name: upload linux-x64 artifact
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ with:
+ upload_url: ${{ steps.create_release.outputs.upload_url }}
+ asset_path: ./rockstar-${{ steps.version.outputs.version }}-linux-x64.tar.gz
+ asset_name: rockstar-${{ steps.version.outputs.version }}-linux-x64.tar.gz
+ asset_content_type: application/gzip
+ - name: upload macos-arm64 artifact
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ with:
+ upload_url: ${{ steps.create_release.outputs.upload_url }}
+ asset_path: ./rockstar-${{ steps.version.outputs.version }}-macos-arm64.tar.gz
+ asset_name: rockstar-${{ steps.version.outputs.version }}-macos-arm64.tar.gz
+ asset_content_type: application/gzip
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index b631be61..12ef7889 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,13 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
-## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
+.DS_Store
+
+.NCrunch_*/
# User-specific files
+*.rsuser
*.suo
*.user
*.userosscache
@@ -12,6 +16,9 @@
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
+# Mono auto generated files
+mono_crash.*
+
# Build results
[Dd]ebug/
[Dd]ebugPublic/
@@ -19,10 +26,14 @@
[Rr]eleases/
x64/
x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
+[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
@@ -36,9 +47,10 @@ Generated\ Files/
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
-# NUNIT
+# NUnit
*.VisualState.xml
TestResult.xml
+nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
@@ -52,7 +64,9 @@ BenchmarkDotNet.Artifacts/
project.lock.json
project.fragment.lock.json
artifacts/
-**/Properties/launchSettings.json
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
@@ -60,7 +74,7 @@ StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
-*_i.h
+*_h.h
*.ilk
*.meta
*.obj
@@ -77,7 +91,9 @@ StyleCopReport.xml
*.tlh
*.tmp
*.tmp_proj
+*_wpftmp.csproj
*.log
+*.tlog
*.vspscc
*.vssscc
.builds
@@ -119,9 +135,6 @@ _ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
-# JustCode is a .NET coding add-in
-.JustCode
-
# TeamCity is a build add-in
_TeamCity*
@@ -132,6 +145,11 @@ _TeamCity*
.axoCover/*
!.axoCover/settings.json
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
# Visual Studio code coverage results
*.coverage
*.coveragexml
@@ -179,6 +197,8 @@ PublishScripts/
# NuGet Packages
*.nupkg
+# NuGet Symbol Packages
+*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
@@ -203,12 +223,14 @@ BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
+*.appxbundle
+*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
-!*.[Cc]ache/
+!?*.[Cc]ache/
# Others
ClientBin/
@@ -221,7 +243,7 @@ ClientBin/
*.publishsettings
orleans.codegen.cs
-# Including strong name files can present a security risk
+# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
@@ -252,6 +274,9 @@ ServiceFabricBackup/
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
@@ -272,6 +297,17 @@ node_modules/
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+*.ncb
+*.aps
+
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
@@ -287,12 +323,8 @@ paket-files/
# FAKE - F# Make
.fake/
-# JetBrains Rider
-.idea/
-*.sln.iml
-
-# CodeRush
-.cr/
+# CodeRush personal settings
+.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
@@ -317,7 +349,7 @@ __pycache__/
# OpenCover UI analysis results
OpenCover/
-# Azure Stream Analytics local run output
+# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
@@ -326,7 +358,51 @@ ASALocalRun/
# NVidia Nsight GPU debugger configuration file
*.nvuser
-# MFractors (Xamarin productivity tool) working folder
+# MFractors (Xamarin productivity tool) working folder
.mfractor/
-._*
\ No newline at end of file
+# Local History for Visual Studio
+.localhistory/
+
+# Visual Studio History (VSHistory) files
+.vshistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+*.sln.iml
+
+.obsidian/
+
+docs.codewithrockstar.com/examples/
+
+docs.codewithrockstar.com/wasm/
+codewithrockstar.com/wasm/
diff --git a/.obsidian/app.json b/.obsidian/app.json
index 9e26dfee..7b0deb76 100644
--- a/.obsidian/app.json
+++ b/.obsidian/app.json
@@ -1 +1,4 @@
-{}
\ No newline at end of file
+{
+ "alwaysUpdateLinks": true,
+ "useMarkdownLinks": true
+}
\ No newline at end of file
diff --git a/.obsidian/appearance.json b/.obsidian/appearance.json
index c8c365d8..9e26dfee 100644
--- a/.obsidian/appearance.json
+++ b/.obsidian/appearance.json
@@ -1,3 +1 @@
-{
- "accentColor": ""
-}
\ No newline at end of file
+{}
\ No newline at end of file
diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json
index fcbb245c..b86fdc02 100644
--- a/.obsidian/workspace.json
+++ b/.obsidian/workspace.json
@@ -1,21 +1,21 @@
{
"main": {
- "id": "0ccb4f25a00d801e",
+ "id": "daec523910f75076",
"type": "split",
"children": [
{
- "id": "b446a303a064afa7",
+ "id": "8defaba5a2a5b669",
"type": "tabs",
"children": [
{
- "id": "2628a240602d50df",
+ "id": "005da2a05b4fbf96",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
- "file": "rockstar-2.md",
+ "file": "codewithrockstar.com/docs/02-types-and-values.md",
"mode": "source",
- "source": false
+ "source": true
}
}
}
@@ -25,15 +25,15 @@
"direction": "vertical"
},
"left": {
- "id": "f690222422428971",
+ "id": "f268972251ee4b9e",
"type": "split",
"children": [
{
- "id": "0d09fb9a066e2e1b",
+ "id": "c8b707c9a0236288",
"type": "tabs",
"children": [
{
- "id": "d63958cb6777fd12",
+ "id": "efc3d0b2f9e18fac",
"type": "leaf",
"state": {
"type": "file-explorer",
@@ -43,7 +43,7 @@
}
},
{
- "id": "edfaaca8776d8fca",
+ "id": "aa578ce3457c6a61",
"type": "leaf",
"state": {
"type": "search",
@@ -58,7 +58,7 @@
}
},
{
- "id": "42df576a0b3c3273",
+ "id": "420b590388970899",
"type": "leaf",
"state": {
"type": "bookmarks",
@@ -72,20 +72,20 @@
"width": 300
},
"right": {
- "id": "d9851b42a3243e36",
+ "id": "9bff8b7aebd31401",
"type": "split",
"children": [
{
- "id": "8bcd16d8bb3a85be",
+ "id": "15ba46ad93ef4686",
"type": "tabs",
"children": [
{
- "id": "831e26003812585b",
+ "id": "9084c39fe202a25b",
"type": "leaf",
"state": {
"type": "backlink",
"state": {
- "file": "rockstar-2.md",
+ "file": "codewithrockstar.com/docs/02-types-and-values.md",
"collapseAll": false,
"extraContext": false,
"sortOrder": "alphabetical",
@@ -97,19 +97,19 @@
}
},
{
- "id": "947fceb37d61fad5",
+ "id": "142902d61e1e985a",
"type": "leaf",
"state": {
"type": "outgoing-link",
"state": {
- "file": "rockstar-2.md",
+ "file": "codewithrockstar.com/docs/02-types-and-values.md",
"linksCollapsed": false,
"unlinkedCollapsed": true
}
}
},
{
- "id": "d9df56ca7c325aba",
+ "id": "1ec3213e536aed8b",
"type": "leaf",
"state": {
"type": "tag",
@@ -120,12 +120,12 @@
}
},
{
- "id": "6aacb9c7afabddf1",
+ "id": "8c35ebdb306d03ea",
"type": "leaf",
"state": {
"type": "outline",
"state": {
- "file": "rockstar-2.md"
+ "file": "codewithrockstar.com/docs/02-types-and-values.md"
}
}
}
@@ -146,6 +146,53 @@
"command-palette:Open command palette": false
}
},
- "active": "2628a240602d50df",
- "lastOpenFiles": []
+ "active": "efc3d0b2f9e18fac",
+ "lastOpenFiles": [
+ "Starship/Rockstar.Engine/obj/Debug/net8.0/gw1hreob.tjq~",
+ "Starship/Rockstar.Test/obj/Debug/net8.0/nCrunchTemp_5bc5a958-8d02-4d54-9d5b-e9660cd70e4c.csproj.AssemblyReference.cache",
+ "Starship/Rockstar/obj/Debug/net8.0/nCrunchTemp_3c9525d6-b14c-40f6-be90-33819e483ee6.csproj.AssemblyReference.cache",
+ "Starship/Rockstar.Profiler/obj/Debug/net8.0/nCrunchTemp_d678d38e-5041-4245-a2a7-eb0a118524cb.csproj.AssemblyReference.cache",
+ "Starship/Rockstar.Engine/obj/Debug/net8.0/nCrunchTemp_8ca7425a-775c-4bc8-a44e-7cb90069cdab.csproj.AssemblyReference.cache",
+ "Starship/Rockstar.Test/obj/Debug/net8.0/nCrunchTemp_5bc5a958-8d02-4d54-9d5b-e9660cd70e4c.GlobalUsings.g.cs",
+ "Starship/Rockstar/obj/Debug/net8.0/nCrunchTemp_3c9525d6-b14c-40f6-be90-33819e483ee6.GlobalUsings.g.cs",
+ "Starship/Rockstar.Profiler/obj/Debug/net8.0/nCrunchTemp_d678d38e-5041-4245-a2a7-eb0a118524cb.GlobalUsings.g.cs",
+ "Starship/Rockstar.Engine/obj/Debug/net8.0/nCrunchTemp_8ca7425a-775c-4bc8-a44e-7cb90069cdab.GlobalUsings.g.cs",
+ "Starship/Rockstar.Test/obj/Debug/net8.0/nCrunchTemp_5bc5a958-8d02-4d54-9d5b-e9660cd70e4c.assets.cache",
+ "Starship/Rockstar/obj/Debug/net8.0/nCrunchTemp_3c9525d6-b14c-40f6-be90-33819e483ee6.assets.cache",
+ "codewithrockstar.com/docs/01-getting-started.md",
+ "codewithrockstar.com/docs/02-types-and-values.md",
+ "codewithrockstar.com/docs/09-arrays.md",
+ "codewithrockstar.com/docs/06-expressions.md",
+ "codewithrockstar.com/docs/99-rockstar-acid-test.md",
+ "codewithrockstar.com/docs/07-flow-control.md",
+ "codewithrockstar.com/links.md",
+ "codewithrockstar.com/news.md",
+ "codewithrockstar.com/_site/assets/img/teemill/480p/red-on-press.webp",
+ "codewithrockstar.com/_site/assets/img/teemill/480p/red-basic-fit.webp",
+ "codewithrockstar.com/_site/assets/img/teemill/480p/red-logo.webp",
+ "codewithrockstar.com/_site/assets/img/teemill/480p/blue-tailored.webp",
+ "codewithrockstar.com/_site/assets/img/teemill/480p/blue-logo.webp",
+ "codewithrockstar.com/_site/assets/img/teemill/480p/blue-kids-fit.webp",
+ "codewithrockstar.com/assets/img/teemill/480p/red-on-press.webp",
+ "codewithrockstar.com/assets/img/teemill/480p/red-logo.webp",
+ "codewithrockstar.com/assets/img/teemill/480p/red-basic-fit.webp",
+ "codewithrockstar.com/assets/img/teemill/480p/blue-tailored.webp",
+ "cm-lang-rockstar/node_modules/style-mod/src/README.md",
+ "cm-lang-rockstar/node_modules/@bcoe/v8-coverage/dist/lib/README.md",
+ "cm-lang-rockstar/node_modules/@bcoe/v8-coverage/dist/lib/LICENSE.md",
+ "cm-lang-rockstar/node_modules/@bcoe/v8-coverage/dist/lib/CHANGELOG.md",
+ "codewithrockstar.com/merch.md",
+ "cm-lang-rockstar/node_modules/@sinonjs/commons/lib/prototypes/README.md",
+ "cm-lang-rockstar/node_modules/@rollup/plugin-node-resolve/README.md",
+ "cm-lang-rockstar/node_modules/@codemirror/lint/README.md",
+ "cm-lang-rockstar/node_modules/deepmerge/changelog.md",
+ "cm-lang-rockstar/node_modules/terser/node_modules/source-map-support/README.md",
+ "cm-lang-rockstar/node_modules/tslib/README.md",
+ "cm-lang-rockstar/node_modules/imurmurhash/README.md",
+ "codewithrockstar.com/tutorial.md",
+ "codewithrockstar.com/docs/04-arithmetic.md",
+ "codewithrockstar.com/code-of-conduct.md",
+ "cm-lang-rockstar/README.md",
+ "codewithrockstar.com/index.md"
+ ]
}
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
deleted file mode 100644
index 31cf7e59..00000000
--- a/CONTRIBUTING.md
+++ /dev/null
@@ -1,60 +0,0 @@
-# How to contribute to Rockstar
-
-> "Rockstar was never intended to be more than a joke - a parody spec that I threw together in a couple
-of hours in a bar one evening. The amount of interest and enthusiasm that this project has generated
-has been astonishing, and wonderful, but – perhaps inevitably – there are a LOT of things in the initial Rockstar spec that
-made perfect sense when it was a joke spec but have proved incredibly difficult to actually implement.
->
-> "Over the last six months, the entire Rockstar project has been through a sort of massive red/green/refactor cycle -
-> creating tests to validate core language features, building implementations that pass those tests, and then looking
-> at ways to clean up and harmonise those implementations.
->
-> Thanks to the ongoing efforts of Rockstar developers around the world, we've ironed out most of the contradictions,
-> resolved dozens of ambiguities and gotchas in the original spec, and come up with something that's probably good
-> enough to call a 'release candidate'. But I have a funny feeling like this is still only the beginning. :)
->
-> - @dylanbeattie, January 2019
-
-## Ways you can contribute
-
-First, make sure you've read the [code of conduct](CODE_OF_CONDUCT.md). TL;DR: be excellent to each other. Be calm, be
-kind, help make Rockstar a community where new faces feel welcome and old hands feel appreciated.
-
-* Report a bug. If you've found something that doesn't work, let us know.
-* Suggest a new feature.
-* Write a great Rockstar program we can add to our examples
-* Create your own implementation of Rockstar
-
-### Reporting bugs
-
-If you've found a bug in the specification or in the Satriani implementation, let us know about it.
-* Search the [Issues](https://github.com/rockstarlang/rockstar/issues) to check we're not already tracking it.
-* If you can't find an open issue that describes your problem, [open a new one](https://github.com/RockstarLang/rockstar/issues/new).
- * Include a **title and clear description**
- * Describe:
- * What you did (ideally with a code sample)
- * What you expected to happen
- * What actually happened, including any error messages or program output.
-
-### Fixing Bugs
-
-If you've fixed an open bug - awesome! You're a true Rockstar developer.
-* Open a new GitHub pull request with your patch. Pull requests should include:
- * The fix itself
- * One or more test cases in the form of `.rock` programs demonstrating the bug, that
- should FAIL on an unpatched implementation and PASS with your patch in place.
- * Updates to any associated documentation or examples
-
-### Contributing Features
-
-If you've got a great idea for a Rockstar language feature, start by checking
-[issues](https://github.com/rockstarlang/rockstar/issues) to check we're not already tracking it, or that your idea
-hasn't already been rejected.
-
-If not, start by [opening an issue](https://github.com/RockstarLang/rockstar/issues/new) that describes your idea. If
-you want to chat to some of the core team about it first, hop onto the
-[Rockstar Developers channel on Discord](https://discordapp.com/invite/xsQK7UU) and tell us what you're thinking.
-
-Remember, a good Rockstar feature is one that extends the capabilities of the language *and* allows developers the
-kind of lyrical creativity that makes for great Rockstar programs.
-
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index a2401e43..0ad25db4 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,21 +1,661 @@
-MIT License
-
-Copyright (c) 2018 Dylan Beattie
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+.
diff --git a/README.md b/README.md
index ffdfe766..ad0a7508 100644
--- a/README.md
+++ b/README.md
@@ -1,74 +1,67 @@
+# rockstar2
+It's time. Rockstar 2: "The Difficult Second Version"
+
+The Build Process
+
+Building codewithrockstar.com works like this:
+
+build-and-test-rockstar-engine
+
+- runs on Linux
+- Builds the parser and interpreter
+- Runs the test suite
+- Uploads artifacts for:
+ - linux native binary
+ - WASM interpreter for the website
+
+IF THAT WORKS:
+
+build-windows-binary
+* builds the Rockstar windows binary
+
+build-macos-binary
+* builds the macOS binary
+
+ build-and-deploy-website
+ * Downloads the linux binary Rockstar WASM artifact from step 1
+ * Downloads the windows and macOS binaries from steps 2 and 3
+ * Builds the Jekyll site
+### Debug/Dev Mode Setup
+
+In dev mode, I use symbolic directory links between the various parts of the project. Rebuilding the .NET solution will rebuild the WASM interpreter, which Jekyll can see as `/wasm/**`, and trigger a site rebuild, and all the Rockstar code examples are part of both the `Rockstar.Test` .NET test suite project and the `codewithrockstar.com` site:
+
+```
+> cd codewithrockstar.com
+> mklink /d wasm ..\Starship\Rockstar.Wasm\bin\Debug\net8.0-browser
+> mklink /d examples ..\Starship\Rockstar.Test\programs\examples
```
- _____ _____ ______ ___ ______________ ____ ______
-| _ \ / _ \ / ___/ /| / / / ______ _// || _ \
-| |_) )/ / \ \/ / / |/ / ( (___ | | / || |_) )
-| /( ( ) ( | \ \____ \ | |/ /| || /
-| |\ \ \ \_/ /\ \___ | |\ \ _____) )| // / | || |\ \
-|__| \ \ \_____/ \_____||__| \__\\______/ |//__/ |__||__| \__\
- \ | ------------------------------------------------------
- \ | h t t p s : // c o d e w i t h r o c k s t a r . c o m
- \| ------------------------------------------------------
-```
-
-Rockstar is a dynamically typed computer programming language, designed for creating programs that are also song lyrics. Rockstar is heavily influenced by the lyrical conventions of 1980s hard rock and power ballads.
-
-### But why?
-
-Mainly because if we make Rockstar a real (and completely pointless) programming language, then recruiters and hiring managers won't be able to talk about 'rockstar developers' any more.
-
-Also 'cos it's kinda fun and any language based on the idea of compiling Meatloaf lyrics has to be worth a look, right?
-
-Plus we can make stickers. Who doesn't want a sticker on their laptop saying 'CERTIFIED ROCKSTAR DEVELOPER'?
-
-## What's here?
-
-* The [Rockstar language specification](spec.md)
-* How to [contribute to Rockstar](CONTRIBUTING.md)
-* Some [examples of Rockstar programs](examples/README.md)
-
-## Implementations
-
-The official reference implementation is [Satriani](https://github.com/RockstarLang/rockstar/tree/master/satriani) - written in JavaScript, it runs in browsers and supports NodeJS for server-side and larger projects.
-
-[Rocky](https://github.com/gaborsch/rocky) is a fully spec-compliant Rockstar implementation written in Java, and is currently the only Rockstar implementation that includes support for the DEC64 numeric type that was described in the original language specification.
-
-Other implementations:
-
-* [rockstar-js](https://github.com/wolfgang42/rockstar-js) - Rockstar-to-JavaScript transpiler
-* [rockstar-lexer](https://github.com/aitorres/rockstar-lexer) - Rockstar lexer written in Haskell with Alex
-* [rockstar-java](https://github.com/nbrevu/rockstar-java) - Rockstar interpreter in Java
-* [rockstar-ml](https://github.com/lkwq007/rockstar-ml) - Rockstar interpreter in OCaml
-* [rockstar-py](https://github.com/yanorestes/rockstar-py) - Rockstar-to-Python transpiler
-* [kaiser-ruby](https://github.com/marcinruszkiewicz/kaiser-ruby) - Rockstar to Ruby transpiler
-* [sublime-rockstar-syntax](https://github.com/paxromana96/sublime-rockstar-syntax) - Syntax highlighter for Sublime Text 3
-* [language-rockstar](https://github.com/thestd/language-rockstar) - Syntax highlighter for Atom
-* [maiden](https://github.com/palfrey/maiden) - Rockstar interpreter in Rust ([online version using WebAssembly](https://palfrey.github.io/maiden/))
-* [thrash](https://github.com/young-steveo/thrash) - Rockstar implementation in Go
-* [rockstar-webpiler](https://github.com/cwfitzgerald/rockstar-webpiler) - Online Rockstar Parser and Transpiler. [rockstar.connorwfitzgerald.com](https://rockstar.connorwfitzgerald.com)
-* [wasm-rocks](https://github.com/boyanio/wasm-rocks) - Online Rockstar-to-WebAssembly compiler
-* [vim-rockstar](https://github.com/sirosen/vim-rockstar) - Syntax highlighting for vim
-* [vscode-rockstar-language](https://github.com/ra100/vscode-rockstar-language) - Syntax highlighting for VSCode
-* [chirp](https://github.com/Suloch/chirp) - Rockstar interpreter in C using flex and yacc
-* [rockstar-feat-csharp](https://github.com/theolivenbaum/rockstar-feat-csharp) - Embed Rockstar on your C# code using source-code generators
-* [sellout](https://github.com/davidadsit/sellout) - Rockstar to C# (dotnet core) transpiler
-* [native-rockstar](https://github.com/gillesdami/native-rockstar) - Rockstar to C++ transpiler in javascript.
-* [rokkstar](https://github.com/ascheja/rokkstar) - Rockstar parser and interpreter written in Kotlin.
-
-## Social Spaces
-* [Rockstar on Rosetta Code](http://www.rosettacode.org/wiki/Category:Rockstar)
-* [/r/RockstarDevs](https://www.reddit.com/r/RockstarDevs/) - Subreddit
-* [Rockstar Developers](https://discordapp.com/invite/xsQK7UU) - Discord Group
-
-## Media Coverage
-* [Meet the boffin behind a computer programming language based on power ballads](https://www.loudersound.com/features/meet-the-boffin-behind-a-computer-programming-language-based-on-power-ballads) in Classic Rock magazine.
-* [Rockstar article on BoingBoing](https://boingboing.net/2018/07/25/hello-cleveland-world.html)
-* [Rockstar on HackerNews](https://news.ycombinator.com/item?id=17585589)
-* [Rockstar on /r/ProgrammerHumor](https://www.reddit.com/r/ProgrammerHumor/comments/934uvw/why_yes_i_am_a_certified_rockstar_developer/)
-* [Rockstar on dice.com](https://insights.dice.com/2018/07/27/rockstar-programming-language-developers/)
-* [The Rockstar Programming Language | Mitigated Frenzy](https://bparsia.wordpress.com/2018/09/11/the-rockstar-programming-language/) - see also their solution to the [Rainfall problem](https://bparsia.wordpress.com/2018/09/12/rockstar-rainfall-problem/#comment-1624)
-* [Rockstar reference on Coding Blocks Podcast](https://www.codingblocks.net/podcast/lightning-talks/)
-* [Rockstar on .NET Rocks! Podcast](https://www.dotnetrocks.com/?show=1636)
-
-## See Also
-
-* [Enterprise™](https://github.com/joaomilho/Enterprise) - "The world is not made only of Rockstar programmers. For all the non hipsters out there, now there's Enterprise™"
+
+```
+codewithrockstar.com
+ /wasm --> [ /Starship/Rockstar.Wasm/bin/Debug/net8.0-browser ]
+ /examples --> [ /Starship/Rockstar.Test/programs/examples ]
+ /index.html
+ /example.md
+ /js
+ /rockstar-editor.js (from codemirror)
+```
+
+
+Function currying
+
+```
+output
+ function call: product
+ function call: sum
+ number: 2
+ number: 4
+ function call: sum
+ number: 5
+ number: 6
+```
+
+So: `product(sum(2,4,sum(5,6))` needs to be translated to `product(sum(2,4),sum(5,6))` based on the arity of the functions
+
+So `sum(2,4,sum(5,6))` needs to evaluate `sum(2,4)` and leave the expression `sum(5,6)` in the bucket
+
+Then `product(sum(2,4)`
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Expressions/Binary.cs b/Starship/Rockstar.Engine/Expressions/Binary.cs
new file mode 100644
index 00000000..fa246ad4
--- /dev/null
+++ b/Starship/Rockstar.Engine/Expressions/Binary.cs
@@ -0,0 +1,77 @@
+using System.Text;
+using Rockstar.Engine.Values;
+
+namespace Rockstar.Engine.Expressions;
+
+public class Binary : Expression {
+
+ public bool ShouldUpdatePronounSubject(out Variable subject) {
+ subject = new SimpleVariable("__NOPE__");
+ if (!op.IsComparison() || lhs is not Lookup { Variable: not Pronoun } lookup) return false;
+ subject = lookup.Variable;
+ return true;
+
+ }
+
+ private readonly Operator op;
+ private readonly Expression lhs;
+ private readonly IEnumerable rhs;
+
+ public Binary(Operator op, Expression lhs, Expression rhs) {
+ this.op = op;
+ this.lhs = lhs;
+ this.rhs = new List { rhs };
+ }
+
+ public Binary(Operator op, Expression lhs, IEnumerable rhs) {
+ this.op = op;
+ this.lhs = lhs;
+ this.rhs = rhs;
+ }
+
+ private Booleän Equäls(Value lhs, Value rhs) => (lhs, rhs) switch {
+ (Booleän b, _) => b.Equäls(rhs),
+ (_, Booleän b) => b.Equäls(lhs),
+ (Strïng s, _) => s.Equäls(rhs),
+ (_, Strïng s) => s.Equäls(lhs),
+ _ => lhs.Equäls(rhs)
+ };
+
+ public Value Resolve(Func eval) {
+ var v = eval(lhs);
+ return op switch {
+ Operator.Times => v * rhs.Select(eval),
+ Operator.Divide => v / rhs.Select(eval),
+ Operator.Plus => v + rhs.Select(eval),
+ Operator.Minus => v - rhs.Select(eval),
+
+ Operator.Equals => Equäls(v, eval(rhs.Single())),
+ Operator.NotEquals => Equäls(v, eval(rhs.Single())).Nope,
+ Operator.IdenticalTo => v.IdenticalTo(eval(rhs.Single())),
+ Operator.NotIdenticalTo => v.IdenticalTo(eval(rhs.Single())).Nope,
+
+ Operator.LessThanEqual => v.LessThanEqual(eval(rhs.Single())),
+ Operator.MoreThanEqual => v.MoreThanEqual(eval(rhs.Single())),
+ Operator.LessThan => v.LessThan(eval(rhs.Single())),
+ Operator.MoreThan => v.MoreThan(eval(rhs.Single())),
+
+ Operator.Nor => new Booleän(!(v.Truthy || eval(rhs.Single()).Truthy)),
+ Operator.And => v.Truthy ? eval(rhs.Single()) : v,
+ Operator.Or => v.Truthy ? v : eval(rhs.Single()),
+ _ => throw new($"I don't know how to apply {op} to {v.GetType().Name}")
+ };
+ }
+
+ public override StringBuilder Print(StringBuilder sb, string prefix) {
+ sb.Append(prefix).AppendLine($"{op}:".ToLowerInvariant());
+ lhs.Print(sb, prefix + INDENT);
+ foreach (var expr in rhs) expr.Print(sb, prefix + INDENT);
+ return sb;
+ }
+
+ public override string ToString() {
+ var sb = new StringBuilder();
+ this.Print(sb, String.Empty);
+ return sb.ToString();
+ }
+}
diff --git a/Starship/Rockstar.Engine/Expressions/CommonVariable.cs b/Starship/Rockstar.Engine/Expressions/CommonVariable.cs
new file mode 100644
index 00000000..56ac5ed6
--- /dev/null
+++ b/Starship/Rockstar.Engine/Expressions/CommonVariable.cs
@@ -0,0 +1,16 @@
+using System.Text.RegularExpressions;
+
+namespace Rockstar.Engine.Expressions;
+
+public class CommonVariable(string name) : Variable(name) {
+ public override string Key => NormalizedName.ToLowerInvariant();
+}
+
+public class ProperVariable : Variable {
+ private static readonly Regex validator = new("^(\\p{Lu}\\w*\\W+)+(\\p{Lu}\\w*)$");
+ public ProperVariable(string name) : base(name) {
+ if (!validator.IsMatch(name)) throw new ArgumentException($"'{name}' is not a valid proper variable name");
+ }
+
+ public override string Key => NormalizedName.ToUpperInvariant();
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Expressions/Dequeue.cs b/Starship/Rockstar.Engine/Expressions/Dequeue.cs
new file mode 100644
index 00000000..054dbd53
--- /dev/null
+++ b/Starship/Rockstar.Engine/Expressions/Dequeue.cs
@@ -0,0 +1,15 @@
+using System.Text;
+
+namespace Rockstar.Engine.Expressions;
+
+public abstract class VariableExpression(Variable variable) : Expression {
+ public Variable Variable => variable;
+ public override StringBuilder Print(StringBuilder sb, string prefix) {
+ base.Print(sb, prefix);
+ return variable.Print(sb, prefix + INDENT);
+ }
+}
+
+public class Dequeue(Variable variable) : VariableExpression(variable);
+
+public class Pop(Variable variable) : VariableExpression(variable);
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Expressions/Enlist.cs b/Starship/Rockstar.Engine/Expressions/Enlist.cs
new file mode 100644
index 00000000..94ed45a2
--- /dev/null
+++ b/Starship/Rockstar.Engine/Expressions/Enlist.cs
@@ -0,0 +1,23 @@
+using System.Text;
+using Rockstar.Engine.Statements;
+
+namespace Rockstar.Engine.Expressions;
+
+public class Enlist(Variable variable) : Statement {
+
+ public Variable Variable { get; } = variable;
+ public List Expressions = [];
+
+ public override StringBuilder Print(StringBuilder sb, string prefix) {
+ base.Print(sb, prefix);
+ Variable.Print(sb, prefix + INDENT);
+ foreach (var expr in Expressions) expr.Print(sb, prefix + INDENT);
+ return sb;
+ }
+
+ public Enlist(Variable variable, Expression expr) : this(variable)
+ => Expressions.Add(expr);
+
+ public Enlist(Variable variable, IEnumerable list) : this(variable)
+ => Expressions.AddRange(list);
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Expressions/Expression.cs b/Starship/Rockstar.Engine/Expressions/Expression.cs
new file mode 100644
index 00000000..e236e556
--- /dev/null
+++ b/Starship/Rockstar.Engine/Expressions/Expression.cs
@@ -0,0 +1,13 @@
+using System.Text;
+
+namespace Rockstar.Engine.Expressions;
+
+public abstract class Expression {
+ public const string INDENT = " ";
+
+ public virtual StringBuilder Print(StringBuilder sb, string prefix)
+ => sb.Append(prefix).Append(this.GetType().Name.ToLowerInvariant()).AppendLine(":");
+
+ public IEnumerable Concat(IEnumerable tail)
+ => new List { this }.Concat(tail);
+}
diff --git a/Starship/Rockstar.Engine/Expressions/Lookup.cs b/Starship/Rockstar.Engine/Expressions/Lookup.cs
new file mode 100644
index 00000000..0401970a
--- /dev/null
+++ b/Starship/Rockstar.Engine/Expressions/Lookup.cs
@@ -0,0 +1,11 @@
+using System.Text;
+
+namespace Rockstar.Engine.Expressions;
+
+public class Lookup(Variable variable) : Expression {
+ public Variable Variable => variable;
+ public override StringBuilder Print(StringBuilder sb, string prefix)
+ => sb.Append(prefix).Append("lookup: ").Append(variable.Name).AppendLine(variable.PrintIndexes());
+
+ public override string ToString() => $"lookup: {Variable}";
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Expressions/Operator.cs b/Starship/Rockstar.Engine/Expressions/Operator.cs
new file mode 100644
index 00000000..37cafab4
--- /dev/null
+++ b/Starship/Rockstar.Engine/Expressions/Operator.cs
@@ -0,0 +1,23 @@
+namespace Rockstar.Engine.Expressions;
+
+public enum Operator {
+ Plus,
+ Minus,
+ Times,
+ Divide,
+ And,
+ Or,
+ Equals,
+ IdenticalTo,
+ NotEquals,
+ NotIdenticalTo,
+ Not,
+ Nor,
+ LessThanEqual,
+ MoreThanEqual,
+ LessThan,
+ MoreThan,
+ Split,
+ Join,
+ Cast
+}
diff --git a/Starship/Rockstar.Engine/Expressions/OperatorExpressions.cs b/Starship/Rockstar.Engine/Expressions/OperatorExpressions.cs
new file mode 100644
index 00000000..785a9673
--- /dev/null
+++ b/Starship/Rockstar.Engine/Expressions/OperatorExpressions.cs
@@ -0,0 +1,15 @@
+namespace Rockstar.Engine.Expressions;
+
+public static class OperatorExpressions {
+ public static bool IsComparison(this Operator op) => op switch {
+ Operator.Equals => true,
+ Operator.NotEquals => true,
+ Operator.IdenticalTo => true,
+ Operator.NotIdenticalTo => true,
+ Operator.LessThanEqual => true,
+ Operator.MoreThanEqual => true,
+ Operator.LessThan => true,
+ Operator.MoreThan => true,
+ _ => false
+ };
+}
diff --git a/Starship/Rockstar.Engine/Expressions/Pronoun.cs b/Starship/Rockstar.Engine/Expressions/Pronoun.cs
new file mode 100644
index 00000000..28e10d3a
--- /dev/null
+++ b/Starship/Rockstar.Engine/Expressions/Pronoun.cs
@@ -0,0 +1,12 @@
+using System.Text;
+
+namespace Rockstar.Engine.Expressions;
+
+public class Pronoun(string name) : Variable(name) {
+ public Pronoun() : this(String.Empty) { }
+ public override string Key => throw new InvalidOperationException("Pronouns don't have keys.");
+ public override StringBuilder Print(StringBuilder sb, string prefix)
+ => sb.Append(prefix).AppendLine(this.ToString());
+
+ public override string ToString() => $"pronoun: {Name}";
+}
diff --git a/Starship/Rockstar.Engine/Expressions/ProperVariable.cs b/Starship/Rockstar.Engine/Expressions/ProperVariable.cs
new file mode 100644
index 00000000..9857e8db
--- /dev/null
+++ b/Starship/Rockstar.Engine/Expressions/ProperVariable.cs
@@ -0,0 +1,5 @@
+//namespace Rockstar.Engine.Expressions;
+
+//public class ProperVariable(string name) : Variable(name) {
+// public override string Key => NormalizedName.ToUpperInvariant();
+//}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Expressions/SimpleVariable.cs b/Starship/Rockstar.Engine/Expressions/SimpleVariable.cs
new file mode 100644
index 00000000..e01659e0
--- /dev/null
+++ b/Starship/Rockstar.Engine/Expressions/SimpleVariable.cs
@@ -0,0 +1,11 @@
+using System.Text.RegularExpressions;
+
+namespace Rockstar.Engine.Expressions;
+
+public class SimpleVariable : Variable {
+ private static readonly Regex illegalCharacters = new("\\W+", RegexOptions.Compiled);
+ public SimpleVariable(string name) :base(name) {
+ if (illegalCharacters.IsMatch(name)) throw new ArgumentException($"{name} is not a valid simple variable name");
+ }
+ public override string Key => Name.ToLowerInvariant();
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Expressions/Unary.cs b/Starship/Rockstar.Engine/Expressions/Unary.cs
new file mode 100644
index 00000000..4a14beda
--- /dev/null
+++ b/Starship/Rockstar.Engine/Expressions/Unary.cs
@@ -0,0 +1,33 @@
+using System.Text;
+using Rockstar.Engine.Values;
+
+namespace Rockstar.Engine.Expressions;
+
+public class Unary(Operator op, Expression expr)
+ : Expression {
+ public override StringBuilder Print(StringBuilder sb, string prefix) {
+ sb.Append(prefix).Append("unary: ").AppendLine(op.ToString().ToLowerInvariant());
+ return expr.Print(sb, prefix + INDENT);
+ }
+
+ public Value Resolve(Func eval) {
+ var v = eval(expr);
+ return op switch {
+ Operator.Not => Booleän.Not(v),
+ _ => throw new InvalidOperationException($"Can't apply {op} as a unary operator.")
+
+
+ //Operator.Equals => v.Equäls(eval(rhs.Single())),
+ //Operator.NotEquals => v.NotEquäls(eval(rhs.Single())),
+ //Operator.LessThanEqual => v.LessThanEqual(eval(rhs.Single())),
+ //Operator.MoreThanEqual => v.MoreThanEqual(eval(rhs.Single())),
+ //Operator.LessThan => v.LessThan(eval(rhs.Single())),
+ //Operator.MoreThan => v.MoreThan(eval(rhs.Single())),
+
+ //Operator.Nor => new Booleän(v.Falsey && eval(rhs.Single()).Falsey),
+ //Operator.And => v.Truthy ? eval(rhs.Single()) : v,
+ //Operator.Or => v.Truthy ? v : eval(rhs.Single()),
+ };
+ }
+
+}
diff --git a/Starship/Rockstar.Engine/Expressions/Variable.cs b/Starship/Rockstar.Engine/Expressions/Variable.cs
new file mode 100644
index 00000000..99eb774e
--- /dev/null
+++ b/Starship/Rockstar.Engine/Expressions/Variable.cs
@@ -0,0 +1,55 @@
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace Rockstar.Engine.Expressions;
+
+public abstract class Variable(string name) : Expression {
+ public string Name => name;
+
+ public override string ToString()
+ => $"{GetType().Name.ToLowerInvariant()}: {Key}"
+ + (Indexes.Any() ? "[" + String.Join("][", Indexes.Select(i => i.ToString())) + "]" : "");
+
+
+ public override StringBuilder Print(StringBuilder sb, string prefix) {
+ sb.Append(prefix).AppendLine($"variable: {name}");
+ switch (Indexes.Count) {
+ case 0: return sb;
+ case 1:
+ sb.Append(INDENT).AppendLine("index:");
+ break;
+ default:
+ sb.Append(INDENT).AppendLine("indexes:");
+ break;
+ }
+ foreach (var index in Indexes) index.Print(sb, prefix + INDENT);
+ return sb;
+ }
+
+ private static readonly Regex whitespace = new("\\s+", RegexOptions.Compiled);
+
+ protected string NormalizedName
+ => String.Join("_", whitespace.Split(Name));
+
+ public abstract string Key { get; }
+
+ public IEnumerable Concat(IEnumerable tail)
+ => new List { this }.Concat(tail);
+
+ public List Indexes { get; } = [];
+
+ public Variable AtIndex(Expression index) {
+ Indexes.Add(index);
+ return this;
+ }
+
+ public Variable AtIndex(IEnumerable indexes) {
+ Indexes.AddRange(indexes);
+ return this;
+ }
+
+ public string PrintIndexes() {
+ if (Indexes.Any()) return ("[" + String.Join(",", Indexes.Select(i => i.ToString()).ToArray()) + "]");
+ return String.Empty;
+ }
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/IRockstarIO.cs b/Starship/Rockstar.Engine/IRockstarIO.cs
new file mode 100644
index 00000000..e7564646
--- /dev/null
+++ b/Starship/Rockstar.Engine/IRockstarIO.cs
@@ -0,0 +1,6 @@
+namespace Rockstar.Engine;
+
+public interface IRockstarIO {
+ public string? Read();
+ public void Write(string s);
+}
diff --git a/Starship/Rockstar.Engine/PegasusParserExtensions.cs b/Starship/Rockstar.Engine/PegasusParserExtensions.cs
new file mode 100644
index 00000000..346e3cc6
--- /dev/null
+++ b/Starship/Rockstar.Engine/PegasusParserExtensions.cs
@@ -0,0 +1,12 @@
+using System.Text.RegularExpressions;
+using Pegasus.Common;
+
+namespace Rockstar.Engine;
+
+public static class PegasusParserExtensions {
+ public static Source Source(this Cursor cursor, string lexeme = "")
+ => new(cursor.Line, cursor.Column, lexeme);
+
+ public static string Error(this Cursor cursor, string unexpected)
+ => "Unexpected '" + Regex.Escape(unexpected) + "' at line " + cursor.Line + ", col " + (cursor.Column - 1);
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Result.cs b/Starship/Rockstar.Engine/Result.cs
new file mode 100644
index 00000000..d61ed6f1
--- /dev/null
+++ b/Starship/Rockstar.Engine/Result.cs
@@ -0,0 +1,22 @@
+using Rockstar.Engine.Values;
+
+namespace Rockstar.Engine;
+
+public class Result(Value value, WhatToDo whatToDo = WhatToDo.Next) {
+ public override string ToString() => WhatToDo switch {
+ WhatToDo.Return => $"value: {value}",
+ WhatToDo.Skip => "skip",
+ WhatToDo.Break => "break",
+ WhatToDo.Next => "next",
+ WhatToDo.Exit => "exit",
+ _ => "unknown"
+ };
+
+ public Value Value => value;
+ public WhatToDo WhatToDo => whatToDo;
+ public static Result Skip => new(new Nüll(), WhatToDo.Skip);
+ public static Result Break => new(new Nüll(), WhatToDo.Break);
+ public static Result Unknown = new(new Nüll(), WhatToDo.Unknown);
+ public static Result Return(Value value) => new(value, WhatToDo.Return);
+ public static Result Exit => new(new Nüll(), WhatToDo.Exit);
+}
diff --git a/Starship/Rockstar.Engine/Rockstar.Engine.csproj b/Starship/Rockstar.Engine/Rockstar.Engine.csproj
new file mode 100644
index 00000000..5a8b845b
--- /dev/null
+++ b/Starship/Rockstar.Engine/Rockstar.Engine.csproj
@@ -0,0 +1,18 @@
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
diff --git a/Starship/Rockstar.Engine/Rockstar.Engine.v3.ncrunchproject b/Starship/Rockstar.Engine/Rockstar.Engine.v3.ncrunchproject
new file mode 100644
index 00000000..8a3f8899
--- /dev/null
+++ b/Starship/Rockstar.Engine/Rockstar.Engine.v3.ncrunchproject
@@ -0,0 +1,7 @@
+
+
+
+ rockstar.peg
+
+
+
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/RockstarEnvironment.cs b/Starship/Rockstar.Engine/RockstarEnvironment.cs
new file mode 100644
index 00000000..92ec4918
--- /dev/null
+++ b/Starship/Rockstar.Engine/RockstarEnvironment.cs
@@ -0,0 +1,393 @@
+using System.Text.RegularExpressions;
+using Rockstar.Engine.Expressions;
+using Rockstar.Engine.Statements;
+using Rockstar.Engine.Values;
+
+namespace Rockstar.Engine;
+
+public enum Scope {
+ Global,
+ Local
+}
+
+public class RockstarEnvironment(IRockstarIO io) {
+ // This line will be automatically overwritten by GitHub Actions
+ // when the engine is built.
+ public const string VERSION = "v0.11.4";
+
+ private const string ARGUMENTS_ARRAY_NAME = "__arguments__";
+ public static CommonVariable Arguments = new CommonVariable(ARGUMENTS_ARRAY_NAME);
+
+ public RockstarEnvironment(IRockstarIO io, IEnumerable args) : this(io) {
+ var argsArray = new Arräy(args.Select(arg => new Strïng(arg)));
+ this.SetVariable(new CommonVariable(ARGUMENTS_ARRAY_NAME), argsArray);
+ }
+
+ public RockstarEnvironment(IRockstarIO io, RockstarEnvironment parent) : this(io) {
+ Parent = parent;
+ }
+
+ public RockstarEnvironment? Parent { get; init; }
+
+ public RockstarEnvironment Extend() => new(IO, this);
+
+ protected IRockstarIO IO = io;
+
+ public string? ReadInput() => IO.Read();
+ public void Write(string output) => IO.Write(output);
+
+ private Variable? pronounSubject;
+ internal void UpdatePronounSubject(Variable variable) => pronounSubject = variable;
+
+ private Variable QualifyPronoun(Variable variable) =>
+ variable is Pronoun pronoun
+ ? pronounSubject ?? throw new($"You must assign a variable before using a pronoun ('{pronoun.Name}')")
+ : variable;
+
+ private readonly Dictionary variables = new();
+
+ private bool Owns(Variable variable)
+ => variables.ContainsKey(variable.Key);
+
+ private RockstarEnvironment FindStore(Variable variable) {
+ if (Parent == default) return this;
+ if (this.Owns(variable)) return this;
+ return Parent.FindStore(variable);
+ }
+
+ public RockstarEnvironment GetStore(Variable variable, Scope scope) => scope switch {
+ Scope.Global => FindStore(variable),
+ _ => this
+ };
+
+
+ public Result SetVariable(Variable variable, Value value, Scope scope = Scope.Global) {
+ var target = QualifyPronoun(variable);
+ var store = GetStore(target, scope);
+ if (variable is not Pronoun) UpdatePronounSubject(target);
+ var indexes = variable.Indexes.Select(Eval).ToList();
+ var stored = store.SetLocal(target, indexes, value);
+ return new(stored);
+ }
+
+ private Value SetLocal(Variable variable, Value value) => SetLocal(variable, [], value);
+
+ private Value SetLocal(Variable variable, IList indexes, Value value) {
+ if (!indexes.Any()) return variables[variable.Key] = value;
+ variables.TryAdd(variable.Key, new Arräy());
+ return variables[variable.Key] switch {
+ Arräy array => array.Set(indexes, value),
+ Strïng s => s.SetCharAt(indexes, value),
+ Numbër n => n.SetBit(indexes, value),
+ _ => throw new($"{variable.Name} is not an indexed variable")
+ };
+ }
+
+ public Result Execute(Quine quine) {
+ Write(quine.Source);
+ return Result.Exit;
+ }
+
+ public Result Execute(Program program)
+ => program.Blocks.Aggregate(Result.Unknown, (_, block) => Execute(block));
+
+ internal Result Execute(Block block) {
+ var result = Result.Unknown;
+ foreach (var statement in block.Statements) {
+ result = Execute(statement);
+ switch (result.WhatToDo) {
+ case WhatToDo.Exit: return result;
+ case WhatToDo.Skip: return result;
+ case WhatToDo.Break: return result;
+ case WhatToDo.Return: return result;
+ }
+ }
+
+ return result;
+ }
+
+ private Result Execute(Statement statement) => statement switch {
+ Output output => Output(output),
+ Declare declare => Declare(declare),
+ Assign assign => Assign(assign),
+ Loop loop => Loop(loop),
+ Conditional cond => Conditional(cond),
+ FunctionCall call => Call(call),
+ Return r => Return(r),
+ Exit => Result.Exit,
+ Continue => Result.Skip,
+ Break => Result.Break,
+ Enlist e => Enlist(e),
+ Mutation m => Mutation(m),
+ Rounding r => Rounding(r),
+ Listen listen => Listen(listen),
+ Crement crement => Crement(crement),
+ Debug debug => Debug(debug),
+ ExpressionStatement e => ExpressionStatement(e),
+ Ninja n => Ninja(n),
+ ForInLoop loop => ForInLoop(loop),
+ ForOfLoop loop => ForOfLoop(loop),
+ _ => throw new($"I don't know how to execute {statement.GetType().Name} statements")
+ };
+
+ private Result Ninja(Ninja ninja) {
+ var value = Strïng.Empty;
+ value.Append(Eval(ninja.Numbër));
+ return Assign(ninja.Variable, value);
+ }
+
+ private Result Output(Output output) {
+ var value = Eval(output.Expression);
+ Write(Regex.Unescape(value.ToStrïng().Value));
+ Write(output.Suffix);
+ return new(value);
+ }
+
+ private Result Debug(Debug debug) {
+ var value = Eval(debug.Expression);
+ Write("DEBUG: ");
+ if (debug.Expression is Lookup lookup) Write(lookup.Variable.Name + ": ");
+ Write(value.GetType().Name.ToLowerInvariant() + " " + value switch {
+ Strïng s => "\"" + s.Value + "\"",
+ _ => value.ToStrïng().Value
+ });
+ Write(Environment.NewLine);
+ return new(value);
+ }
+
+ private Result Crement(Crement crement) {
+ var variable = QualifyPronoun(crement.Variable);
+ return Eval(variable) switch {
+ Nüll n => Assign(variable, new Numbër(crement.Delta)),
+ Booleän b => crement.Delta % 2 == 0 ? new(b) : Assign(variable, b.Negate),
+ IHaveANumber n => Assign(variable, new Numbër(n.Value + crement.Delta)),
+ Strïng s => s.IsEmpty ? Assign(variable, new Numbër(crement.Delta)) : throw new($"Cannot increment '{variable.Name}' - strings can only be incremented if they're empty"),
+ { } v => throw new($"Cannot increment '{variable.Name}' because it has type {v.GetType().Name}")
+ };
+ }
+
+ private Result Listen(Listen l) {
+ var input = ReadInput();
+ Value value = input == default ? new Nüll() : new Strïng(input);
+ if (l.Variable != default) SetVariable(l.Variable, value);
+ return new(value);
+ }
+
+ private Result Mutation(Mutation m) {
+ var source = Eval(m.Expression);
+ var modifier = m.Modifier == null ? null : Eval(m.Modifier);
+ var result = m.Operator switch {
+ Operator.Join => Join(source, modifier),
+ Operator.Split => Split(source, modifier),
+ Operator.Cast => Cast(source, modifier),
+ _ => throw new($"Unsupported mutation operator {m.Operator}")
+ };
+ if (m.Target == default) return new(result);
+ SetLocal(QualifyPronoun(m.Target), [], result);
+ if (m.Target is not Pronoun) UpdatePronounSubject(m.Target);
+ return new(result);
+ }
+
+ private static Value Cast(Value source, Value? modifier) {
+ return source switch {
+ Strïng s => modifier switch {
+ IHaveANumber numberBase => Numbër.Parse(s, numberBase),
+ _ => s.ToCharCodes()
+ },
+ IHaveANumber n => new Strïng(Char.ConvertFromUtf32((int) n.Value)),
+ _ => throw new($"Can't cast expression of type {source.GetType().Name}")
+ };
+ }
+
+ private static Arräy Split(Value source, Value? modifier) {
+ if (source is not Strïng s) throw new("Only strings can be split.");
+ var splitter = modifier?.ToStrïng() ?? Strïng.Empty;
+ return s.Split(splitter);
+ }
+
+ private static Value Join(Value source, Value? joiner) {
+ if (source is not Arräy array) throw new("Can't join something which is not an array.");
+ return array.Join(joiner);
+ }
+
+ private Result Rounding(Rounding r) {
+ var value = Lookup(r.Variable);
+ if (value is not Numbër n) throw new($"Can't apply rounding to variable {r.Variable.Name} of type {value.GetType().Name}");
+ var rounded = new Numbër(r.Round switch {
+ Round.Down => Math.Floor(n.Value),
+ Round.Up => Math.Ceiling(n.Value),
+ Round.Nearest => Math.Round(n.Value),
+ _ => throw new ArgumentOutOfRangeException()
+ });
+ SetVariable(r.Variable, rounded);
+ return new(rounded);
+ }
+
+ private Result Pop(Pop pop) {
+ var variable = QualifyPronoun(pop.Variable);
+ var value = LookupValue(variable.Key);
+ return value switch {
+ Arräy array => new(array.Pop()),
+ Strïng strïng => new(strïng.Pop()),
+ _ => new(Nüll.Instance)
+ };
+ }
+
+ private Result Dequeue(Dequeue dequeue) {
+ var variable = QualifyPronoun(dequeue.Variable);
+ var value = LookupValue(variable.Key);
+ return value switch {
+ Arräy array => new(array.Dequeue()),
+ Strïng strïng => new(strïng.Dequeue()),
+ _ => new(Nüll.Instance)
+ };
+ }
+
+ private Result Enlist(Enlist e) {
+ var variable = QualifyPronoun(e.Variable);
+ var value = LookupValue(variable.Key);
+ if (value is Strïng s) {
+ foreach (var expr in e.Expressions) s.Append(Eval(expr));
+ return new(s);
+ }
+
+ if (value is not Arräy array) {
+ array = value == Mysterious.Instance ? new Arräy() : new(value);
+ SetLocal(variable, array);
+ }
+ foreach (var expr in e.Expressions) array.Push(Eval(expr));
+ return new(array);
+ }
+
+ private Result ExpressionStatement(ExpressionStatement e) => new(Eval(e.Expression));
+
+ private Result Return(ExpressionStatement r) {
+ var value = Eval(r.Expression);
+ return Result.Return(value);
+ }
+
+ private Result Call(FunctionCall call)
+ => Call(call, []);
+
+ private Result Call(FunctionCall call, Queue bucket) {
+ var value = Lookup(call.Function);
+ if (value is not Closure closure) throw new($"'{call.Function.Name}' is not a function");
+ var names = closure.Functiön.Args.ToList();
+
+ List values = [];
+
+ foreach (var arg in call.Args.Take(names.Count)) {
+ value = arg is FunctionCall nestedCall ? Call(nestedCall, bucket).Value : Eval(arg);
+ values.Add(value);
+ }
+ if (call.Args.Count + bucket.Count < names.Count) {
+ throw new($"Not enough arguments supplied to function {call.Function.Name} - expected {names.Count} ({String.Join(", ", names.Select(v => v.Name))}), got {call.Args.Count}");
+ }
+ while (values.Count < names.Count) values.Add(Eval(bucket.Dequeue()));
+ foreach (var expression in call.Args.Skip(names.Count)) bucket.Enqueue(expression);
+ Dictionary args = new();
+ for (var i = 0; i < names.Count; i++) args[names[i]] = values[i].Clone();
+ return new(closure.Apply(args).Value);
+ }
+
+ private Expression UpdatePronounSubjectBasedOnSubjectOfCondition(Expression condition) {
+ if (condition is Binary binary && binary.ShouldUpdatePronounSubject(out var subject)) UpdatePronounSubject(subject);
+ return condition;
+ }
+
+ private Result Conditional(Conditional cond) {
+ UpdatePronounSubjectBasedOnSubjectOfCondition(cond.Condition);
+ if (Eval(cond.Condition).Truthy) return Execute(cond.Consequent);
+ return cond.Alternate != default ? Execute(cond.Alternate) : Result.Unknown;
+ }
+
+
+ private Result ForInLoop(ForInLoop loop) {
+ var result = Result.Unknown;
+ var array = Eval(loop.Expression) as Arräy;
+ if (array is null) throw new Exception("Can't use for-in loops on something that is not an array");
+ var scope = this.Extend();
+ for (var i = 0; i < array.List.Count; i++) {
+ scope.SetVariable(loop.Value, array.List[i], Scope.Local);
+ if (loop.Index != null) scope.SetVariable(loop.Index, new Numbër(i), Scope.Local);
+ result = scope.Execute(loop.Body);
+ switch (result.WhatToDo) {
+ case WhatToDo.Skip: continue;
+ case WhatToDo.Break: return new(result.Value);
+ case WhatToDo.Return: return result;
+ }
+ }
+ return result;
+ }
+
+ private Result ForOfLoop(ForOfLoop loop) {
+ var result = Result.Unknown;
+ var array = Eval(loop.Expression) as Arräy;
+ if (array is null) throw new Exception("Can't use for-of loops on something that is not an array");
+ var scope = this.Extend();
+ foreach (var pair in array.Hash) {
+ scope.SetVariable(loop.Value, pair.Value, Scope.Local);
+ if (loop.Index != null) scope.SetVariable(loop.Index, pair.Key, Scope.Local);
+ result = scope.Execute(loop.Body);
+ switch (result.WhatToDo) {
+ case WhatToDo.Skip: continue;
+ case WhatToDo.Break: return new(result.Value);
+ case WhatToDo.Return: return result;
+ }
+ }
+ return result;
+ }
+
+ private Result Loop(Loop loop) {
+ var result = Result.Unknown;
+ while (Eval(loop.Condition).Truthy == loop.CompareTo) {
+ UpdatePronounSubjectBasedOnSubjectOfCondition(loop.Condition);
+ result = Execute(loop.Body);
+ switch (result.WhatToDo) {
+ case WhatToDo.Skip: continue;
+ case WhatToDo.Break: return new(result.Value);
+ case WhatToDo.Return: return result;
+ }
+ }
+ return result;
+ }
+
+ public Value Eval(Expression expression) => expression switch {
+ Value value => value,
+ Binary binary => binary.Resolve(Eval),
+ Lookup lookup => Lookup(lookup.Variable),
+ Variable v => Lookup(v),
+ Unary unary => unary.Resolve(Eval),
+ FunctionCall call => Call(call).Value,
+ Pop pop => Pop(pop).Value,
+ Dequeue delist => Dequeue(delist).Value,
+ _ => throw new NotImplementedException($"Eval not implemented for {expression.GetType()}")
+ };
+
+ private Result Declare(Declare declare) {
+ var value = declare.Expression == default ? Mysterious.Instance : Eval(declare.Expression);
+ return Assign(declare.Variable, value, Scope.Local);
+ }
+
+ public Result Assign(Assign assign)
+ => Assign(assign.Variable, Eval(assign.Expression), Scope.Global);
+
+ public Result Assign(Variable variable, Value value, Scope scope = Scope.Global) => value switch {
+ Functiön function => SetVariable(variable, MakeLambda(function, variable), Scope.Local),
+ _ => SetVariable(variable, value, scope)
+ };
+
+ private Value MakeLambda(Functiön functiön, Variable variable)
+ => this.Parent == default ? new(functiön, variable, this.Extend()) : new Closure(functiön, variable, this);
+
+ private Value LookupValue(string key) {
+ if (variables.TryGetValue(key, out var value)) return value;
+ return Parent != default ? Parent.LookupValue(key) : Mysterious.Instance;
+ }
+
+ public Value Lookup(Variable variable) {
+ var key = variable is Pronoun pronoun ? QualifyPronoun(pronoun).Key : variable.Key;
+ var value = LookupValue(key);
+ return value.AtIndex(variable.Indexes.Select(Eval));
+ }
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Source.cs b/Starship/Rockstar.Engine/Source.cs
new file mode 100644
index 00000000..83932a91
--- /dev/null
+++ b/Starship/Rockstar.Engine/Source.cs
@@ -0,0 +1,9 @@
+namespace Rockstar.Engine;
+
+public class Source(int line, int column, string lexeme = "") {
+
+ public string Location =>
+ $"(line {line}, column {column - lexeme.Length} [{lexeme}])";
+
+ public static readonly Source None = new(0, 0, "");
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Statements/Assign.cs b/Starship/Rockstar.Engine/Statements/Assign.cs
new file mode 100644
index 00000000..d329499d
--- /dev/null
+++ b/Starship/Rockstar.Engine/Statements/Assign.cs
@@ -0,0 +1,13 @@
+using System.Text;
+using Rockstar.Engine.Expressions;
+
+namespace Rockstar.Engine.Statements;
+
+public class Assign(Variable variable, Expression expression) : ExpressionStatement(expression) {
+ public Variable Variable => variable;
+ public override StringBuilder Print(StringBuilder sb, string prefix) {
+ sb.AppendLine(prefix + "assign:");
+ variable.Print(sb, prefix + INDENT);
+ return Expression.Print(sb, prefix + INDENT);
+ }
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Statements/Block.cs b/Starship/Rockstar.Engine/Statements/Block.cs
new file mode 100644
index 00000000..dd5241d2
--- /dev/null
+++ b/Starship/Rockstar.Engine/Statements/Block.cs
@@ -0,0 +1,25 @@
+using System.Text;
+
+namespace Rockstar.Engine.Statements;
+
+public class Block {
+ public List Statements { get; } = [];
+ public Block() { }
+ public Block Concat(Block tail) {
+ this.Statements.AddRange(tail.Statements);
+ return this;
+ }
+ public Block(params Statement[] statements) => this.Statements.AddRange(statements);
+
+ public override string ToString() => Print(new()).ToString();
+
+ public StringBuilder PrintTopLevel(StringBuilder sb) {
+ foreach (var stmt in Statements) stmt.Print(sb, "");
+ return sb;
+ }
+
+ public StringBuilder Print(StringBuilder sb, string prefix = "") {
+ foreach (var stmt in Statements) stmt.Print(sb, prefix + "│ ");
+ return sb.Append(prefix).AppendLine("└──────────");
+ }
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Statements/Break.cs b/Starship/Rockstar.Engine/Statements/Break.cs
new file mode 100644
index 00000000..d9700428
--- /dev/null
+++ b/Starship/Rockstar.Engine/Statements/Break.cs
@@ -0,0 +1,5 @@
+namespace Rockstar.Engine.Statements;
+
+public class Break(string wildcard = "") : WildcardStatement(wildcard) {
+ protected override string What => "break";
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Statements/Conditional.cs b/Starship/Rockstar.Engine/Statements/Conditional.cs
new file mode 100644
index 00000000..bf07abad
--- /dev/null
+++ b/Starship/Rockstar.Engine/Statements/Conditional.cs
@@ -0,0 +1,21 @@
+using System.Text;
+using Rockstar.Engine.Expressions;
+
+namespace Rockstar.Engine.Statements;
+
+public class Conditional(Expression condition, Block consequent, Block? alternate = null) : Statement {
+
+ public Expression Condition => condition;
+ public Block Consequent => consequent;
+ public Block? Alternate => alternate;
+
+ public override StringBuilder Print(StringBuilder sb, string prefix) {
+ sb.Append(prefix).AppendLine("if:");
+ condition.Print(sb, prefix);
+ sb.Append(prefix).AppendLine("then:");
+ consequent.Print(sb, prefix);
+ if (alternate == default) return sb;
+ sb.Append(prefix).AppendLine("else:");
+ return alternate.Print(sb, prefix);
+ }
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Statements/Continue.cs b/Starship/Rockstar.Engine/Statements/Continue.cs
new file mode 100644
index 00000000..f1d75d73
--- /dev/null
+++ b/Starship/Rockstar.Engine/Statements/Continue.cs
@@ -0,0 +1,5 @@
+namespace Rockstar.Engine.Statements;
+
+public class Continue(string wildcard = "") : WildcardStatement(wildcard) {
+ protected override string What => "continue";
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Statements/Crement.cs b/Starship/Rockstar.Engine/Statements/Crement.cs
new file mode 100644
index 00000000..0ea6273c
--- /dev/null
+++ b/Starship/Rockstar.Engine/Statements/Crement.cs
@@ -0,0 +1,14 @@
+using System.Text;
+using Rockstar.Engine.Expressions;
+
+namespace Rockstar.Engine.Statements;
+
+public class Crement(Variable v, int delta) : Statement {
+ public Variable Variable => v;
+ private string Direction => (delta > 0 ? "increment" : "decrement");
+ public int Delta => delta;
+ public override StringBuilder Print(StringBuilder sb, string prefix) {
+ sb.Append(prefix).AppendLine($"{Direction} x {delta}");
+ return v.Print(sb, prefix + INDENT);
+ }
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Statements/Debug.cs b/Starship/Rockstar.Engine/Statements/Debug.cs
new file mode 100644
index 00000000..6362342f
--- /dev/null
+++ b/Starship/Rockstar.Engine/Statements/Debug.cs
@@ -0,0 +1,17 @@
+using System.Text;
+using Rockstar.Engine.Expressions;
+using Rockstar.Engine.Values;
+
+namespace Rockstar.Engine.Statements;
+
+public class Debug(Expression expr) : ExpressionStatement(expr) {
+ public override StringBuilder Print(StringBuilder sb, string prefix) {
+ sb.Append(prefix).Append("debug: ");
+ return Expression switch {
+ Lookup lookup => sb.AppendLine(lookup.ToString()),
+ Value value => sb.AppendLine(value.ToString()),
+ _ => Expression.Print(sb.AppendLine(), prefix + INDENT)
+ };
+ }
+
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Statements/Declare.cs b/Starship/Rockstar.Engine/Statements/Declare.cs
new file mode 100644
index 00000000..94ff4692
--- /dev/null
+++ b/Starship/Rockstar.Engine/Statements/Declare.cs
@@ -0,0 +1,15 @@
+using System.Text;
+using Rockstar.Engine.Expressions;
+
+namespace Rockstar.Engine.Statements;
+
+public class Declare(Variable variable, Expression? expression = null) : Statement {
+ public Variable Variable => variable;
+ public Expression? Expression => expression;
+
+ public override StringBuilder Print(StringBuilder sb, string prefix) {
+ sb.AppendLine(prefix + "declare:");
+ variable.Print(sb, prefix + INDENT);
+ return (expression == default ? sb : expression.Print(sb, prefix + INDENT));
+ }
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Statements/Decrement.cs b/Starship/Rockstar.Engine/Statements/Decrement.cs
new file mode 100644
index 00000000..d5469b05
--- /dev/null
+++ b/Starship/Rockstar.Engine/Statements/Decrement.cs
@@ -0,0 +1,13 @@
+using System.Text;
+using Rockstar.Engine.Expressions;
+
+namespace Rockstar.Engine.Statements;
+
+public class Decrement(Variable v, int multiple) : Statement {
+ public Variable Variable => v;
+ public int Multiple => multiple;
+ public override StringBuilder Print(StringBuilder sb, string prefix) {
+ sb.Append(prefix).AppendLine($"decrement x {multiple}");
+ return v.Print(sb, prefix + INDENT);
+ }
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Statements/Exit.cs b/Starship/Rockstar.Engine/Statements/Exit.cs
new file mode 100644
index 00000000..5a5cbc15
--- /dev/null
+++ b/Starship/Rockstar.Engine/Statements/Exit.cs
@@ -0,0 +1,5 @@
+namespace Rockstar.Engine.Statements;
+
+public class Exit : Statement {
+
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Statements/ExpressionStatement.cs b/Starship/Rockstar.Engine/Statements/ExpressionStatement.cs
new file mode 100644
index 00000000..e22d487c
--- /dev/null
+++ b/Starship/Rockstar.Engine/Statements/ExpressionStatement.cs
@@ -0,0 +1,11 @@
+using System.Text;
+using Rockstar.Engine.Expressions;
+
+namespace Rockstar.Engine.Statements;
+
+public class ExpressionStatement(Expression expr) : Statement {
+ public Expression Expression = expr;
+
+ public override StringBuilder Print(StringBuilder sb, string prefix)
+ => Expression.Print(sb.Append(prefix).AppendLine("expression_statement:"), prefix + INDENT);
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Statements/ForInLoop.cs b/Starship/Rockstar.Engine/Statements/ForInLoop.cs
new file mode 100644
index 00000000..8cb3397a
--- /dev/null
+++ b/Starship/Rockstar.Engine/Statements/ForInLoop.cs
@@ -0,0 +1,26 @@
+using Rockstar.Engine.Expressions;
+
+namespace Rockstar.Engine.Statements;
+
+public abstract class ForLoop : Statement {
+ private readonly Variable value;
+ private readonly Variable index;
+ public Variable Value => PrependArticle && value is SimpleVariable ? new CommonVariable($"the {value.Name}") : value;
+ public Variable Index => PrependArticle && index is SimpleVariable ? new CommonVariable($"the {index.Name}") : index;
+ public Expression Expression { get; }
+ public Block Body { get; }
+ public bool PrependArticle { get; }
+
+ protected ForLoop(Variable value, Variable index, Expression expression, Block body, bool prependArticle) {
+ this.value = value;
+ this.index = index;
+ Expression = expression;
+ Body = body;
+ PrependArticle = prependArticle;
+ }
+}
+
+public class ForInLoop : ForLoop {
+ public ForInLoop(Variable value, Variable index, Expression expression, Block body, bool prependArticle)
+ : base(value, index, expression, body, prependArticle) { }
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Statements/ForOfLoop.cs b/Starship/Rockstar.Engine/Statements/ForOfLoop.cs
new file mode 100644
index 00000000..def1894a
--- /dev/null
+++ b/Starship/Rockstar.Engine/Statements/ForOfLoop.cs
@@ -0,0 +1,8 @@
+using Rockstar.Engine.Expressions;
+
+namespace Rockstar.Engine.Statements;
+
+public class ForOfLoop : ForLoop {
+ public ForOfLoop(Variable value, Variable index, Expression expression, Block body, bool prependArticle)
+ : base(value, index, expression, body, prependArticle) { }
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Statements/Listen.cs b/Starship/Rockstar.Engine/Statements/Listen.cs
new file mode 100644
index 00000000..e970816a
--- /dev/null
+++ b/Starship/Rockstar.Engine/Statements/Listen.cs
@@ -0,0 +1,15 @@
+using System.Text;
+using Rockstar.Engine.Expressions;
+
+namespace Rockstar.Engine.Statements;
+
+public class Listen : Statement {
+ public Variable? Variable { get; init; } = default;
+ public Listen() {}
+ public Listen(Variable variable) => this.Variable = variable;
+ public override StringBuilder Print(StringBuilder sb, string prefix) {
+ sb.Append(prefix).Append("listen:");
+ if (Variable != default) sb.Append(" => ").Append(Variable.Name);
+ return sb.AppendLine();
+ }
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Statements/Loop.cs b/Starship/Rockstar.Engine/Statements/Loop.cs
new file mode 100644
index 00000000..d4acbc3a
--- /dev/null
+++ b/Starship/Rockstar.Engine/Statements/Loop.cs
@@ -0,0 +1,18 @@
+using System.Text;
+using Rockstar.Engine.Expressions;
+
+namespace Rockstar.Engine.Statements;
+
+public abstract class Loop(Expression condition, Block body, bool compareTo)
+ : Statement {
+ public bool CompareTo => compareTo;
+ public Expression Condition => condition;
+ public Block Body => body;
+ protected abstract string LoopType { get; }
+ public override StringBuilder Print(StringBuilder sb, string prefix) {
+ sb.Append(prefix).AppendLine($"{LoopType}:");
+ condition.Print(sb, prefix + INDENT);
+ sb.Append(prefix).Append(INDENT).AppendLine("loop:");
+ return body.Print(sb, prefix + INDENT);
+ }
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Statements/Mutation.cs b/Starship/Rockstar.Engine/Statements/Mutation.cs
new file mode 100644
index 00000000..ff2278bb
--- /dev/null
+++ b/Starship/Rockstar.Engine/Statements/Mutation.cs
@@ -0,0 +1,24 @@
+using System.Text;
+using Rockstar.Engine.Expressions;
+
+namespace Rockstar.Engine.Statements;
+
+public class Mutation(Operator op, Expression expr, Variable? target = default, Expression? modifier = default)
+ : Statement {
+ public Operator Operator => op;
+ public Expression Expression => expr;
+ public Variable? Target => target;
+ public Expression? Modifier => modifier;
+
+ public override StringBuilder Print(StringBuilder sb, string prefix) {
+ sb.Append(prefix).Append(op.ToString().ToLowerInvariant()).AppendLine(":");
+ Expression.Print(sb, prefix + INDENT);
+ if (Target != default) {
+ sb.Append(prefix + INDENT).AppendLine("target:");
+ Target.Print(sb, prefix + INDENT + INDENT);
+ }
+ if (Modifier == default) return sb;
+ sb.Append(prefix + INDENT).AppendLine("using:");
+ return Modifier.Print(sb, prefix + INDENT + INDENT);
+ }
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Statements/Ninja.cs b/Starship/Rockstar.Engine/Statements/Ninja.cs
new file mode 100644
index 00000000..c59e5987
--- /dev/null
+++ b/Starship/Rockstar.Engine/Statements/Ninja.cs
@@ -0,0 +1,15 @@
+using System.Text;
+using Rockstar.Engine.Expressions;
+using Rockstar.Engine.Values;
+
+namespace Rockstar.Engine.Statements;
+
+public class Ninja(Variable variable, Numbër numbër) : Statement {
+ public Variable Variable => variable;
+ public Numbër Numbër => numbër;
+ public override StringBuilder Print(StringBuilder sb, string prefix) {
+ sb.AppendLine(prefix + "ninja:");
+ variable.Print(sb, prefix + INDENT);
+ return Numbër.Print(sb, prefix + INDENT);
+ }
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Statements/Output.cs b/Starship/Rockstar.Engine/Statements/Output.cs
new file mode 100644
index 00000000..a4fe99aa
--- /dev/null
+++ b/Starship/Rockstar.Engine/Statements/Output.cs
@@ -0,0 +1,13 @@
+using System.Text;
+using Rockstar.Engine.Expressions;
+
+namespace Rockstar.Engine.Statements;
+
+public class Output(Expression expr, string suffix = "") : ExpressionStatement(expr) {
+ public string Suffix { get; } = suffix;
+
+ public override StringBuilder Print(StringBuilder sb, string prefix) {
+ sb.Append(prefix).AppendLine("output: ");
+ return Expression.Print(sb, prefix + INDENT);
+ }
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Statements/Program.cs b/Starship/Rockstar.Engine/Statements/Program.cs
new file mode 100644
index 00000000..01e58324
--- /dev/null
+++ b/Starship/Rockstar.Engine/Statements/Program.cs
@@ -0,0 +1,21 @@
+using System.Text;
+
+namespace Rockstar.Engine.Statements;
+
+public class Program {
+ public List Blocks { get; } = [];
+ public Program() { }
+ public Program(params Block[] blocks) => this.Blocks.AddRange(blocks);
+ public Program Concat(Program tail) {
+ this.Blocks.AddRange(tail.Blocks);
+ return this;
+ }
+
+ public StringBuilder Print(StringBuilder sb) {
+ foreach (var block in Blocks) block.PrintTopLevel(sb);
+ return sb;
+ }
+
+ public override string ToString()
+ => Print(new()).ToString();
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Statements/Quine.cs b/Starship/Rockstar.Engine/Statements/Quine.cs
new file mode 100644
index 00000000..d6005df5
--- /dev/null
+++ b/Starship/Rockstar.Engine/Statements/Quine.cs
@@ -0,0 +1,5 @@
+namespace Rockstar.Engine.Statements;
+
+public class Quine(string source) : Program {
+ public string Source => source;
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Statements/Return.cs b/Starship/Rockstar.Engine/Statements/Return.cs
new file mode 100644
index 00000000..feb01a04
--- /dev/null
+++ b/Starship/Rockstar.Engine/Statements/Return.cs
@@ -0,0 +1,11 @@
+using System.Text;
+using Rockstar.Engine.Expressions;
+
+namespace Rockstar.Engine.Statements;
+
+public class Return(Expression expr) : ExpressionStatement(expr) {
+ public override StringBuilder Print(StringBuilder sb, string prefix) {
+ sb.Append(prefix).AppendLine("return:");
+ return Expression.Print(sb, prefix + INDENT);
+ }
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Statements/Round.cs b/Starship/Rockstar.Engine/Statements/Round.cs
new file mode 100644
index 00000000..4b3f2327
--- /dev/null
+++ b/Starship/Rockstar.Engine/Statements/Round.cs
@@ -0,0 +1,7 @@
+namespace Rockstar.Engine.Statements;
+
+public enum Round {
+ Up,
+ Down,
+ Nearest
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Statements/Rounding.cs b/Starship/Rockstar.Engine/Statements/Rounding.cs
new file mode 100644
index 00000000..099a3b84
--- /dev/null
+++ b/Starship/Rockstar.Engine/Statements/Rounding.cs
@@ -0,0 +1,8 @@
+using Rockstar.Engine.Expressions;
+
+namespace Rockstar.Engine.Statements;
+
+public class Rounding(Variable variable, Round round) : Statement {
+ public Variable Variable => variable;
+ public Round Round => round;
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Statements/Statement.cs b/Starship/Rockstar.Engine/Statements/Statement.cs
new file mode 100644
index 00000000..d9b4a865
--- /dev/null
+++ b/Starship/Rockstar.Engine/Statements/Statement.cs
@@ -0,0 +1,7 @@
+using Rockstar.Engine.Expressions;
+
+namespace Rockstar.Engine.Statements;
+
+public class Statement : Expression {
+
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Statements/UntilLoop.cs b/Starship/Rockstar.Engine/Statements/UntilLoop.cs
new file mode 100644
index 00000000..cbeea7fb
--- /dev/null
+++ b/Starship/Rockstar.Engine/Statements/UntilLoop.cs
@@ -0,0 +1,8 @@
+using Rockstar.Engine.Expressions;
+
+namespace Rockstar.Engine.Statements;
+
+public class UntilLoop(Expression condition, Block body)
+ : Loop(condition, body, false) {
+ protected override string LoopType => "until";
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Statements/WhileLoop.cs b/Starship/Rockstar.Engine/Statements/WhileLoop.cs
new file mode 100644
index 00000000..2eaa3860
--- /dev/null
+++ b/Starship/Rockstar.Engine/Statements/WhileLoop.cs
@@ -0,0 +1,8 @@
+using Rockstar.Engine.Expressions;
+
+namespace Rockstar.Engine.Statements;
+
+public class WhileLoop(Expression condition, Block body)
+ : Loop(condition, body, true) {
+ protected override string LoopType => "while";
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Statements/WildcardStatement.cs b/Starship/Rockstar.Engine/Statements/WildcardStatement.cs
new file mode 100644
index 00000000..2c43a5aa
--- /dev/null
+++ b/Starship/Rockstar.Engine/Statements/WildcardStatement.cs
@@ -0,0 +1,12 @@
+using System.Text;
+
+namespace Rockstar.Engine.Statements;
+
+public abstract class WildcardStatement(string wildcard) : Statement {
+ protected abstract string What { get; }
+ public string Wildcard => wildcard;
+ public override StringBuilder Print(StringBuilder sb, string prefix)
+ => sb.Append(prefix).Append(What)
+ .AppendLine(String.IsNullOrEmpty(wildcard) ? "" : $" (wildcard: \"{wildcard}\")");
+
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/StringBuilderIO.cs b/Starship/Rockstar.Engine/StringBuilderIO.cs
new file mode 100644
index 00000000..26b7eff2
--- /dev/null
+++ b/Starship/Rockstar.Engine/StringBuilderIO.cs
@@ -0,0 +1,12 @@
+using System.Text;
+
+namespace Rockstar.Engine;
+
+public class StringBuilderIO(Func readInput) : IRockstarIO {
+ public StringBuilderIO() : this(() => null) { }
+ private readonly StringBuilder sb = new();
+ public string? Read() => readInput();
+ public void Write(string? s) => sb.Append(s);
+ public string Output => sb.ToString();
+ public void Reset() => sb.Clear();
+}
\ No newline at end of file
diff --git "a/Starship/Rockstar.Engine/Values/Arr\303\244y.cs" "b/Starship/Rockstar.Engine/Values/Arr\303\244y.cs"
new file mode 100644
index 00000000..70d51663
--- /dev/null
+++ "b/Starship/Rockstar.Engine/Values/Arr\303\244y.cs"
@@ -0,0 +1,168 @@
+using System.Diagnostics;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices.Marshalling;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace Rockstar.Engine.Values;
+
+public class Arräy : Value, IHaveANumber {
+
+ decimal IHaveANumber.Value => Length;
+ public int IntegerValue => Length;
+
+ public List List { get; }
+ public Dictionary Hash { get; } = [];
+
+ private static Arräy Clone(Arräy source) {
+ var a = new Arräy(source.List.Select(v => v.Clone()));
+ foreach (var pair in source.Hash) a.Hash[pair.Key] = pair.Value.Clone();
+ return a;
+ }
+
+ private int Length => List.Count;
+ public Numbër Lëngth => new(Length);
+
+ public bool ArrayEquals(Arräy that)
+ => List.ValuesMatch(that.List) && Hash.ValuesMatch(that.Hash);
+
+ public Arräy(IEnumerable items) => List = [.. items];
+
+ public Arräy(Dictionary hash, IEnumerable items) {
+ this.List = [.. items];
+ this.Hash = hash.ToDictionary(pair => pair.Key.Clone(), pair => pair.Value.Clone());
+ }
+
+ public Arräy(params Value[] items) => List = [.. items];
+ public Arräy(Value item) => List = [item];
+
+ public override int GetHashCode()
+ => Hash.Values.Aggregate(0, (hashCode, value) => hashCode ^ value.GetHashCode());
+
+ public override bool Truthy => Hash.Count > 0;
+ public bool IsEmpty => Length == 0;
+
+ public override Strïng ToStrïng() => new(this.ToString());
+
+ public override string ToString() {
+ var sb = new StringBuilder();
+ sb.Append("[ ");
+ sb.AppendJoin(", ", List.Select(item => item.ToString()));
+ if (Hash.Any()) {
+ if (List.Any()) sb.Append("; ");
+ sb.AppendJoin("; ", Hash.Select(pair => pair.Key + ": " + pair.Value));
+ }
+
+ if (Hash.Any() || List.Any()) sb.Append(" ");
+ sb.Append("]");
+ return Regex.Replace(sb.ToString(), "null(, null){4,}", " ... ");
+ }
+
+ public override Booleän Equäls(Value? that)
+ => new(Equals(that));
+
+ protected override bool Equals(Value? other) => other switch {
+ Arräy array => ArrayEquals(array),
+ IHaveANumber n => Length == n.Value,
+ Mysterious m => Length == 0,
+ Strïng s => Length == 0 && s.IsEmpty,
+ _ => throw new($"I can't compare arrays with {other?.GetType().Name ?? "null"}")
+ };
+
+ public override Booleän IdenticalTo(Value that)
+ => new(Object.ReferenceEquals(this, that));
+
+ private Value Set(int index, Value value) {
+ while (index >= List.Count) List.Add(Nüll.Instance);
+ return List[index] = value;
+ }
+
+ public T Set(Value index, T value) where T : Value => index switch {
+ Numbër { IsNonNegativeInteger: true } n => (T) Set(n.IntegerValue, value),
+ _ => (T) (Hash[index] = value)
+ };
+
+ private bool TryGet(Value index, out Value? value) {
+ value = Mysterious.Instance;
+ if (index is not Numbër { IsNonNegativeInteger: true } n) return Hash.TryGetValue(index, out value);
+ var inRange = n.IntegerValue < List.Count;
+ if (inRange) value = List[n.IntegerValue];
+ return inRange;
+ }
+
+ public Arräy Nest(Value index, Arräy arräy) {
+ var found = Hash.TryGetValue(index, out var v);
+ if (found) return v as Arräy ?? throw new("Error: not an indexed variable");
+ Set(index, arräy);
+ return arräy;
+ }
+
+ public Value AtIndex(int index) => List[index];
+
+ public override Value AtIndex(Value index) => index switch {
+ Numbër { IsNonNegativeInteger: true } n => n.IntegerValue < List.Count ? List[n.IntegerValue] : Mysterious.Instance,
+ _ => Hash.GetValueOrDefault(index) ?? Mysterious.Instance
+ };
+
+ public override Value Clone() => Arräy.Clone(this);
+
+ public Strïng Join(Value? joiner)
+ => new(String.Join(joiner?.ToStrïng().Value ?? "", List.Select(value => value.ToStrïng().Value)));
+
+ public Value Push(Value value) => List.Push(value);
+
+ public Value Dequeue() => List.Shift() ?? Mysterious.Instance;
+
+ public Value Set(IList indexes, Value value) {
+ var array = this;
+ for (var i = 0; i < indexes.Count; i++) {
+ var index = indexes[i];
+ if (i == indexes.Count - 1) return array.Set(index, value);
+ array = array.Nest(index, new Arräy());
+ }
+ return value;
+ }
+
+ public Value Pop() => List.Pop() ?? Mysterious.Instance;
+
+ class HashComparer : IEqualityComparer> {
+ public bool Equals(KeyValuePair x, KeyValuePair y)
+ => x.Key.Equäls(y.Key).Truthy && x.Value.Equäls(y.Value).Truthy;
+
+ public int GetHashCode(KeyValuePair obj)
+ => HashCode.Combine(obj.Key, obj.Value);
+ }
+
+ private Arräy Except(Value v) {
+ var newHash = this.Hash.Where(pair => pair.Value.Equäls(v).Falsey).ToDictionary();
+ var newList = this.List.Where(item => item.Equäls(v).Falsey);
+ return new(newHash, newList);
+ }
+
+ private Arräy Except(Arräy that) {
+ var newHash = this.Hash.Except(that.Hash, new HashComparer()).ToDictionary();
+ var newList = this.List.Except(that.List);
+ return new(newHash, newList);
+ }
+
+ private Arräy Concat(Value v)
+ => new(Hash, this.List.Concat([v]));
+
+ private Arräy Concat(Arräy that) {
+ var newHash = this.Hash.Concat(that.Hash).ToDictionary();
+ var newList = this.List.Concat(that.List);
+ return new(newHash, newList);
+ }
+
+ public Value Subtract(Value rhs) => rhs switch {
+ Arräy array => this.Except(array),
+ _ => this.Except(rhs)
+ };
+
+ public Value Add(Value rhs) => rhs switch {
+ Arräy array => this.Concat(array),
+ Numbër n => new Numbër(this.Lëngth.Value + n.Value),
+ _ => this.Concat(rhs)
+ };
+}
\ No newline at end of file
diff --git "a/Starship/Rockstar.Engine/Values/Boole\303\244n.cs" "b/Starship/Rockstar.Engine/Values/Boole\303\244n.cs"
new file mode 100644
index 00000000..bc2f348a
--- /dev/null
+++ "b/Starship/Rockstar.Engine/Values/Boole\303\244n.cs"
@@ -0,0 +1,37 @@
+using System.Text;
+
+namespace Rockstar.Engine.Values;
+
+public class Booleän(bool value) : ValueOf(value), IHaveANumber {
+ public override StringBuilder Print(StringBuilder sb, string prefix)
+ => sb.Append(prefix).Append("boolean: ").AppendLine(this.ToStrïng().Value);
+
+ public static Booleän operator !(Booleän that) => new(that.Falsey);
+
+ public override bool Truthy => Value;
+
+ public override Strïng ToStrïng() => Value ? Strïng.True : Strïng.False;
+ public override string ToString() => Value ? "true" : "false";
+
+ public override Booleän Equäls(Value that)
+ => new(this.Truthy == that.Truthy);
+
+ public override Booleän IdenticalTo(Value that)
+ => that is Booleän ? that.Equäls(this) : False;
+
+ public override Value Clone() => this;
+
+ public static Booleän False = new(false);
+ public static Booleän True = new(true);
+ decimal IHaveANumber.Value => Value ? 1 : 0;
+ public int IntegerValue => Value ? 1 : 0;
+ public Booleän Nope => new(!Truthy);
+ public Value Negate => Not(this);
+
+ public static Value Not(Value v) => new Booleän(!v.Truthy);
+
+ public static explicit operator Booleän(bool b) => b ? True : False;
+ public static explicit operator bool(Booleän b) => b.Truthy;
+ public static bool operator true(Booleän b) => b.Truthy;
+ public static bool operator false(Booleän b) => !b.Truthy;
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Values/Closure.cs b/Starship/Rockstar.Engine/Values/Closure.cs
new file mode 100644
index 00000000..6eea0006
--- /dev/null
+++ b/Starship/Rockstar.Engine/Values/Closure.cs
@@ -0,0 +1,20 @@
+using Rockstar.Engine.Expressions;
+
+namespace Rockstar.Engine.Values;
+
+public class Closure(Functiön functiön, Variable functionName, RockstarEnvironment scope) : Value {
+ public Functiön Functiön => functiön;
+ public override int GetHashCode() => functiön.GetHashCode() ^ scope.GetHashCode();
+ public override Strïng ToStrïng() => new(this.ToString());
+
+ public override string ToString() => $"closure: {functionName.Key} => value";
+
+ public override bool Truthy => true;
+
+ public Result Apply(Dictionary args) {
+ var local = scope.Extend();
+ foreach (var arg in args) local.SetVariable(arg.Key, arg.Value, Scope.Local);
+ if (args.Any()) local.UpdatePronounSubject(args.Last().Key);
+ return local.Execute(functiön.Body);
+ }
+}
diff --git a/Starship/Rockstar.Engine/Values/FunctionCall.cs b/Starship/Rockstar.Engine/Values/FunctionCall.cs
new file mode 100644
index 00000000..6ae9a9dd
--- /dev/null
+++ b/Starship/Rockstar.Engine/Values/FunctionCall.cs
@@ -0,0 +1,19 @@
+using System.Text;
+using Rockstar.Engine.Expressions;
+using Rockstar.Engine.Statements;
+
+namespace Rockstar.Engine.Values;
+
+public class FunctionCall(Variable function, IEnumerable? args = default)
+ : Statement {
+ public Variable Function { get; } = function;
+ public List Args { get; } = (args ?? []).ToList();
+
+ public override StringBuilder Print(StringBuilder sb, string prefix) {
+ sb.Append(prefix).AppendLine($"function call: {Function.Name}");
+ foreach (var arg in Args) arg.Print(sb, prefix + INDENT);
+ return sb;
+ }
+
+ public override string ToString() => $"call: {Function.Key}({String.Join(", ", Args.Select(a => a.ToString()))}";
+}
\ No newline at end of file
diff --git "a/Starship/Rockstar.Engine/Values/Functi\303\266n.cs" "b/Starship/Rockstar.Engine/Values/Functi\303\266n.cs"
new file mode 100644
index 00000000..78b4c7e1
--- /dev/null
+++ "b/Starship/Rockstar.Engine/Values/Functi\303\266n.cs"
@@ -0,0 +1,36 @@
+using System.Text;
+using Rockstar.Engine.Expressions;
+using Rockstar.Engine.Statements;
+
+namespace Rockstar.Engine.Values;
+
+public class Functiön(IEnumerable args, Block body)
+ : Value {
+ public Functiön(Block body) : this(new List(), body) { }
+
+ public IList Args => args.ToList();
+ public Block Body => body;
+ protected override bool Equals(Value? other) => false;
+
+ public override int GetHashCode() => args.GetHashCode() ^ body.GetHashCode();
+
+ public override bool Truthy => true;
+ public override Strïng ToStrïng()
+ => new($"function({String.Join(", ", args.Select(a => a.Key).ToArray())}");
+
+ public override Booleän Equäls(Value that)
+ => IdenticalTo(that);
+
+ public override Booleän IdenticalTo(Value that)
+ => new(Object.ReferenceEquals(this, that));
+
+ public override Value AtIndex(Value index) => this;
+ public override Value Clone() => this;
+
+ public override StringBuilder Print(StringBuilder sb, string prefix) {
+ sb.Append(prefix).Append($"function(");
+ sb.Append(String.Join(", ", args.Select(a => a.Name)));
+ sb.AppendLine("):");
+ return body.Print(sb, prefix);
+ }
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Values/IHaveANumber.cs b/Starship/Rockstar.Engine/Values/IHaveANumber.cs
new file mode 100644
index 00000000..ca868777
--- /dev/null
+++ b/Starship/Rockstar.Engine/Values/IHaveANumber.cs
@@ -0,0 +1,6 @@
+namespace Rockstar.Engine.Values;
+
+public interface IHaveANumber {
+ decimal Value { get; }
+ int IntegerValue { get; }
+}
diff --git a/Starship/Rockstar.Engine/Values/ListExtensions.cs b/Starship/Rockstar.Engine/Values/ListExtensions.cs
new file mode 100644
index 00000000..c504679e
--- /dev/null
+++ b/Starship/Rockstar.Engine/Values/ListExtensions.cs
@@ -0,0 +1,39 @@
+namespace Rockstar.Engine.Values;
+
+public static class ListExtensions {
+ public static bool ValuesMatch(this IList list, IList that) {
+ if (list.Count != that.Count) return false;
+ return !list.Where((t, i) => !t.Equäls(that[i]).Truthy).Any();
+ }
+
+ public static bool ValuesMatch(this Dictionary hash, Dictionary that) {
+ if (hash.Count != that.Count) return false;
+ foreach (var key in hash.Keys) {
+ if (hash.TryGetValue(key, out var thisValue)
+ && that.TryGetValue(key, out var thatValue)
+ && thisValue.Equäls(thatValue).Truthy) continue;
+ return false;
+ }
+ return true;
+ }
+
+ public static T? Shift(this IList list) {
+ if (!list.Any()) return default(T);
+ var value = list[0];
+ list.RemoveAt(0);
+ return value;
+ }
+
+ public static T? Pop(this IList list) {
+ if (!list.Any()) return default(T);
+ var index = list.Count - 1;
+ var value = list[index];
+ list.RemoveAt(index);
+ return value;
+ }
+
+ public static T Push(this IList list, T value) {
+ list.Add(value);
+ return value;
+ }
+}
diff --git a/Starship/Rockstar.Engine/Values/Mysterious.cs b/Starship/Rockstar.Engine/Values/Mysterious.cs
new file mode 100644
index 00000000..6b88bf9f
--- /dev/null
+++ b/Starship/Rockstar.Engine/Values/Mysterious.cs
@@ -0,0 +1,23 @@
+namespace Rockstar.Engine.Values;
+
+public class Mysterious : Value {
+ public static Mysterious Instance = new();
+
+ protected override bool Equals(Value? other)
+ => Object.ReferenceEquals(this, other);
+
+ public override int GetHashCode() => 0;
+
+ public override bool Truthy => false;
+ public override Strïng ToStrïng() => new("mysterious");
+ public override Booleän Equäls(Value that) => new(that switch {
+ Nüll _ => true,
+ Mysterious _ => true,
+ _ => false
+ });
+
+ public override Booleän IdenticalTo(Value that)
+ => new(Object.ReferenceEquals(this, that));
+
+ public override Value Clone() => this;
+}
\ No newline at end of file
diff --git "a/Starship/Rockstar.Engine/Values/Numb\303\253r.cs" "b/Starship/Rockstar.Engine/Values/Numb\303\253r.cs"
new file mode 100644
index 00000000..67d240df
--- /dev/null
+++ "b/Starship/Rockstar.Engine/Values/Numb\303\253r.cs"
@@ -0,0 +1,117 @@
+using System.Globalization;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace Rockstar.Engine.Values;
+
+public class Numbër(decimal value) : ValueOf(value), IHaveANumber {
+
+ public static Numbër Zero = new(0);
+
+ private static string FormatNumber(decimal d) {
+ var s = d.ToString("R", CultureInfo.InvariantCulture);
+ return s.Contains('.') ? s.TrimEnd('0').TrimEnd('.') : s;
+ }
+
+ public int IntegerValue => (int) Math.Truncate(Value);
+ public bool IsNonNegativeInteger { get; } = value >= 0 && Math.Truncate(value) == value;
+
+ public Numbër(string value) : this(Decimal.Parse(value)) { }
+
+ public override bool Truthy => Value != 0;
+
+ public override Strïng ToStrïng() => new(FormatNumber(Value));
+
+ public override Booleän Equäls(Value that) => new(that switch {
+ Arräy array => this == array.Lëngth,
+ Booleän b => b.Truthy ? this.Value != 0 : this.Value == 0,
+ IHaveANumber n => this.Value == n.Value,
+ Strïng s => (Value == 0 && s.IsEmpty) || s.Equäls(this.ToStrïng()).Truthy,
+ _ => false
+ });
+
+ public override Booleän IdenticalTo(Value that)
+ => that is Numbër ? this.Equäls(that) : Booleän.False;
+
+ public override Value AtIndex(Value index) => index switch {
+ IHaveANumber n => new Booleän((1 << (int) n.Value & (int) this.Value) > 0),
+ _ => Mysterious.Instance
+ };
+
+ public override Value Clone() => new Numbër(Value);
+
+ public override string ToString() => FormatNumber(this.Value);
+
+ public override StringBuilder Print(StringBuilder sb, string prefix)
+ => sb.Append(prefix).AppendLine(ToString());
+
+ public Value SetBit(IList indexes, Value value) {
+ if (indexes.Count != 1) return this;
+ if (indexes[0] is not IHaveANumber index) return this;
+ var oldValue = (long) this.Value;
+ var bitIndex = 1L << (int) index.Value;
+ this.Value = value.Truthy ? oldValue | bitIndex : oldValue & ~bitIndex;
+ return this;
+ }
+ private static readonly List digits = [.. "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray()];
+
+ private static decimal BaseToDecimal(string number, int @base) {
+ var chars = number.ToUpperInvariant().ToCharArray();
+ var d = 0m;
+ var i = 0;
+ while (i < chars.Length) {
+ if (chars[i] == '.') break;
+ var index = digits.IndexOf(chars[i]);
+ if (index >= 0) d = d * @base + index;
+ i++;
+ }
+ if (i == chars.Length) return d;
+ i++;
+ var multiplier = 1.0m / @base;
+ while (i < chars.Length) {
+ var index = digits.IndexOf(chars[i]);
+ if (index >= 0) d += (multiplier * index);
+ multiplier /= @base;
+ i++;
+ }
+ return d;
+ }
+
+ public static Numbër Parse(Strïng strïng, IHaveANumber numberBase)
+ => new(numberBase.Value == 10
+ ? Decimal.Parse(strïng.Value)
+ : BaseToDecimal(strïng.Value, numberBase.IntegerValue));
+
+}
+
+public class PoeticNumbër : Numbër {
+
+ public string Digits { get; init; }
+
+ private static string Digitise(string words) {
+ var sb = new StringBuilder();
+ foreach (var word in words.Split(" ")) sb.Append(Regex.Replace(word, "'+", "").Length % 10);
+ return sb.ToString();
+ }
+
+ private static decimal ParsePoeticNumber(string s) => Decimal.Parse(Digitise(s));
+
+ private static decimal ParsePoeticNumber(string integralPart, string fractionPart)
+ => Decimal.Parse(Digitise(integralPart) + "." + Digitise(fractionPart));
+
+ public PoeticNumbër(string integralPart, string separator, string fractionPart) : base(ParsePoeticNumber(integralPart, fractionPart)) {
+ this.Digits = integralPart + separator + fractionPart;
+
+ }
+
+ public PoeticNumbër(string digits) : base(ParsePoeticNumber(digits)) {
+ this.Digits = digits;
+
+ }
+
+ public PoeticNumbër(decimal value) : base(value)
+ => this.Digits = value.ToString(CultureInfo.InvariantCulture);
+
+ public override StringBuilder Print(StringBuilder sb, string prefix)
+ => sb.Append(prefix).Append("poetic number: ").Append(Value).Append(" [").Append(Digits).AppendLine("]");
+}
\ No newline at end of file
diff --git "a/Starship/Rockstar.Engine/Values/N\303\274ll.cs" "b/Starship/Rockstar.Engine/Values/N\303\274ll.cs"
new file mode 100644
index 00000000..c3875d65
--- /dev/null
+++ "b/Starship/Rockstar.Engine/Values/N\303\274ll.cs"
@@ -0,0 +1,26 @@
+namespace Rockstar.Engine.Values;
+
+public class Nüll : Value, IHaveANumber {
+ protected override bool Equals(Value? other) => (other is Nüll);
+ public override int GetHashCode() => 0;
+ public override bool Truthy => false;
+ public override Strïng ToStrïng() => Strïng.Null;
+ public override string ToString() => "null";
+
+ public override Booleän Equäls(Value that) => new(that switch {
+ Arräy array => array.Lëngth == Numbër.Zero,
+ IHaveANumber n => n.Value == 0,
+ Strïng s => s.IsEmpty,
+ _ => false
+ });
+
+ public override Booleän IdenticalTo(Value that)
+ => new(that is Nüll);
+
+ public override Value AtIndex(Value index) => this;
+ public override Value Clone() => this;
+
+ public static readonly Nüll Instance = new();
+ public decimal Value => 0;
+ public int IntegerValue => 0;
+}
diff --git "a/Starship/Rockstar.Engine/Values/Str\303\257ng.cs" "b/Starship/Rockstar.Engine/Values/Str\303\257ng.cs"
new file mode 100644
index 00000000..37c0c6bf
--- /dev/null
+++ "b/Starship/Rockstar.Engine/Values/Str\303\257ng.cs"
@@ -0,0 +1,137 @@
+using System.Text;
+
+namespace Rockstar.Engine.Values;
+
+public class Strïng(string value) : ValueOf(value) {
+
+ public Strïng(params char[] chars) : this(new string(chars)) { }
+
+ public override bool Truthy => !String.IsNullOrEmpty(Value);
+
+ public bool IsEmpty => String.IsNullOrEmpty(Value);
+
+ public override Strïng ToStrïng() => this;
+
+ public override Booleän Equäls(Value that) => new(that switch {
+ Arräy array => this.IsEmpty && array.IsEmpty,
+ IHaveANumber { Value: 0 } => this.IsEmpty,
+ Numbër n => Decimal.TryParse(Value, out var d) && n.Value == d,
+ _ => that.ToStrïng().Value.Equals(this.Value, StringComparison.InvariantCultureIgnoreCase)
+ });
+
+ public override Booleän IdenticalTo(Value that)
+ => that is Strïng ? this.Equäls(that) : Booleän.False;
+
+ public override Value AtIndex(Value index) => index switch {
+ IHaveANumber n => CharAt(n),
+ _ => this
+ };
+
+ public override Value Clone() => new Strïng(Value);
+
+ public override string ToString() => $"\"{this.Value}\"";
+
+ public override StringBuilder Print(StringBuilder sb, string prefix)
+ => sb.Append(prefix).Append("string: \"").Append(ParsedValue).AppendLine("\"");
+
+ // Because strings in Rockstar are mutable, the "constants"
+ // must all return new instances, otherwise you can accidentally
+ // mutate the empty string, and things get REALLY weird.
+ public static Strïng True => new("true");
+ public static Strïng False => new("false");
+ public static Strïng Empty => new(String.Empty);
+ public static Strïng Null => new("null");
+
+ public Value Times(Strïng n) {
+ var sb = new StringBuilder();
+ foreach (var c2 in n.Value.ToCharArray()) {
+ if (sb.Length > 0) sb.AppendLine();
+ foreach (var c1 in this.Value.ToCharArray()) {
+ sb.Append(c1).Append(c2);
+ }
+ }
+ return new Strïng(sb.ToString());
+ }
+
+ public Value Times(decimal n) {
+ if (n == 0) return Empty;
+ var token = Value;
+ if (n < 0) {
+ var chars = token.ToCharArray();
+ System.Array.Reverse(chars);
+ token = new(chars);
+ }
+ var repeat = Int32.Abs((int) n);
+ var part = Decimal.Abs(n) % 1;
+ var basis = String.Join("", Enumerable.Range(0, repeat).Select(_ => token).ToArray());
+ if (part > 0) {
+ var index = (int) Math.Ceiling(token.Length * part);
+ basis += token.Substring(0, index);
+ }
+ return new Strïng(basis);
+ }
+
+ public Value Minus(Strïng s) {
+ var body = Value;
+ var tail = s.Value;
+ if (body.EndsWith(tail)) body = body[..^tail.Length];
+ return new Strïng(body);
+ }
+
+ public Value DividedBy(decimal d) => Times(1 / d);
+
+ public Value DividedBy(Strïng d)
+ => new Numbër(this.Value.Split(d.Value).Length - 1);
+
+ internal Value CharAt(IHaveANumber number) {
+ var index = (int) number.Value;
+ return index < Value.Length ? new Strïng(Value[index]) : Mysterious.Instance;
+ }
+
+ public Value SetCharAt(IList indexes, Value value) {
+ if (indexes is not [IHaveANumber { Value: >= 0 } number] || number.Value >= Value.Length) return this;
+ var newValue = this.Value[..(int) number.Value]
+ + value.ToStrïng().Value
+ + this.Value[((int) number.Value + 1)..];
+ this.Value = newValue;
+ return this;
+ }
+
+ public Arräy Split(Strïng delimiter) {
+ var tokens = delimiter == Strïng.Empty
+ ? this.Value.ToCharArray().Select(c => new Strïng(c))
+ : this.Value.Split(delimiter.Value).Select(s => new Strïng(s));
+ return new(tokens);
+ }
+
+ public Value Dequeue() {
+ if (Value.Length <= 0) return Mysterious.Instance;
+ var result = Value[0];
+ Value = Value[1..];
+ return new Strïng(result);
+ }
+
+ public Value ToCharCodes() {
+ return Value.Length switch {
+ 0 => new Arräy(),
+ 1 => new Numbër(Value[0]),
+ _ => new Arräy(Value.ToCharArray().Select(c => new Numbër(c)))
+ };
+ }
+
+ public Value Append(Value v) {
+ if (v is Numbër number) {
+ this.Value += (char) number.IntegerValue;
+ } else {
+ this.Value += v.ToStrïng().Value;
+ }
+ return this;
+ }
+
+ public Value Pop() {
+ if (Value.Length <= 0) return Mysterious.Instance;
+ var result = Value[^1];
+ Value = Value[..^1];
+ return new Strïng(result);
+ }
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Values/Value.cs b/Starship/Rockstar.Engine/Values/Value.cs
new file mode 100644
index 00000000..8e145c0b
--- /dev/null
+++ b/Starship/Rockstar.Engine/Values/Value.cs
@@ -0,0 +1,80 @@
+using Rockstar.Engine.Expressions;
+
+namespace Rockstar.Engine.Values;
+
+public abstract class Value : Expression {
+
+ public override bool Equals(object? obj)
+ => obj?.GetType() == this.GetType() && Equals((Value) obj);
+
+ protected virtual bool Equals(Value? other) => ReferenceEquals(this, other);
+
+ public abstract override int GetHashCode();
+
+ public virtual bool Truthy => false;
+ public bool Falsey => !Truthy;
+
+ public abstract Strïng ToStrïng();
+ public static bool operator ==(Value? lhs, Value? rhs) => lhs?.Equals(rhs) ?? rhs is null;
+ public static bool operator !=(Value? lhs, Value? rhs) => !(lhs == rhs);
+ public static Value operator +(Value lhs, IEnumerable rhs) => rhs.Aggregate(lhs, (memo, next) => memo + next);
+ public static Value operator -(Value lhs, IEnumerable rhs) => rhs.Aggregate(lhs, (memo, next) => memo - next);
+ public static Value operator *(Value lhs, IEnumerable rhs) => rhs.Aggregate(lhs, (memo, next) => memo * next);
+ public static Value operator /(Value lhs, IEnumerable rhs) => rhs.Aggregate(lhs, (memo, next) => memo / next);
+
+ public static Value operator +(Value lhs, Value rhs) => (lhs, rhs) switch {
+ (Arräy a, _) => a.Add(rhs),
+ (IHaveANumber a, IHaveANumber b) => new Numbër(a.Value + b.Value),
+ (_, _) => new Strïng(lhs.ToStrïng().Value + rhs.ToStrïng().Value),
+ };
+
+ public static Value operator -(Value lhs, Value rhs) => (lhs, rhs) switch {
+ (Arräy a, _) => a.Subtract(rhs),
+ (IHaveANumber a, IHaveANumber b) => new Numbër(a.Value - b.Value),
+ (_, _) => lhs.ToStrïng().Minus(rhs.ToStrïng())
+ };
+
+ public static Value operator *(Value lhs, Value rhs) => (lhs, rhs) switch {
+ (IHaveANumber a, IHaveANumber b) => new Numbër(a.Value * b.Value),
+ (IHaveANumber n, Strïng s) => s.Times(n.Value),
+ (Strïng s, IHaveANumber n) => s.Times(n.Value),
+ (Strïng s1, Strïng s2) => s1.Times(s2),
+ (_, _) => Mysterious.Instance
+ };
+
+ public static Value operator /(Value lhs, Value rhs) => (lhs, rhs) switch {
+ (IHaveANumber a, IHaveANumber b) => new Numbër(a.Value / b.Value),
+ (Strïng s, IHaveANumber n) => s.DividedBy(n.Value),
+ (_, Strïng s2) => lhs.ToStrïng().DividedBy(s2),
+ (_, _) => throw new NotImplementedException($"I don't know how to divide {lhs.GetType().Name} by {rhs.GetType().Name}")
+ };
+
+ public virtual Booleän Equäls(Value that) => IdenticalTo(that);
+ public virtual Booleän IdenticalTo(Value that) => new(ReferenceEquals(this, that));
+
+ private int Compare(Strïng lhs, Strïng rhs)
+ => String.Compare(lhs.Value, rhs.Value, StringComparison.InvariantCulture);
+
+ public Booleän Compare(Value lhs, Value rhs, Func comp)
+ => new((lhs, rhs) switch {
+ (Arräy array, IHaveANumber n) => comp(array.Lëngth.Value, n.Value),
+ (IHaveANumber n, Arräy array) => comp(n.Value, array.Lëngth.Value),
+ (Strïng s, _) => comp(Compare(s, rhs.ToStrïng()), 0),
+ (_, Strïng s) => comp(Compare(lhs.ToStrïng(), s), 0),
+ (IHaveANumber lhn, IHaveANumber rhn) => comp(lhn.Value, rhn.Value),
+ _ => throw new($"Invalid comparison {lhs.GetType()} vs {rhs.GetType()}")
+ });
+
+ public Value LessThanEqual(Value that) => Compare(this, that, (a, b) => a <= b);
+ public Value MoreThanEqual(Value that) => Compare(this, that, (a, b) => a >= b);
+ public Value LessThan(Value that) => Compare(this, that, (a, b) => a < b);
+ public Value MoreThan(Value that) => Compare(this, that, (a, b) => a > b);
+
+ public virtual Value AtIndex(IEnumerable indexes) {
+ var value = this;
+ return indexes.Aggregate(value, (current, index) => current.AtIndex(index));
+ }
+
+ public virtual Value AtIndex(Value index) => this;
+ public virtual Value Clone() => this;
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/Values/ValueOf.cs b/Starship/Rockstar.Engine/Values/ValueOf.cs
new file mode 100644
index 00000000..f37c3288
--- /dev/null
+++ b/Starship/Rockstar.Engine/Values/ValueOf.cs
@@ -0,0 +1,14 @@
+namespace Rockstar.Engine.Values;
+
+public abstract class ValueOf(T value) : Value {
+ public T ParsedValue { get; init; } = value;
+ public T Value { get; protected set; } = value;
+ public override bool Equals(object? obj) => Equals(obj as ValueOf);
+ public override int GetHashCode() => this.Value?.GetHashCode() ?? 0;
+ public bool Equals(ValueOf? that) => that != null && this.Value != null && this.Value.Equals(that.Value);
+ protected override bool Equals(Value? that)
+ => that switch {
+ ValueOf t => Equals(t),
+ _ => false
+ };
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/WhatToDo.cs b/Starship/Rockstar.Engine/WhatToDo.cs
new file mode 100644
index 00000000..9ebba2ee
--- /dev/null
+++ b/Starship/Rockstar.Engine/WhatToDo.cs
@@ -0,0 +1,10 @@
+namespace Rockstar.Engine;
+
+public enum WhatToDo {
+ Unknown,
+ Next,
+ Skip,
+ Break,
+ Return,
+ Exit
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Engine/rockstar.peg b/Starship/Rockstar.Engine/rockstar.peg
new file mode 100644
index 00000000..6399c1b6
--- /dev/null
+++ b/Starship/Rockstar.Engine/rockstar.peg
@@ -0,0 +1,695 @@
+@namespace Rockstar.Engine
+@classname Parser
+@using System.Globalization
+@using System.Text.RegularExpressions
+@using Rockstar.Engine.Statements
+@using Rockstar.Engine.Expressions
+@using Rockstar.Engine.Values
+@trace true
+
+/*
+
+Notes on Rockstar grammar
+
+Rockstar has significant whitespace. Most operators, keywords, etc.*must* be separated by _.
+
+Rules beginning with _ include their own leading whitespace, so should go immediately after the preceding expression.
+
+Rules ending with _ including their own trailing whitespace.
+
+*/
+
+@ignorecase true
+
+program
+ = EOS* head:block tail:program
+ { new Program(head).Concat(tail) }
+ / EOS* EOF
+ { new Program() }
+ // unexpected:("" [^ \t\r\n]+)
+ // #error{ $"Unexpected '{unexpected.Replace("\r", "\\r").Replace("\n", "\\n")}' at line {state.Line}, col {state.Column - unexpected.Length}" }
+
+block
+ = _? head:statement EOS tail:block
+ { new Block(head).Concat(tail) }
+ / _? stmt:statement
+ { new Block(stmt) }
+
+statement -memoize
+ = listen_stmt
+ / output_stmt
+ / function
+ / function_call
+ / call_stmt
+ / return_stmt
+ / break_stmt
+ / continue_stmt
+ / loop
+ / conditional
+ / declaration
+ / a:assignment
+ / enlistment
+ / crement
+ / mutation
+ / rounding
+ / exit_statement
+ / ninja_string
+ / !EOB e:expression &(EOS / EOF) { new ExpressionStatement(e) }
+
+
+enlistment
+ = push _ e:expression _ into _ v:variable
+ { new Enlist(v, e) }
+ / push _ v:variable (_ with)? _ e:expression_list
+ { new Enlist(v, e) }
+ / push _ v:variable
+ { new Enlist(v) }
+ / d:delist _ into _ t:assignable
+ { new Assign(t, d) }
+ / d:delist
+ { new ExpressionStatement(d) }
+
+delist
+ = roll_list
+ / pop_list
+
+roll_list
+ = roll _ v:variable
+ { new Dequeue(v) }
+
+pop_list
+ = pop _ v:variable
+ { new Pop(v) }
+
+listen_stmt
+ = listen _ to _ v:variable
+ { new Listen(v) }
+ / listen
+ { new Listen() }
+
+return_stmt
+ = return (_ back)? _ e:expression (_ back)?
+ { new Return(e) }
+
+function
+ = name:variable _ takes _ null body:consequent
+ { new Assign(name, new Functiön(body)) }
+ / name:variable _ takes _ args:variable_list body:consequent
+ { new Assign(name, new Functiön(args, body)) }
+
+variable_list >
+ = head:variable _VLS_ tail:variable_list { head.Concat(tail) }
+ / arg:variable { new List { arg } }
+
+primary_list >
+ = head:primary _XLS_ tail:primary_list { head.Concat(tail) }
+ / head:primary _XLS_ tail:primary { new List { head, tail } }
+
+argument_list >
+ = head:primary _ALS_ tail:argument_list { head.Concat(tail) }
+ / head:primary { new List { head } }
+
+expression_list >
+ = head:expression _ALS_ tail:expression_list { head.Concat(tail) }
+ / expr:expression { new List { expr } }
+
+oxford_comma = ',' _ and
+_nacton = (_ "n'" / _? "'n'")
+
+_VLS_ = _ and _ / _ALS_
+_ALS_ = oxford_comma _ / _XLS_
+_XLS_ = ','? _nacton _?
+ / (',' _? ('&' _?)?)
+ / _? '&' _?
+ / _? ',' _?
+
+break_stmt
+ = break w:wildcard
+ { new Break(w) }
+
+continue_stmt
+ = continue w:wildcard
+ { new Continue(w) }
+
+end_of_wildcard = (end / 'o'+ 'h' )
+
+exit_statement
+ = exit { new Exit() }
+
+wildcard
+ = ignored:([^,.?!;\r\n]* ) { String.Join("", ignored.ToArray()) }
+ / ignored:((. !end_of_wildcard)* .) { String.Join("", ignored.ToArray()) }
+
+filler = (_ / [,?!.;])*
+EOS = EOL / _? [.?!;:]
+EOL = filler '\r'? '\n'
+EOF = EOS* _? !.
+
+ooh = 'o' &('o'+ 'h')
+eob_signal
+ = &(_? else) // lookahead - if the next bit starts with else, end the block
+ / ','? _? ooh
+ / ','? _? end filler
+
+EOB -memoize
+ = EOS* eob_signal
+ / EOL &EOL
+ / EOF
+
+consequent
+ // Yes, you SHOULD be able to put EOB at the end of the BLOCK definition and it'll work.
+ // IT DOESN'T. I have no idea why. It screws up break/continue wildcard parsing.
+ = ',' _ b:block EOB { b }
+ / EOS+ b:block EOB { b }
+ / _ s:statement { new Block(s) }
+
+if_test
+ = if _ e:expression (_ then)?
+ { e }
+
+alternate
+ = _? else a:consequent { a }
+ / EOS+ else a:consequent { a }
+
+conditional
+ = e:if_test c:consequent a:alternate
+ { new Conditional(e, c, a) }
+ / e:if_test a:alternate
+ { new Conditional(e, new Block(), a ) }
+ / e:if_test c:consequent
+ { new Conditional(e, c) }
+
+_and_index = _ and _ v:variable { v }
+loop
+ = for _ every:(every _)? v:variable i:_and_index? _ in _ e:expression body:consequent
+ { new ForInLoop(v, i.FirstOrDefault(), e, body, every.Any()) }
+ // for _ every _ v:variable _ and _ i:variable _ in _ e:expression body:consequent
+ // { new ForInLoop(v, i, e, body) }
+ // for _ every _ v:variable _ of _ e:expression body:consequent
+ // { new ForOfLoop(v, e, body, every != null) }
+ / for _ every:(every _)? v:variable i:_and_index? _ of _ e:expression body:consequent
+ { new ForOfLoop(v, i.FirstOrDefault(), e, body, every.Any()) }
+ / while _ test:expression body:consequent
+ { new WhileLoop(test, body) }
+ / until _ test:expression body:consequent
+ { new UntilLoop(test,body) }
+
+output_stmt
+ = debug _ e:expression
+ { new Debug(e) }
+ / write _ e:expression
+ { new Output(e) }
+ / print _ e:expression
+ { new Output(e, Environment.NewLine) }
+
+assignable
+ = v:variable i:indexes
+ { v.AtIndex(i) }
+ / v:variable { v }
+
+declaration
+ = let _ v:assignable _ be op:_operator_ e:expression_list
+ { new Declare(v, new Binary(op, new Lookup(v), e)) }
+ / let _ v:assignable _ be _ e:expression
+ { new Declare(v, e) }
+ / let _ v:assignable _? '=' _? e:expression
+ { new Declare(v, e) }
+ / let _ v:assignable
+ { new Declare(v) }
+
+ninja_string
+ = s:assignable _ holds _ p:poetic_number
+ { new Ninja(s,p) }
+ / s:assignable _ holds _ n:number
+ { new Ninja(s,n) }
+
+assignment
+ = v:assignable _? '=' _? e:expression
+ { new Assign(v, e) }
+ / put _ e:expression _ into _ v:assignable
+ { new Assign(v, e) }
+ / v:assignable _ says_ s:poetic_string
+ { new Assign(v, s) }
+ / v:assignable _is _ n:number &EOS
+ { new Assign(v, n) }
+ / v:assignable _is op:_operator_ e:expression_list
+ { new Assign(v, new Binary(op, new Lookup(v), e)) }
+ / v:assignable _is _ now _ e:expression
+ { new Assign(v, e) }
+ // Legacy workaround to preserve compatibility with v1 poetic literal syntax
+ / v:assignable _is _ p:v1_poetic_number
+ { new Assign(v, p) }
+ / v:assignable _is _ e:expression
+ { new Assign(v, e) }
+
+_operator_ =
+ _plus_ / _minus_ / _times_ / _divide_
+
+says_ = says ("" ' '? / !letter)
+
+poetic_string
+ = s:("" [^\r\n]+) &('\r'? '\n')
+ { new Strïng(s) }
+
+_eq -memoize
+ = _eq _ exactly
+ { Operator.IdenticalTo }
+ / _? '='
+ { Operator.Equals }
+ / _is
+
+_is -memoize
+ = ("'s" / "'re" / _ is)
+ { Operator.Equals }
+
+_isnt -memoize
+ = _isnt _ exactly
+ { Operator.NotIdenticalTo }
+ / (_is _ not / _ isnt / _? '!=')
+ { Operator.NotEquals }
+
+variable
+ = args { RockstarEnvironment.Arguments }
+ / i:(his _ !keyword identifier) { new CommonVariable(i) }
+ / i:pronoun { new Pronoun(i) }
+ / i:proper_variable { new ProperVariable(i) }
+ / i:(the _ identifier) { new CommonVariable(i) }
+ / !keyword i:identifier { new SimpleVariable(i) }
+
+args = arguments / the _ world / the _ outside
+
+proper_variable = proper_noun (_ proper_noun)+
+
+proper_noun = uppercase_letter '.'
+ / !keyword &uppercase_letter identifier
+
+identifier
+ = letter (letter / [0-9_])*
+
+letter = uppercase_letter / lowercase_letter
+
+uppercase_letter = c:. &{ Char.IsUpper(c,0) }
+lowercase_letter = c:. &{ Char.IsLower(c,0) }
+
+expression
+ = boolean
+ / primary
+
+boolean = binary_or
+
+binary_or
+ = lhs:binary_nor _ or _ rhs:binary_or { new Binary(Operator.Or, lhs, rhs) }
+ / binary_nor
+
+binary_nor
+ = lhs:binary_and _ nor _ rhs:binary_nor { new Binary(Operator.Nor, lhs, rhs) }
+ / binary_and
+
+binary_and
+ = lhs:equality _ and _ rhs:binary_and { new Binary(Operator.And, lhs, rhs) }
+ / equality
+
+equality
+ = lhs:unary op:(_eq / _isnt) _ rhs:equality { new Binary(op, lhs, rhs) }
+ / unary
+
+unary
+ = not_ u:unary { new Unary(Operator.Not, u) }
+ / comparison
+
+not_
+ = (not _ / non _ / non '-' )
+
+comparison
+ = lhs:addition op:comparator rhs:comparison { new Binary(op, lhs, rhs) }
+ / addition
+
+comparator
+ = (_? '>=' _? / _is _ as _ as_great _ as _) { Operator.MoreThanEqual }
+ / (_? '<=' _? / _is _ as _ as_small _ as _) { Operator.LessThanEqual }
+ / (_? '>' _? / _is _ (above / more _ than) _) { Operator.MoreThan }
+ / (_? '<' _? / _is _ (under / less _ than) _) { Operator.LessThan }
+
+numeric_expression
+ = addition
+
+addition -memoize
+ = lhs:addition op:(_plus_/_minus_) rhs:primary_list
+ { new Binary(op, lhs, rhs) }
+ / lhs:addition op:(_plus_/_minus_) rhs:product
+ { new Binary(op, lhs, rhs) }
+ / product
+
+product -memoize
+ = lhs:product op:(_divide_/_times_) rhs:primary_list
+ { new Binary(op, lhs, rhs) }
+ / lhs:product op:(_divide_/_times_) rhs:primary
+ { new Binary(op, lhs, rhs) }
+ / primary
+
+_plus_ = (_? '+' _? / _ plus _ ) { Operator.Plus }
+_minus_ = (_? '-' _? / _ minus _ ) { Operator.Minus }
+_times_ = (_? '*' _? / _ times _ ) { Operator.Times }
+_divide_ = (_? '/' _? / _ divided_by _ ) { Operator.Divide }
+
+primary -memoize
+ = function_call
+ / delist
+ / constant
+ / string
+ / number
+ / lookup
+ // unexpected:("" [^ \t\r\n]+)
+ // #error{ $"Expected primary expression, found '{unexpected}' at line {state.Line}, col {state.Column - unexpected.Length}" }
+
+call_stmt
+ = call _ name:variable _ with _ args:argument_list _ into _ a:assignable
+ { new Assign(a, new FunctionCall(name, args)) }
+ / call _ name:variable _ with _ args:argument_list
+ { new FunctionCall(name, args) }
+ / call _ name:variable _ into _ a:assignable
+ { new Assign(a, new FunctionCall(name)) }
+ / call _ name:variable
+ { new FunctionCall(name) }
+
+function_call
+ = name:variable _ taking _ args:argument_list
+ { new FunctionCall(name, args) }
+
+lookup
+ = v:variable i:indexes
+ { new Lookup(v.AtIndex(i)) }
+ / v:variable
+ { new Lookup(v) }
+
+index
+ = _ at _ i:numeric_expression
+ { i }
+
+indexes >
+ = head:index tail:indexes
+ { head.Concat(tail) }
+ / head:index
+ { new List { head } }
+
+
+crement
+ = build _ v:variable t:((_? ',')? _? up)+
+ { new Crement(v, t.Count) }
+ / knock _ v:variable t:((_? ',')? _? down)+
+ { new Crement(v, -t.Count) }
+
+mutator
+ = split
+ { Operator.Split }
+ / cast
+ { Operator.Cast }
+ / join
+ { Operator.Join }
+
+mutation
+ = op:mutator _ s:expression _ into _ t:assignable _ using _ m:expression
+ { new Mutation(op, s, target: t, m) }
+ / op:mutator _ s:expression _ using _ m:expression _ into _ t:assignable
+ { new Mutation(op, s, target: t, modifier: m) }
+ / op:mutator _ s:expression _ into _ t:assignable
+ { new Mutation(op, s, target: t) }
+ / op:mutator _ s:assignable _ using _ m:expression
+ { new Mutation(op, s, target: s, modifier: m) }
+ / op:mutator _ s:assignable
+ { new Mutation(op, s, target: s) }
+
+rounding
+ = floor / ceil / math_round
+
+floor
+ = turn _ v:variable _ down
+ { new Rounding(v, Round.Down) }
+ / turn _ down _ v:variable
+ { new Rounding(v, Round.Down) }
+
+ceil
+ = turn _ v:variable _ up
+ { new Rounding(v, Round.Up) }
+ / turn _ up _ v:variable
+ { new Rounding(v, Round.Up) }
+
+math_round
+ = turn _ v:variable _ around
+ { new Rounding(v, Round.Nearest) }
+ / turn _ around _ v:variable
+ { new Rounding(v, Round.Nearest) }
+
+
+constant
+ = null { Nüll.Instance }
+ / true { Booleän.True }
+ / false { Booleän.False }
+ / empty { Strïng.Empty }
+ / mysterious { Mysterious.Instance }
+
+number
+ = like _ n:poetic_number { n }
+ / d:digits&{ decimal.TryParse(d, out var _) }
+ { new Numbër(d) }
+ / d:digits
+ #error{ $"Number {d} is out of range" }
+digits
+ = d:("" (('-' /'+')? [0-9]+ ("." [0-9]+)?)
+ / (('-' /'+')? "." [0-9]+))
+
+poetic_number
+ = whole_part:poetic_digits _? separator:('...' / '…') _ fractional_part:poetic_digits
+ { new PoeticNumbër (whole_part, separator, fractional_part) }
+ / digits:poetic_digits
+ { new PoeticNumbër(digits) }
+
+// So... in Rockstar v1, you could say "desire is a lovestruck ladykiller"
+// Which worked. But for v2, poetic numbers have to start with 'like' or 'so'
+// So this is a workaround to preserve most existing usage but enable
+// the new syntax, by retrofitting a rule that a poetic number using v1
+// syntax must start with a letter, and can't start with any keyword
+// indicating an arithmetic or logical operator.
+v1_digit_filter
+ = &letter !keywords_which_can_begin_an_expression
+
+v1_poetic_number
+ = _? &v1_digit_filter whole_part:poetic_digits _? separator:('.') _ fractional_part:poetic_digits
+ { new PoeticNumbër (whole_part, separator, fractional_part) }
+ / _? &v1_digit_filter digits:poetic_digits
+ { new PoeticNumbër(digits) }
+
+PDS = ("" _ / [0-9\',;:?!+_/] )
+
+poetic_digits
+ = PDS* head:poetic_digit PDS+ tail:poetic_digits PDS*
+ { head + " " + tail }
+ / d:poetic_digit
+ { d }
+
+poetic_digit
+ = word:("" (letter / [\-'])+)
+
+string = s:quoted_string+ { new Strïng(String.Join("\"", s)) }
+quoted_string
+ = '"' s:("" [^"]*) '"' { s }
+
+keywords_which_can_begin_an_expression
+ = plus / minus / times / divided_by
+ / like / and / or / nor / not / non
+ / null / true / false / empty / mysterious
+ // Putting the here would break EVERYTHING
+
+keyword
+ = keywords_which_can_begin_an_expression
+ / as
+ / as_great
+ / as_small
+ / back
+ / be
+ / build
+ / cast
+ / down
+ / else
+ / end
+ / false
+ / holds
+ / if
+ / into
+ / is
+ / isnt
+ / join
+ / knock
+ / less
+ / let
+ / listen
+ / more
+ / non
+ / not
+ / null
+ / or
+ / over
+ / print
+ / pronoun
+ / put
+ / return
+ / says
+ / split
+ / takes
+ / taking
+ / than
+ / the
+ / to
+ / until
+ / up
+ / while
+
+_ = "" (whitespace / comment)+
+whitespace = [ \t]
+
+comment
+ = line_comment
+ / chordpro_comment
+ / block_comment
+
+line_comment = '#' [^\n]* '\n'
+chordpro_comment = '{' [^\}]* '}' / '[' [^\]]* ']'
+start_comment = '('
+end_comment = ')'
+not_comment = (!start_comment !end_comment .)
+block_comment = start_comment (comment / not_comment)* end_comment
+
+
+
+
+above = 'above' !letter / over
+and = 'and' !letter
+arguments = 'arguments' !letter
+around = 'around' !letter / 'round' !letter
+as = 'as' !letter
+as_great = 'great' !letter / 'high' !letter / 'big' !letter / 'strong' !letter
+as_small = 'less' !letter / 'low' !letter / 'small' !letter / 'weak' !letter
+at = 'at' !letter
+back = 'back' !letter
+be = 'be' !letter
+break = 'break' !letter
+build = 'build' !letter
+call = 'call' !letter
+cast = 'cast' !letter / 'burn' !letter
+continue = 'continue' !letter / 'take' !letter
+debug = 'debug' !letter
+divided_by = 'divided' _ 'by' !letter / 'between' !letter / over
+down = 'down' !letter
+else = 'else' !letter / 'otherwise' !letter
+empty = 'empty' !letter / 'silent' !letter / 'silence' !letter
+end = 'end' !letter / 'yeah' !letter / 'baby' !letter / 'oh' !letter
+every = 'every' !letter
+exit = 'exit' !letter
+exactly = 'exactly' !letter / 'totally' !letter / 'really' !letter
+false = "false" !letter / "lies" !letter / "no" !letter / "wrong"!letter
+for = 'for' !letter
+his = 'his' !letter / 'her' !letter
+holds = 'hold' !letter / 'holds' !letter
+if = 'if' !letter / 'when' !letter
+in = 'in' !letter
+into = 'into' !letter / 'in' !letter
+is = 'is' !letter / 'was' !letter / 'are' !letter / 'were' !letter / 'am' !letter
+isnt = "isnt" !letter / "isn't" !letter / 'aint' !letter / "ain't" !letter / "wasn't" !letter / "wasnt" !letter / "aren't" !letter / "arent" !letter / "weren't" !letter / "werent" !letter
+join = 'join' !letter / 'unite' !letter / 'gather' !letter
+knock = 'knock' !letter
+less = 'less' !letter / 'lower' / 'smaller' !letter / 'weaker' !letter
+let = 'let' !letter
+like = 'like' !letter / 'so' !letter
+listen = 'listen' !letter
+minus = 'minus' !letter / 'without' !letter
+more = 'greater' / 'higher' / 'bigger' / 'stronger' !letter / 'more' !letter
+mysterious = 'mysterious' !letter
+non = 'non' !letter
+nor = 'nor' !letter
+not = 'not' !letter
+now = 'now' !letter
+null = 'null' !letter / 'nothing' !letter / 'nowhere' !letter / 'nobody' !letter / 'gone'!letter
+of = 'of' !letter
+or = 'or' !letter
+outside = 'outside' !letter
+over = 'over' !letter
+plus = 'plus' !letter / 'with' !letter
+pop = 'pop' !letter
+print = "print" !letter / "shout" !letter / "say" !letter / "scream" !letter / "whisper" !letter
+pronoun = 'they' !letter / 'them'!letter / 'she' !letter / 'him' !letter / 'her' !letter / 'hir' !letter / 'zie' !letter / 'zir' !letter / 'xem' !letter / 'ver'!letter / 'ze' !letter / 've' !letter / 'xe' !letter / 'it' !letter / 'he'!letter / 'you' !letter / 'me' !letter / 'i' !letter
+push = 'rock' !letter / 'push' !letter
+put = 'put' !letter
+return = 'return' !letter / 'giving' !letter / 'give' !letter / 'send' !letter
+roll = 'roll' !letter
+says = 'say' !letter / 'says' !letter / 'said' !letter
+split = 'cut' !letter / 'split' !letter / 'shatter' !letter
+takes = 'takes' !letter / 'wants' !letter
+taking = 'taking' !letter
+than = 'than' !letter
+the = 'an' !letter / 'a' !letter / 'the' !letter / 'my' !letter / 'your' !letter / 'our' !letter / 'their' !letter
+then = 'then' !letter
+times = 'times' !letter / of
+to = 'to' !letter
+true = "true" !letter / "yes" !letter / "ok" !letter / "right"!letter
+turn = 'turn' !letter
+under = 'under' !letter / 'below' !letter
+until = 'until' !letter
+up = 'up' !letter
+using = 'using' !letter / 'with' !letter
+while = 'while' !letter
+with = 'with' !letter
+world = 'world' !letter
+write = 'write' !letter
+
+
+
+// argument
+// = molecule
+//
+// molecule
+// = molecular_boolean
+// / primary
+//
+// molecular_boolean = molecular_binary_or
+//
+// molecular_binary_or
+// = lhs:molecular_binary_nor _ or _ rhs:molecular_binary_or { new Binary(Operator.Or, lhs, rhs) }
+// / molecular_binary_nor
+//
+// molecular_binary_nor
+// = lhs:molecular_binary_and _ nor _ rhs:molecular_binary_nor { new Binary(Operator.Nor, lhs, rhs) }
+// / molecular_binary_and
+//
+// molecular_binary_and
+// = lhs:molecular_equality _ and _ rhs:molecular_binary_and { new Binary(Operator.And, lhs, rhs) }
+// / molecular_equality
+//
+// molecular_equality
+// = lhs:molecular_unary op:(_is / _isnt) _ rhs:molecular_equality { new Binary(op, lhs, rhs) }
+// / molecular_unary
+//
+// molecular_unary
+// = not_ u:molecular_unary { new Unary(Operator.Not, u) }
+// / molecular_comparison
+//
+// molecular_comparison
+// = lhs:molecular_addition op:comparator rhs:molecular_comparison { new Binary(op, lhs, rhs) }
+// / molecular_addition
+//
+// molecular_addition -memoize
+// = lhs:molecular_addition op:(_plus_/_minus_) rhs:molecular_product
+// { new Binary(op, lhs, rhs) }
+// / molecular_product
+//
+// molecular_product -memoize
+// = lhs:molecular_product op:(_divide_/_times_) rhs:primary
+// { new Binary(op, lhs, rhs) }
+// / primary
+
+// molecule_list >
+// = head:molecule _ALS_ tail:argument_list { head.Concat(tail) }
+// / head:molecule { new List { head } }
+
+// binary_rhs >
+// = primary_list
+// / e:expression { new List { e } }
diff --git a/Starship/Rockstar.Profiler/Program.cs b/Starship/Rockstar.Profiler/Program.cs
new file mode 100644
index 00000000..1ca923e0
--- /dev/null
+++ b/Starship/Rockstar.Profiler/Program.cs
@@ -0,0 +1,56 @@
+using System.Diagnostics;
+using Rockstar.Engine;
+
+const string DIRECTORY = "D:/Projects/github/RockstarLang/rockstar2/Starship/Rockstar.Test/programs";
+var fullPath = Path.GetFullPath(DIRECTORY);
+Console.WriteLine(fullPath);
+var files = Directory.GetFiles(fullPath, "*.rock", SearchOption.AllDirectories);
+var parser = new Parser();
+var stopwatch = new Stopwatch();
+
+const int FACTOR = 2;
+foreach (var file in files) {
+ stopwatch.Restart();
+ var parseTime = 0;
+ var runTime = 0;
+ bool error;
+ Exception? exception = null;
+ try {
+ var program = parser.Parse(File.ReadAllText(file));
+ parseTime = (int) stopwatch.ElapsedMilliseconds;
+ var io = new StringBuilderIO(() => "1");
+ var e = new RockstarEnvironment(io);
+ error = false;
+ e.Execute(program);
+ stopwatch.Restart();
+ for (var i = 0; i < 10; i++) e.Execute(program);
+ runTime = (int) stopwatch.ElapsedMilliseconds;
+ } catch (Exception ex) {
+ exception = ex;
+ error = true;
+ }
+ stopwatch.Stop();
+ var reportPath = file.Replace(fullPath, "").TrimStart(Path.DirectorySeparatorChar);
+ Console.ForegroundColor = ConsoleColor.Blue;
+ Console.Write(String.Empty.PadRight(parseTime / FACTOR, '#'));
+ if (error) {
+ Console.ForegroundColor = ConsoleColor.Red;
+ var boom = exception == default ? "TIMEOUT" : exception.Message;
+ Console.Write(boom);
+ var pad = Math.Max(0, 60 - (parseTime / FACTOR) - boom.Length);
+ Console.Write(String.Empty.PadRight(pad));
+ } else {
+ Console.ForegroundColor = ConsoleColor.Yellow;
+ Console.Write(String.Empty.PadRight(runTime / FACTOR, '#'));
+ var pad = Math.Max(0, 60 - (parseTime / FACTOR - runTime / FACTOR));
+ Console.Write(String.Empty.PadRight(pad));
+ }
+
+ Console.ForegroundColor = ConsoleColor.Blue;
+ Console.Write(parseTime.ToString().PadLeft(5));
+ Console.Write("ms ");
+ Console.ForegroundColor = ConsoleColor.Yellow;
+ Console.Write(runTime.ToString().PadLeft(5));
+ Console.Write("ms ");
+ Console.WriteLine(reportPath);
+}
\ No newline at end of file
diff --git a/Starship/Rockstar.Profiler/Rockstar.Profiler.csproj b/Starship/Rockstar.Profiler/Rockstar.Profiler.csproj
new file mode 100644
index 00000000..657edadf
--- /dev/null
+++ b/Starship/Rockstar.Profiler/Rockstar.Profiler.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/Starship/Rockstar.Test/EnvironmentTests.cs b/Starship/Rockstar.Test/EnvironmentTests.cs
new file mode 100644
index 00000000..177aeeab
--- /dev/null
+++ b/Starship/Rockstar.Test/EnvironmentTests.cs
@@ -0,0 +1,76 @@
+using Rockstar.Engine.Expressions;
+using Rockstar.Engine.Values;
+
+namespace Rockstar.Test {
+ public class EnvironmentTests {
+
+ [Fact]
+ public void GlobalScopeWorks() {
+ var io = new StringBuilderIO();
+ var e1 = new RockstarEnvironment(io);
+ var foo = new SimpleVariable("foo");
+ var bar = new SimpleVariable("bar");
+ var value = new Numbër(123);
+ var e2 = e1.Extend();
+ var e3 = e2.Extend();
+ var e4 = e3.Extend();
+ e1.SetVariable(foo, value, Scope.Local);
+ e2.SetVariable(bar, value, Scope.Local);
+ e4.GetStore(foo, Scope.Global).ShouldBe(e1);
+ e4.GetStore(bar, Scope.Global).ShouldBe(e2);
+
+ var n = new Numbër(456);
+ e4.SetVariable(foo, n, Scope.Local);
+ e4.Lookup(foo).ShouldBe(n);
+ e3.Lookup(foo).ShouldBe(value);
+ e2.Lookup(foo).ShouldBe(value);
+ e1.Lookup(foo).ShouldBe(value);
+
+ e4.SetVariable(foo, n, Scope.Local);
+ e4.Lookup(foo).ShouldBe(n);
+ e3.Lookup(foo).ShouldBe(value);
+ e2.Lookup(foo).ShouldBe(value);
+ e1.Lookup(foo).ShouldBe(value);
+
+ var s1 = new Strïng("foo");
+ var s2 = new Strïng("bar");
+ e4.SetVariable(bar, s1);
+ e1.SetVariable(bar, s2);
+ e4.Lookup(bar).ShouldBe(s1);
+ e3.Lookup(bar).ShouldBe(s1);
+ e2.Lookup(bar).ShouldBe(s1);
+ e1.Lookup(bar).ShouldBe(s2);
+ }
+
+ [Theory]
+ [InlineData("MY VARIABLE", "my variable")]
+ [InlineData("thE SKY", "the\tsky")]
+ [InlineData("your dreams", "Your Dreams")]
+ [InlineData("a GIRL", "a girl")]
+ [InlineData("Our price", "OUR PRICE")]
+ public void CommonVariableNamesAreNormalized(string name1, string name2) {
+ var io = new StringBuilderIO();
+ var e = new RockstarEnvironment(io);
+ var v1 = new CommonVariable(name1);
+ var v2 = new CommonVariable(name2);
+ v1.Key.ShouldBe(v2.Key);
+ e.Assign(v1, new Numbër(123));
+ e.Lookup(v2).ShouldBe(new Numbër(123));
+ }
+
+ [Theory]
+ [InlineData("Doctor Feelgood", "DOCTOR FEELGOOD")]
+ [InlineData("Billie Jean", "BILLIE JEAN")]
+ [InlineData("Billy Ray Cyrus", "BILLY RAY \tCyrus")]
+ [InlineData("Income Tax", "INCome TaX")]
+ public void ProperVariableNamesAreNormalized(string name1, string name2) {
+ var io = new StringBuilderIO();
+ var e = new RockstarEnvironment(io);
+ var v1 = new ProperVariable(name1);
+ var v2 = new ProperVariable(name2);
+ v1.Key.ShouldBe(v2.Key);
+ e.Assign(v1, new Numbër(123));
+ e.Lookup(v2).ShouldBe(new Numbër(123));
+ }
+ }
+}
diff --git a/Starship/Rockstar.Test/Examples/ExampleTests.cs b/Starship/Rockstar.Test/Examples/ExampleTests.cs
new file mode 100644
index 00000000..f53172f8
--- /dev/null
+++ b/Starship/Rockstar.Test/Examples/ExampleTests.cs
@@ -0,0 +1,13 @@
+//namespace Rockstar.Test.Examples;
+
+//public class ExampleTests(ITestOutputHelper output) : FixtureBase(output) {
+
+// [Theory]
+// [MemberData(nameof(AllExampleFiles))]
+// public void RunV1Fixtures(RockFile file) => RunFile(file);
+
+// [Theory]
+// [MemberData(nameof(AllExampleFiles))]
+// public void ParseV1Fixtures(RockFile file) => TestParser(file);
+
+//}
\ No newline at end of file
diff --git a/Starship/Rockstar.Test/FixtureBase.cs b/Starship/Rockstar.Test/FixtureBase.cs
new file mode 100644
index 00000000..0d758bd1
--- /dev/null
+++ b/Starship/Rockstar.Test/FixtureBase.cs
@@ -0,0 +1,100 @@
+namespace Rockstar.Test;
+
+public abstract class FixtureBase(ITestOutputHelper testOutput) : RockstarTestBase(testOutput) {
+
+ protected static readonly string ExamplesDirectory = Path.Combine("programs", "examples");
+ protected static readonly string FixturesDirectory = Path.Combine("programs", "fixtures");
+ protected static readonly string V1FixturesDirectory = Path.Combine("programs", "v1-fixtures");
+
+ private static IEnumerable ListRockFiles(string relativePath) {
+ var allFiles = Directory.GetFiles(relativePath, "*.rock", SearchOption.AllDirectories);
+ return allFiles.Select(filePath => new RockFile(filePath)).ToList();
+ }
+
+ private static IEnumerable