Skip to content

Commit

Permalink
Add lexical variable scoping (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
mitchpaulus authored Dec 9, 2024
1 parent 1965077 commit ddbd490
Show file tree
Hide file tree
Showing 10 changed files with 54 additions and 20 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,16 +113,16 @@ wt (reverse wjoin wl) each
# for (i = 1; i <= NF; i = i + 1) sum = sum + $i
# print sum
# }
.. (wsplit (toFloat) map sum wl) each
wt ((toFloat) map sum str wl) each
# 19. Add up all fields in all lines and print the sum
# { for (i = 1; i <= NF; i = i + 1) sum = sum + $i }
# END { print sum }
.. (wsplit (toFloat) map sum) map sum wl
wt ((toFloat) map sum) sum wl
# 20. Print every line after replacing each field by its absolute value
# { for (i = 1; i <= NF; i = i + 1) $i = ($i < 0) ? -$i : $i; print }
.. (wsplit (toFloat abs) map wjoin wl) each
wt ((toFloat abs) map wjoin wl) each
```
Expand Down
4 changes: 4 additions & 0 deletions examples/awk/18.awk
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{ sum = 0
for (i = 1; i <= NF; i = i + 1) sum = sum + $i
print sum
}
3 changes: 3 additions & 0 deletions examples/awk/18.data
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
1 2 3
4 5 6
7 8 9
1 change: 1 addition & 0 deletions examples/awk/18.msh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
wt ((toFloat) map sum str wl) each
1 change: 1 addition & 0 deletions examples/awk/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,6 @@ emp_test 14
emp_test 15
emp_test 16
emp_test 17
data_test 18

exit "$FAIL"
4 changes: 3 additions & 1 deletion lib/std.msh
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ def each
0 each-idx! # index
(
[(@each-idx @each-len >=) (break)] if
# "Each idx: " w @each-idx w " Each len: " w @each-len wl
over @each-idx nth # Get current item
over x # Copy over quote, execute
over x # Copy over quote, execute
@each-idx 1 + each-idx! # inc index
# "Each idx: " w @each-idx wl
) loop

# Drop list and quote, total length, index
Expand Down
51 changes: 36 additions & 15 deletions mshell/Evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ func (objList *MShellStack) String() string {
type EvalState struct {
PositionalArgs []string
LoopDepth int
Variables map[string]MShellObject

StopOnError bool
}
Expand All @@ -62,6 +61,7 @@ type EvalResult struct {
type ExecuteContext struct {
StandardInput io.Reader
StandardOutput io.Writer
Variables map[string]MShellObject
}

func SimpleSuccess() EvalResult {
Expand Down Expand Up @@ -103,7 +103,7 @@ MainLoop:
stack.Push(&MShellList{Items: listStack, StandardInputFile: "", StandardOutputFile: "", StdoutBehavior: STDOUT_NONE})
case *MShellParseQuote:
parseQuote := t.(*MShellParseQuote)
q := MShellQuotation{Tokens: parseQuote.Items, StandardInputFile: "", StandardOutputFile: "", StandardErrorFile: ""}
q := MShellQuotation{Tokens: parseQuote.Items, StandardInputFile: "", StandardOutputFile: "", StandardErrorFile: "", Variables: context.Variables}
stack.Push(&q)
case Token:
t := t.(Token)
Expand All @@ -116,7 +116,13 @@ MainLoop:
for _, definition := range definitions {
if definition.Name == t.Lexeme {
// Evaluate the definition
result := state.Evaluate(definition.Items, stack, context, definitions)

var newContext ExecuteContext
newContext.Variables = make(map[string]MShellObject)
newContext.StandardInput = context.StandardInput
newContext.StandardOutput = context.StandardOutput

result := state.Evaluate(definition.Items, stack, newContext, definitions)
if !result.Success || result.BreakNum > 0 {
return result
}
Expand Down Expand Up @@ -661,7 +667,7 @@ MainLoop:
return FailWithMessage(fmt.Sprintf("%d:%d: Error setting OLDPWD: %s\n", t.Line, t.Column, err.Error()))
}

state.Variables["OLDPWD"] = &MShellString{oldPwd}
context.Variables["OLDPWD"] = &MShellString{oldPwd}

err = os.Chdir(dir)
if err != nil {
Expand All @@ -679,7 +685,7 @@ MainLoop:
return FailWithMessage(fmt.Sprintf("%d:%d: Error setting PWD: %s\n", t.Line, t.Column, err.Error()))
}

state.Variables["PWD"] = &MShellString{pwd}
context.Variables["PWD"] = &MShellString{pwd}
} else if t.Lexeme == "in" {
substring, err := stack.Pop()
if err != nil {
Expand Down Expand Up @@ -916,9 +922,20 @@ MainLoop:
switch obj2.(type) {
case *MShellInt:
stack.Push(&MShellInt{obj2.(*MShellInt).Value + obj1.(*MShellInt).Value})
case *MShellFloat:
stack.Push(&MShellFloat{float64(obj2.(*MShellFloat).Value) + float64(obj1.(*MShellInt).Value)})
default:
return FailWithMessage(fmt.Sprintf("%d:%d: Cannot add an integer to a %s.\n", t.Line, t.Column, obj2.TypeName()))
}
case *MShellFloat:
switch obj2.(type) {
case *MShellFloat:
stack.Push(&MShellFloat{obj2.(*MShellFloat).Value + obj1.(*MShellFloat).Value})
case *MShellInt:
stack.Push(&MShellFloat{float64(obj2.(*MShellInt).Value) + obj1.(*MShellFloat).Value})
default:
return FailWithMessage(fmt.Sprintf("%d:%d: Cannot add a float to a %s.\n", t.Line, t.Column, obj2.TypeName()))
}
case *MShellString:
switch obj2.(type) {
case *MShellString:
Expand Down Expand Up @@ -946,7 +963,7 @@ MainLoop:
stack.Push(newList)
}
default:
return FailWithMessage(fmt.Sprintf("%d:%d: Cannot apply '+' to a %s to a %s.\n", t.Line, t.Column, obj2.TypeName(), obj1.TypeName()))
return FailWithMessage(fmt.Sprintf("%d:%d: Cannot apply '+' between a %s and a %s.\n", t.Line, t.Column, obj2.TypeName(), obj1.TypeName()))
}
} else if t.Type == MINUS {
obj1, err := stack.Pop()
Expand Down Expand Up @@ -1130,10 +1147,10 @@ MainLoop:
return FailWithMessage(fmt.Sprintf("%d:%d: Nothing on stack to store into variable %s.\n", t.Line, t.Column, varName))
}

state.Variables[varName] = obj
context.Variables[varName] = obj
} else if t.Type == VARRETRIEVE {
name := t.Lexeme[1:] // Remove the leading @
obj, found_mshell_variable := state.Variables[name]
obj, found_mshell_variable := context.Variables[name]
if found_mshell_variable {
stack.Push(obj)
} else {
Expand All @@ -1145,7 +1162,7 @@ MainLoop:
var message strings.Builder
message.WriteString(fmt.Sprintf("%d:%d: Variable %s not found.\n", t.Line, t.Column, name))
message.WriteString("Variables:\n")
for key := range state.Variables {
for key := range context.Variables {
message.WriteString(fmt.Sprintf(" %s\n", key))
}
return FailWithMessage(message.String())
Expand All @@ -1166,17 +1183,18 @@ MainLoop:
return FailWithMessage(fmt.Sprintf("%d:%d: Loop quotation needs a minimum of one token.\n", t.Line, t.Column))
}

context := ExecuteContext{
loopContext := ExecuteContext{
StandardInput: nil,
StandardOutput: nil,
Variables: context.Variables,
}

if quotation.StandardInputFile != "" {
file, err := os.Open(quotation.StandardInputFile)
if err != nil {
return FailWithMessage(fmt.Sprintf("%d:%d: Error opening file %s for reading: %s\n", t.Line, t.Column, quotation.StandardInputFile, err.Error()))
}
context.StandardInput = file
loopContext.StandardInput = file
defer file.Close()
}

Expand All @@ -1185,7 +1203,7 @@ MainLoop:
if err != nil {
return FailWithMessage(fmt.Sprintf("%d:%d: Error opening file %s for writing: %s\n", t.Line, t.Column, quotation.StandardOutputFile, err.Error()))
}
context.StandardOutput = file
loopContext.StandardOutput = file
defer file.Close()
}

Expand All @@ -1196,7 +1214,7 @@ MainLoop:
breakDiff := 0

for loopCount < maxLoops {
result := state.Evaluate(quotation.Tokens, stack, context, definitions)
result := state.Evaluate(quotation.Tokens, stack, loopContext, definitions)

if !result.Success {
return result
Expand Down Expand Up @@ -1297,6 +1315,7 @@ MainLoop:
// Default to stdout of this process itself
quoteContext.StandardOutput = os.Stdout
}
quoteContext.Variables = quotation.Variables

result := state.Evaluate(quotation.Tokens, stack, quoteContext, definitions)
if !result.Success {
Expand Down Expand Up @@ -1548,9 +1567,9 @@ MainLoop:
}

// Check that varName is in state.Variables, and varName is string or literal
varValue, ok := state.Variables[varName]
varValue, ok := context.Variables[varName]
if !ok {
return FailWithMessage(fmt.Sprintf("%d:%d: Variable %s not found in state.Variables.\n", t.Line, t.Column, varName))
return FailWithMessage(fmt.Sprintf("%d:%d: Variable %s not found in available variables.\n", t.Line, t.Column, varName))
}

switch varValue.(type) {
Expand Down Expand Up @@ -1596,6 +1615,7 @@ func (quotation *MShellQuotation) Execute(state *EvalState, context ExecuteConte
quotationContext := ExecuteContext{
StandardInput: nil,
StandardOutput: nil,
Variables: quotation.Variables,
}

if quotation.StandardInputFile != "" {
Expand Down Expand Up @@ -1800,6 +1820,7 @@ func (state *EvalState) RunPipeline(MShellPipe MShellPipe, context ExecuteContex
newContext := ExecuteContext{
StandardInput: nil,
StandardOutput: nil,
Variables: context.Variables,
}

if i == 0 {
Expand Down
1 change: 1 addition & 0 deletions mshell/MShellObject.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type MShellQuotation struct {
StandardInputFile string
StandardOutputFile string
StandardErrorFile string
Variables map[string]MShellObject
}

// type MShellQuotation2 struct {
Expand Down
2 changes: 1 addition & 1 deletion mshell/Main.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,14 @@ func main() {
state := EvalState{
PositionalArgs: positionalArgs,
LoopDepth: 0,
Variables: make(map[string]MShellObject),
}

var stack MShellStack
stack = []MShellObject{}
context := ExecuteContext{
StandardInput: os.Stdin,
StandardOutput: os.Stdout,
Variables: map[string]MShellObject{},
}

var allDefinitions []MShellDefinition
Expand Down
1 change: 1 addition & 0 deletions tests/indexing.msh.stdout
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ a, b, x, c, d
a, b, c, x, d
a, b, x, d
a, b, c, x
d, c, b, a

0 comments on commit ddbd490

Please sign in to comment.