From 66807c02c98aaae6d1420ba5c220cd7ac89e8643 Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Thu, 9 Oct 2025 09:48:27 -0700 Subject: [PATCH] clean --- src/compiler/compiler.test.ts | 5 +- src/editor/plugins/keymap.tsx | 1 - src/parser/parser.test.ts | 44 +++++- src/parser/shrimp.grammar | 2 +- src/parser/shrimp.ts | 8 +- today.md | 244 ++++++++++++++++++++++++++++++++++ 6 files changed, 293 insertions(+), 11 deletions(-) create mode 100644 today.md diff --git a/src/compiler/compiler.test.ts b/src/compiler/compiler.test.ts index 5d8aad4..08b5586 100644 --- a/src/compiler/compiler.test.ts +++ b/src/compiler/compiler.test.ts @@ -77,12 +77,13 @@ describe('errors', () => { }) }) -describe('multiline tests', () => { - test.only('multiline function', () => { +describe.skip('multiline tests', () => { + test('multiline function', () => { expect(` add = fn a b: result = a + b result + end add 3 4 `).toEvaluateTo(7) }) diff --git a/src/editor/plugins/keymap.tsx b/src/editor/plugins/keymap.tsx index dd14a72..abbb0b8 100644 --- a/src/editor/plugins/keymap.tsx +++ b/src/editor/plugins/keymap.tsx @@ -43,7 +43,6 @@ const singleLineFilter = EditorState.transactionFilter.of((transaction) => { if (multilineMode) return transaction // Allow everything in multiline mode transaction.changes.iterChanges((fromA, toA, fromB, toB, inserted) => { - console.log(`🌭`, { string: inserted.toString(), newline: inserted.toString().includes('\n') }) if (inserted.toString().includes('\n')) { multilineMode = true updateStatusMessage() diff --git a/src/parser/parser.test.ts b/src/parser/parser.test.ts index a451ba9..0aa725c 100644 --- a/src/parser/parser.test.ts +++ b/src/parser/parser.test.ts @@ -350,13 +350,50 @@ describe('Assign', () => { }) describe('multiline', () => { - test.only('parses multiline strings', () => { + test('parses multiline strings', () => { expect(`'first'\n'second'`).toMatchTree(` String first String second`) }) - test('trims leading and trailing whitespace in expected tree', () => { + test('parses multiline functions', () => { + expect(` + add = fn a b: + result = a + b + result + end + + add 3 4 + `).toMatchTree(` + Assign + Identifier add + = = + FunctionDef + fn fn + Params + Identifier a + Identifier b + : : + Assign + Identifier result + = = + BinOp + Identifier a + operator + + Identifier b + FunctionCallOrIdentifier + Identifier result + + end end + FunctionCall + Identifier add + PositionalArg + Number 3 + PositionalArg + Number 4`) + }) + + test('ignores leading and trailing whitespace in expected tree', () => { expect(` 3 @@ -374,7 +411,8 @@ end Identifier x Identifier y : : - Identifier x + FunctionCallOrIdentifier + Identifier x end end `) }) diff --git a/src/parser/shrimp.grammar b/src/parser/shrimp.grammar index b4a0056..6f0c82f 100644 --- a/src/parser/shrimp.grammar +++ b/src/parser/shrimp.grammar @@ -86,7 +86,7 @@ singleLineFunctionDef { } multilineFunctionDef { - "fn" Params ":" newlineOrSemicolon (expression newlineOrSemicolon)* "end" + "fn" Params ":" newlineOrSemicolon (line newlineOrSemicolon)* "end" } Params { diff --git a/src/parser/shrimp.ts b/src/parser/shrimp.ts index 546ffd3..2564936 100644 --- a/src/parser/shrimp.ts +++ b/src/parser/shrimp.ts @@ -4,9 +4,9 @@ import {tokenizer} from "./tokenizer" import {highlighting} from "./highlight" export const parser = LRParser.deserialize({ version: 14, - states: "'[OVQTOOOqQPO'#DTO!zQUO'#DTO#XQPOOOOQO'#DS'#DSO#xQTO'#CbOOQS'#DQ'#DQO$PQTO'#DVOOQO'#Cn'#CnOOQO'#C}'#C}O$XQPO'#C|OOQS'#Cu'#CuQ$aQTOOOOQS'#DP'#DPOOQS'#Ca'#CaO$hQTO'#ClOOQS'#DO'#DOOOQS'#Cv'#CvO$oQUO,58zO%SQTO,59_O%^QTO,58}O%^QTO,58}O%eQPO,58|O%vQUO'#DTO%}QPO,58|OOQS'#Cw'#CwO&SQTO'#CpO&[QPO,59qOOQS,59h,59hOOQS-E6s-E6sQOQPOOOOQS,59W,59WOOQS-E6t-E6tOOQO1G.y1G.yOOQO'#DT'#DTOOQO1G.i1G.iO&aQPO1G.iOOQS1G.h1G.hOOQS-E6u-E6uO&xQTO1G/]O'SQPO7+$wO'hQTO7+$xO'rQPO'#CxO(TQTO<OAN>O", - stateData: "(b~OoOS~OPQOQUO]UO^UO_UOcVOuTO{ZO~OWwXXwXYwXZwX{qX|qX~OP]OQUO]UO^UO_UOa_OuTOWwXXwXYwXZwX~OhcO{[X|[X~P!VOWdOXdOYeOZeO~OQUO]UO^UO_UOuTO~OPgO~P#gOPiOedP~O{lO|lO~O|nO~PVOP]O~P#gOP]Oa_O{Sa|SaxSa~P#gOPQOcVO~P#gOPrO~P#gOxuOWwXXwXYwXZwX~Ox[X~P!VOxuO~OPiOedX~OewO~OWdOXdOYViZVi{Vi|VixVi~OPrO{yO~P#gOWdOXdOYeOZeO{yq|yq~OPrOf|O~P#gOWdOXdOYeOZeO{}O~OPrOf!PO~P#gO^Z~", - goto: "%[{PPPP|!U!Z!jPPPP|PPP!UP!uP!zPP!uP!}#T#[#bPPP#h#l#s#x$QP$c$rP%V%VUXO[cRhTV`QbgkUOQT[_bcdegwy{cSOT[cdewy{VXO[cRkVQ[ORm[SbQgRpbQjVRvjQ{yR!O{TZO[SYO[RqcVaQbgU^QbgRo_bSOT[cdewy{X]Q_bgUPO[cQfTZrdewy{WROT[cQsdQteQxwTzy{VWO[c", + states: "'[OVQTOOOqQPO'#DTO!zQUO'#DTO#XQPOOOOQO'#DS'#DSO#xQTO'#CbOOQS'#DQ'#DQO$PQTO'#DVOOQO'#Cn'#CnOOQO'#C}'#C}O$XQPO'#C|OOQS'#Cu'#CuQ$aQTOOOOQS'#DP'#DPOOQS'#Ca'#CaO$hQTO'#ClOOQS'#DO'#DOOOQS'#Cv'#CvO$oQUO,58zO%SQTO,59_O%^QTO,58}O%^QTO,58}O%eQPO,58|O%vQUO'#DTO%}QPO,58|OOQS'#Cw'#CwO&SQTO'#CpO&[QPO,59qOOQS,59h,59hOOQS-E6s-E6sQOQPOOOOQS,59W,59WOOQS-E6t-E6tOOQO1G.y1G.yOOQO'#DT'#DTOOQO1G.i1G.iO&aQPO1G.iOOQS1G.h1G.hOOQS-E6u-E6uO&xQTO1G/]O'SQPO7+$wO'hQTO7+$xO'uQPO'#CxO'zQTO<OAN>O", + stateData: "([~OoOS~OPQOQUO]UO^UO_UOcVOuTO{ZO~OWwXXwXYwXZwX{qX|qX~OP]OQUO]UO^UO_UOa_OuTOWwXXwXYwXZwX~OhcO{[X|[X~P!VOWdOXdOYeOZeO~OQUO]UO^UO_UOuTO~OPgO~P#gOPiOedP~O{lO|lO~O|nO~PVOP]O~P#gOP]Oa_O{Sa|SaxSa~P#gOPQOcVO~P#gOPrO~P#gOxuOWwXXwXYwXZwX~Ox[X~P!VOxuO~OPiOedX~OewO~OWdOXdOYViZVi{Vi|VixVi~OPrO{yO~P#gOWdOXdOYeOZeO{yq|yq~OPQOcVOf|O~P#gO{}O~OPQOcVOf!PO~P#gO^Z~", + goto: "%d{PPPP|!W!]!lPPPP|PPP!WP!wP#OPP!wP#R#X#`#fPPP#l#p#{$Q$YP$k$zP%]%]YXO[cy{RhTV`QbgkUOQT[_bcdegwy{cSOT[cdewy{ZXO[cy{RkVQ[ORm[SbQgRpbQjVRvjQ{yR!O{TZO[SYO[QqcTzy{VaQbgU^QbgRo_bSOT[cdewy{X]Q_bgYPO[cy{QfTVrdew[ROT[cy{QsdQteRxwZWO[cy{", nodeNames: "⚠ Identifier Word Program FunctionCall PositionalArg ParenExpr BinOp operator operator operator operator FunctionCallOrIdentifier String Number Boolean NamedArg NamedArgPrefix FunctionDef fn Params : end Assign =", maxTerm: 44, propSources: [highlighting], @@ -15,5 +15,5 @@ export const parser = LRParser.deserialize({ tokenData: "(j~ReXY!dYZ!ipq!dwx!nxy#]yz#bz{#g{|#l}!O#q!P!Q$d!Q![#y![!]$i!]!^!i!_!`$n#T#X$s#X#Y%R#Y#Z%|#Z#h$s#h#i'u#i#o$s~~(e~!iOo~~!nO{~~!qTOw!nwx#Qx;'S!n;'S;=`#V<%lO!n~#VO]~~#YP;=`<%l!n~#bOu~~#gOx~~#lOW~~#qOY~~#vPZ~!Q![#y~$OQ^~!O!P$U!Q![#y~$XP!Q![$[~$aP^~!Q![$[~$iOX~~$nOe~~$sOh~Q$vQ!_!`$|#T#o$sQ%ROaQR%US!_!`$|#T#b$s#b#c%b#c#o$sR%eS!_!`$|#T#W$s#W#X%q#X#o$sR%vQfP!_!`$|#T#o$s~&PT!_!`$|#T#U&`#U#b$s#b#c'j#c#o$s~&cS!_!`$|#T#`$s#`#a&o#a#o$s~&rS!_!`$|#T#g$s#g#h'O#h#o$s~'RS!_!`$|#T#X$s#X#Y'_#Y#o$s~'dQ_~!_!`$|#T#o$sR'oQcP!_!`$|#T#o$s~'xS!_!`$|#T#f$s#f#g(U#g#o$s~(XS!_!`$|#T#i$s#i#j'O#j#o$s~(jO|~", tokenizers: [0, 1, tokenizer], topRules: {"Program":[0,3]}, - tokenPrec: 337 + tokenPrec: 331 }) diff --git a/today.md b/today.md new file mode 100644 index 0000000..b174618 --- /dev/null +++ b/today.md @@ -0,0 +1,244 @@ +# 🌟 Modern Language Inspiration & Implementation Plan + +## Language Research Summary + +### Pipe Operators Across Languages + +| Language | Syntax | Placeholder | Notes | +|----------|--------|-------------|-------| +| **Gleam** | `\|>` | `_` | Placeholder can go anywhere, enables function capture | +| **Elixir** | `\|>` | `&1`, `&2` | Always first arg by default, numbered placeholders | +| **Nushell** | `\|` | structured data | Pipes structured data, not just text | +| **F#** | `\|>` | none | Always first argument | +| **Raku** | `==>` | `*` | Star placeholder for positioning | + +### Conditional Syntax + +| Language | Single-line | Multi-line | Returns Value | +|----------|------------|------------|---------------| +| **Lua** | `if x then y end` | `if..elseif..else..end` | No (statement) | +| **Luau** | `if x then y else z` | Same | Yes (expression) | +| **Ruby** | `x = y if condition` | `if..elsif..else..end` | Yes | +| **Python** | `y if x else z` | `if..elif..else:` | Yes | +| **Gleam** | N/A | `case` expressions | Yes | + +## 🍤 Shrimp Design Decisions + +### Pipe Operator with Placeholder (`|`) + +**Syntax Choice: `|` with `_` placeholder** + +```shrimp +# Basic pipe with placeholder +"hello world" | upcase _ +"log.txt" | tail _ lines=10 + +# Placeholder positioning flexibility +"error.log" | grep "ERROR" _ | head _ 5 +data | process format="json" input=_ + +# Multiple placeholders (future consideration) +value | combine _ _ +``` + +**Why this design:** +- **`|` over `|>`**: Cleaner, more shell-like +- **`_` placeholder**: Explicit, readable, flexible positioning +- **Gleam-inspired**: Best of functional programming meets shell scripting + +### Conditionals + +**Multi-line syntax:** +```shrimp +if condition: + expression +elsif other-condition: + expression +else: + expression +end +``` + +**Single-line syntax (expression form):** +```shrimp +result = if x = 5: "five" +# Returns nil when false + +result = if x > 0: "positive" else: "non-positive" +# Explicit else for non-nil guarantee +``` + +**Design choices:** +- **`elsif` not `else if`**: Avoids nested parsing complexity (Ruby-style) +- **`:` after conditions**: Consistent with function definitions +- **`=` for equality**: Context-sensitive (assignment vs comparison) +- **`nil` for no-value**: Short, clear, well-understood +- **Expressions return values**: Everything is an expression philosophy + +## 📝 Implementation Plan + +### Phase 1: Grammar Foundation + +**1.1 Add Tokens** +```grammar +@tokens { + // Existing... + "|" // Pipe operator + "_" // Placeholder + "if" // Conditionals + "elsif" + "else" + "nil" // Null value +} +``` + +**1.2 Precedence Updates** +```grammar +@precedence { + multiplicative @left, + additive @left, + pipe @left, // After arithmetic, before assignment + assignment @right, + call +} +``` + +### Phase 2: Grammar Rules + +**2.1 Pipe Expression** +```grammar +PipeExpr { + expression !pipe "|" PipeTarget +} + +PipeTarget { + FunctionCallWithPlaceholder | + FunctionCall // Error in compiler if no placeholder +} + +FunctionCallWithPlaceholder { + Identifier PlaceholderArg+ +} + +PlaceholderArg { + PositionalArg | NamedArg | Placeholder +} + +Placeholder { + "_" +} +``` + +**2.2 Conditional Expression** +```grammar +Conditional { + SingleLineIf | MultiLineIf +} + +SingleLineIf { + "if" Comparison ":" expression ElseClause? +} + +MultiLineIf { + "if" Comparison ":" newlineOrSemicolon + (line newlineOrSemicolon)* + ElsifClause* + ElseClause? + "end" +} + +ElsifClause { + "elsif" Comparison ":" newlineOrSemicolon + (line newlineOrSemicolon)* +} + +ElseClause { + "else" ":" (expression | (newlineOrSemicolon (line newlineOrSemicolon)*)) +} + +Comparison { + expression "=" expression // Context-sensitive in if/elsif +} +``` + +**2.3 Update line rule** +```grammar +line { + PipeExpr | + Conditional | + FunctionCall | + // ... existing rules +} +``` + +### Phase 3: Test Cases + +**Pipe Tests:** +```shrimp +# Basic placeholder +"hello" | upcase _ + +# Named arguments with placeholder +"file.txt" | process _ format="json" + +# Chained pipes +data | filter _ "error" | count _ + +# Placeholder in different positions +5 | subtract 10 _ # 10 - 5 = 5 +``` + +**Conditional Tests:** +```shrimp +# Single line +x = if n = 0: "zero" + +# Single line with else +sign = if n > 0: "positive" else: "negative" + +# Multi-line +if score > 90: + grade = "A" +elsif score > 80: + grade = "B" +else: + grade = "C" +end + +# Nested conditionals +if x > 0: + if y > 0: + quadrant = 1 + end +end +``` + +### Phase 4: Compiler Implementation + +**4.1 PipeExpr Handling** +- Find placeholder position in right side +- Insert left side value at placeholder +- Error if no placeholder found + +**4.2 Conditional Compilation** +- Generate JUMP bytecode for branching +- Handle nil returns for missing else +- Context-aware `=` parsing + +## 🎯 Key Decision Points + +1. **Placeholder syntax**: `_` vs `$` vs `?` → **Choose `_` (Gleam-like)** +2. **Pipe operator**: `|` vs `|>` vs `>>` → **Choose `|` (cleaner)** +3. **Nil naming**: `nil` vs `null` vs `none` → **Choose `nil` (Ruby-like)** +4. **Equality**: Keep `=` context-sensitive or add `==`? → **Keep `=` (simpler)** +5. **Single-line if**: Require else or default nil? → **Default nil (flexible)** + +## 🚀 Next Steps + +1. Update grammar file with new tokens and rules +2. Write comprehensive test cases +3. Implement compiler support for pipes +4. Implement conditional bytecode generation +5. Test edge cases and error handling + +This plan combines the best ideas from modern languages while maintaining Shrimp's shell-like simplicity and functional philosophy! \ No newline at end of file