Skip to content

Commit

Permalink
Add multiple range slice syntax (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
mitchpaulus committed Jan 4, 2025
1 parent e1f5b74 commit d7950d7
Show file tree
Hide file tree
Showing 6 changed files with 281 additions and 1 deletion.
127 changes: 127 additions & 0 deletions mshell/Evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,133 @@ MainLoop:
parseQuote := t.(*MShellParseQuote)
q := MShellQuotation{Tokens: parseQuote.Items, StandardInputFile: "", StandardOutputFile: "", StandardErrorFile: "", Variables: context.Variables, MShellParseQuote: parseQuote}
stack.Push(&q)
case *MShellIndexerList:
obj1, err := stack.Pop()
if err != nil {
startToken := t.GetStartToken()
return FailWithMessage(fmt.Sprintf("%d:%d: Cannot do 'indexer' operation on an empty stack.\n", startToken.Line, startToken.Column))
}

indexerList := t.(*MShellIndexerList)
if len(indexerList.Indexers) == 1 && indexerList.Indexers[0].(Token).Type == INDEXER {
t := indexerList.Indexers[0].(Token)
// Indexer is a digit between ':' and ':'. Remove ends and parse the number
indexStr := t.Lexeme[1 : len(t.Lexeme)-1]
index, err := strconv.Atoi(indexStr)
if err != nil {
return FailWithMessage(fmt.Sprintf("%d:%d: Error parsing index: %s\n", t.Line, t.Column, err.Error()))
}

result, err := obj1.Index(index)
if err != nil {
return FailWithMessage(fmt.Sprintf("%d:%d: %s", t.Line, t.Column, err.Error()))
}
stack.Push(result)
} else {
var newObject MShellObject;
newObject = nil

for _, indexer := range indexerList.Indexers {
indexerToken := indexer.(Token)
switch indexerToken.Type {
case INDEXER:
indexStr := indexerToken.Lexeme[1 : len(indexerToken.Lexeme)-1]
index, err := strconv.Atoi(indexStr)
if err != nil {
return FailWithMessage(fmt.Sprintf("%d:%d: Error parsing index: %s\n", indexerToken.Line, indexerToken.Column, err.Error()))
}

result, err := obj1.Index(index)
if err != nil {
return FailWithMessage(fmt.Sprintf("%d:%d: %s", indexerToken.Line, indexerToken.Column, err.Error()))
}

var wrappedResult MShellObject
switch obj1.(type) {
case *MShellList:
wrappedResult = NewList(0)
wrappedResult.(*MShellList).Items = append(wrappedResult.(*MShellList).Items, result)
case *MShellQuotation:
wrappedResult = &MShellQuotation{Tokens: []MShellParseItem{result.(MShellParseItem)}, StandardInputFile: "", StandardOutputFile: "", StandardErrorFile: "", Variables: context.Variables, MShellParseQuote: nil}
case *MShellPipe:
newList := NewList(0)
wrappedResult = &MShellPipe{List: *newList, StdoutBehavior: STDOUT_NONE }
wrappedResult.(*MShellPipe).List.Items = append(wrappedResult.(*MShellPipe).List.Items, result)
default:
wrappedResult = result
}

if newObject == nil {
newObject = wrappedResult
} else {
newObject, err = newObject.Concat(wrappedResult)
if err != nil {
return FailWithMessage(fmt.Sprintf("%d:%d: %s", indexerToken.Line, indexerToken.Column, err.Error()))
}
}
case STARTINDEXER, ENDINDEXER:
var indexStr string
// Parse the index value
if indexerToken.Type == ENDINDEXER {
indexStr = indexerToken.Lexeme[1:]
} else {
indexStr = indexerToken.Lexeme[:len(indexerToken.Lexeme)-1]
}

index, err := strconv.Atoi(indexStr)
if err != nil {
return FailWithMessage(fmt.Sprintf("%d:%d: Error parsing index: %s\n", indexerToken.Line, indexerToken.Column, err.Error()))
}

var result MShellObject
if indexerToken.Type == ENDINDEXER {
result, err = obj1.SliceEnd(index)
} else {
result, err = obj1.SliceStart(index)
}

if err != nil {
return FailWithMessage(fmt.Sprintf("%d:%d: %s", indexerToken.Line, indexerToken.Column, err.Error()))
}

if newObject == nil {
newObject = result
} else {
newObject, err = newObject.Concat(result)
if err != nil {
return FailWithMessage(fmt.Sprintf("%d:%d: %s", indexerToken.Line, indexerToken.Column, err.Error()))
}
}

case SLICEINDEXER:
// StartInc:EndExc
parts := strings.Split(indexerToken.Lexeme, ":")
startInt, err := strconv.Atoi(parts[0])
endInt, err2 := strconv.Atoi(parts[1])

if err != nil || err2 != nil {
return FailWithMessage(fmt.Sprintf("%d:%d: Error parsing slice indexes: %s\n", indexerToken.Line, indexerToken.Column, err.Error()))
}

result, err := obj1.Slice(startInt, endInt)
if err != nil {
return FailWithMessage(fmt.Sprintf("%d:%d: Cannot slice index a %s.\n", indexerToken.Line, indexerToken.Column, obj1.TypeName()))
}

if newObject == nil {
newObject = result
} else {
newObject, err = newObject.Concat(result)
if err != nil {
return FailWithMessage(fmt.Sprintf("%d:%d: %s", indexerToken.Line, indexerToken.Column, err.Error()))
}
}

}
}

stack.Push(newObject)
}
case Token:
t := t.(Token)

Expand Down
5 changes: 5 additions & 0 deletions mshell/Lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const (
DOUBLEDASH
AMPERSAND
PATH
COMMA
)

func (t TokenType) String() string {
Expand Down Expand Up @@ -177,6 +178,8 @@ func (t TokenType) String() string {
return "AMPERSAND"
case PATH:
return "PATH"
case COMMA:
return "COMMA"
default:
return "UNKNOWN"
}
Expand Down Expand Up @@ -469,6 +472,8 @@ func (l *Lexer) scanToken() Token {
return l.parseLiteralOrKeyword()
case '=':
return l.makeToken(EQUALS)
case ',':
return l.makeToken(COMMA)
case '&':
return l.makeToken(AMPERSAND)
case '<':
Expand Down
79 changes: 78 additions & 1 deletion mshell/MShellObject.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type MShellObject interface {
ToJson() string
ToString() string
IndexErrStr() string
Concat(other MShellObject) (MShellObject, error)
}

type MShellSimple struct {
Expand Down Expand Up @@ -874,7 +875,7 @@ func (obj *MShellPath) Slice(startInc int, endExc int) (MShellObject, error) {
if startInc < 0 {
startInc = len(obj.Path) + startInc
}

if endExc < 0 {
endExc = len(obj.Path) + endExc
}
Expand Down Expand Up @@ -982,6 +983,82 @@ func (obj *MShellSimple) ToJson() string {
return fmt.Sprintf("{\"type\": \"Simple\", \"token\": %s}", obj.Token.ToJson())
}

// Concat
func (obj *MShellLiteral) Concat(other MShellObject) (MShellObject, error) {
asLiteral, ok := other.(*MShellLiteral)
if !ok {
return nil, fmt.Errorf("Cannot concatenate a Literal with a %s.\n", other.TypeName())
}

return &MShellLiteral{LiteralText: obj.LiteralText + asLiteral.LiteralText}, nil
}

func (obj *MShellBool) Concat(other MShellObject) (MShellObject, error) {
return nil, fmt.Errorf("Cannot concatenate a boolean.\n")
}

func (obj *MShellQuotation) Concat(other MShellObject) (MShellObject, error) {
asQuotation, ok := other.(*MShellQuotation)
if !ok {
return nil, fmt.Errorf("Cannot concatenate a Quotation with a %s.\n", other.TypeName())
}

newTokens := make([]MShellParseItem, len(obj.Tokens)+len(asQuotation.Tokens))
copy(newTokens, obj.Tokens)
copy(newTokens[len(obj.Tokens):], asQuotation.Tokens)
return &MShellQuotation{Tokens: newTokens}, nil
}

func (obj *MShellList) Concat(other MShellObject) (MShellObject, error) {
asList, ok := other.(*MShellList)
if !ok {
return nil, fmt.Errorf("Cannot concatenate a List with a %s.\n", other.TypeName())
}

newItems := make([]MShellObject, len(obj.Items)+len(asList.Items))
copy(newItems, obj.Items)
copy(newItems[len(obj.Items):], asList.Items)
return &MShellList{Items: newItems}, nil
}

func (obj *MShellString) Concat(other MShellObject) (MShellObject, error) {
asString, ok := other.(*MShellString)
if !ok {
return nil, fmt.Errorf("Cannot concatenate a String with a %s.\n", other.TypeName())
}

return &MShellString{Content: obj.Content + asString.Content}, nil
}

func (obj *MShellPath) Concat(other MShellObject) (MShellObject, error) {
asPath, ok := other.(*MShellPath)
if !ok {
return nil, fmt.Errorf("Cannot concatenate a Path with a %s.\n", other.TypeName())
}

return &MShellPath{Path: obj.Path + asPath.Path}, nil
}

func (obj *MShellPipe) Concat(other MShellObject) (MShellObject, error) {
asPipe, ok := other.(*MShellPipe)
if !ok {
return nil, fmt.Errorf("Cannot concatenate a Pipe with a %s.\n", other.TypeName())
}

newItems := make([]MShellObject, len(obj.List.Items)+len(asPipe.List.Items))
copy(newItems, obj.List.Items)
copy(newItems[len(obj.List.Items):], asPipe.List.Items)
return &MShellPipe{List: MShellList{Items: newItems}}, nil
}

func (obj *MShellInt) Concat(other MShellObject) (MShellObject, error) {
return nil, fmt.Errorf("Cannot concatenate an integer.\n")
}

func (obj *MShellFloat) Concat(other MShellObject) (MShellObject, error) {
return nil, fmt.Errorf("Cannot concatenate a float.\n")
}

func ParseRawString(inputString string) (string, error) {
// Purpose of this function is to remove outer quotes, handle escape characters
if len(inputString) < 2 {
Expand Down
53 changes: 53 additions & 0 deletions mshell/Parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,37 @@ type MShellParseList struct {
EndToken Token
}

type MShellIndexerList struct {
Indexers []MShellParseItem
}

func (indexerList *MShellIndexerList) DebugString() string {
if len(indexerList.Indexers) == 0 {
return ""
}

builder := strings.Builder{}
builder.WriteString(indexerList.Indexers[0].DebugString())
for i := 1; i < len(indexerList.Indexers); i++ {
builder.WriteString(", ")
builder.WriteString(indexerList.Indexers[i].DebugString())
}
return builder.String()
}

func (indexerList *MShellIndexerList) ToJson() string {
return ToJson(indexerList.Indexers)
}

func (indexerList *MShellIndexerList) GetStartToken() Token {
return indexerList.Indexers[0].GetStartToken()
}

func (indexerList *MShellIndexerList) GetEndToken() Token {
return indexerList.Indexers[len(indexerList.Indexers)-1].GetEndToken()
}


func (list *MShellParseList) GetStartToken() Token {
return list.StartToken
}
Expand Down Expand Up @@ -193,6 +224,28 @@ func (parser *MShellParser) ParseFile() (*MShellFile, error) {
}
// fmt.Fprintf(os.Stderr, "List: %s\n", list.ToJson())
file.Items = append(file.Items, list)
case INDEXER, ENDINDEXER, STARTINDEXER, SLICEINDEXER:
indexerList := &MShellIndexerList{}
indexerList.Indexers = []MShellParseItem{}
indexerList.Indexers = append(indexerList.Indexers, parser.curr)
parser.NextToken()

for {
if parser.curr.Type == COMMA {
parser.NextToken()
if parser.curr.Type == ENDINDEXER || parser.curr.Type == STARTINDEXER || parser.curr.Type == INDEXER || parser.curr.Type == SLICEINDEXER {
indexerList.Indexers = append(indexerList.Indexers, parser.curr)
parser.NextToken()
} else {
// No error here, just a trailing comma which is fine.
break
}
} else {
break
}
}

file.Items = append(file.Items, indexerList)
case DEF:
_ = parser.Match(parser.curr, DEF)
if parser.curr.Type != LITERAL {
Expand Down
13 changes: 13 additions & 0 deletions tests/indexing.msh
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,16 @@ def cjoin ([str] --) ", " join wl end
["a" "b" "c" "d"] "x" -1 setAt cjoin

["a" "b" "c" "d"] reverse cjoin

# Test multiple slices
["0" "1" "2" "3" "4" "5" "6" "7" "8" "9"] testList!

@testList :2:, -1:, 3:5, :2, cjoin
"0123456789" :2:, -1:, 3:5, :2, wl
--0123456789 :2:, -1:, 3:5, :2, wl
`0123456789` :2:, -1:, 3:5, :2, str wl
[
[printf "1\n"]
[echo 2]
[cat]
] | :0:, :2: ;
5 changes: 5 additions & 0 deletions tests/indexing.msh.stdout
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,8 @@ a, b, c, x, d
a, b, x, d
a, b, c, x
d, c, b, a
2, 9, 3, 4, 0, 1
293401
0912--
293401
1

0 comments on commit d7950d7

Please sign in to comment.