Skip to content

Commit

Permalink
More type check work
Browse files Browse the repository at this point in the history
  • Loading branch information
mitchpaulus committed Dec 22, 2024
1 parent 4d772df commit a34ad05
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 8 deletions.
47 changes: 40 additions & 7 deletions mshell/Main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ import (
// "runtime"
)

type CliCommand int

const (
CLILEX CliCommand = iota
CLIPARSE
CLITYPECHECK
CLIEXECUTE
)

func main() {
// Enable profiling
// runtime.SetCPUProfileRate(1000)
Expand All @@ -32,8 +41,11 @@ func main() {
// trace.Start(f)
// defer trace.Stop()

printLex := false
printParse := false
command := CLIEXECUTE

// printLex := false
// printParse := false

i := 1

input := ""
Expand All @@ -44,9 +56,13 @@ func main() {
arg := os.Args[i]
i++
if arg == "--lex" {
printLex = true
command = CLILEX
// printLex = true
} else if arg == "--typecheck" {
command = CLITYPECHECK
} else if arg == "--parse" {
printParse = true
command = CLIPARSE
// printParse = true
} else if arg == "-h" || arg == "--help" {
fmt.Println("Usage: mshell [options] INPUT")
fmt.Println("Usage: mshell [options] < INPUT")
Expand Down Expand Up @@ -83,15 +99,15 @@ func main() {

l := NewLexer(input)

if printLex {
if command == CLILEX {
tokens := l.Tokenize()
fmt.Println("Tokens:")
for _, t := range tokens {
// Console.Write($"{t.Line}:{t.Column}:{t.TokenType} {t.RawText}\n");
fmt.Printf("%d:%d:%s %s\n", t.Line, t.Column, t.Type, t.Lexeme)
}
return
} else if printParse {
} else if command == CLIPARSE {
p := MShellParser{lexer: l}
p.NextToken()
file, err := p.ParseFile()
Expand All @@ -103,7 +119,7 @@ func main() {

fmt.Println(file.ToJson())
return
}
}

state := EvalState{
PositionalArgs: positionalArgs,
Expand Down Expand Up @@ -162,6 +178,23 @@ func main() {
}

allDefinitions = append(allDefinitions, file.Definitions...)

if command == CLITYPECHECK {
var typeStack MShellTypeStack
typeStack = make([]MShellType, 0)
typeCheckResult := TypeCheck(file.Items, typeStack, allDefinitions, false)

for _, typeError := range typeCheckResult.Errors {
fmt.Fprintf(os.Stderr, "%s", typeError)
}

if len(typeCheckResult.Errors) > 0 {
os.Exit(1)
} else {
os.Exit(0)
}
}

result := state.Evaluate(file.Items, &stack, context, allDefinitions, callStack)

if !result.Success {
Expand Down
72 changes: 72 additions & 0 deletions mshell/Parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,13 +245,26 @@ type MShellType interface {
ToJson() string
Equals(other MShellType) bool
String() string
ToMshell() string
}

type TypeDefinition struct {
InputTypes []MShellType
OutputTypes []MShellType
}

func (def *TypeDefinition) ToMshell() string {
inputMshellCode := make([]string, len(def.InputTypes))
for i, t := range def.InputTypes {
inputMshellCode[i] = t.ToMshell()
}
outputMshellCode := make([]string, len(def.OutputTypes))
for i, t := range def.OutputTypes {
outputMshellCode[i] = t.ToMshell()
}
return fmt.Sprintf("%s -- %s", strings.Join(inputMshellCode, " "), strings.Join(outputMshellCode, " "))
}

func (def *TypeDefinition) ToJson() string {
return fmt.Sprintf("{\"input\": %s, \"output\": %s}", TypeListToJson(def.InputTypes), TypeListToJson(def.OutputTypes))
}
Expand All @@ -260,6 +273,10 @@ type TypeGeneric struct {
Name string
}

func (generic TypeGeneric) ToMshell() string {
return generic.Name
}

func (generic TypeGeneric) ToJson() string {
return fmt.Sprintf("{ \"generic\": \"%s\" }", generic.Name)
}
Expand All @@ -277,6 +294,10 @@ func (generic TypeGeneric) String() string {

type TypeInt struct { }

func (t TypeInt) ToMshell() string {
return "int"
}

func (t TypeInt) ToJson() string {
return "\"int\""
}
Expand All @@ -292,6 +313,10 @@ func (t TypeInt) String() string {

type TypeFloat struct { }

func (t TypeFloat) ToMshell() string {
return "float"
}

func (t TypeFloat) ToJson() string {
return "\"float\""
}
Expand All @@ -308,6 +333,10 @@ func (t TypeFloat) String() string {

type TypeString struct { }

func (t TypeString) ToMshell() string {
return "str"
}

func (t TypeString) ToJson() string {
return "\"string\""
}
Expand All @@ -323,6 +352,10 @@ func (t TypeString) String() string {

type TypeBool struct { }

func (t TypeBool) ToMshell() string {
return "bool"
}

func (t TypeBool) ToJson() string {
return "\"bool\""
}
Expand All @@ -341,6 +374,10 @@ type TypeList struct {
Count int // This is < 0 if the Count is not known
}

func (list *TypeList) ToMshell() string {
return fmt.Sprintf("[%s]", list.ListType.ToMshell())
}

func (list *TypeList) ToJson() string {
return fmt.Sprintf("{\"list\": %s}", list.ListType.ToJson())
}
Expand Down Expand Up @@ -372,6 +409,18 @@ type TypeTuple struct {
Types []MShellType
}

func (tuple *TypeTuple) ToMshell() string {
builder := strings.Builder{}
builder.WriteString("[")
builder.WriteString(tuple.Types[0].ToMshell())
for i := 1; i < len(tuple.Types); i++ {
builder.WriteString(" ")
builder.WriteString(tuple.Types[i].ToMshell())
}
builder.WriteString("]")
return builder.String()
}

func (tuple *TypeTuple) ToJson() string {
return fmt.Sprintf("{\"tuple\": %s}", TypeListToJson(tuple.Types))
}
Expand Down Expand Up @@ -423,6 +472,29 @@ type TypeQuote struct {
OutputTypes []MShellType
}

func (quote *TypeQuote) ToMshell() string {
builder := strings.Builder{}
builder.WriteString("(")

inputTypes := []string{}
for _, t := range quote.InputTypes {
inputTypes = append(inputTypes, t.ToMshell())
}

outputTypes := []string{}
for _, t := range quote.OutputTypes {
outputTypes = append(outputTypes, t.ToMshell())
}

// Write input types space separated
builder.WriteString(strings.Join(inputTypes, " "))
builder.WriteString(" -- ")
// Write output types space separated
builder.WriteString(strings.Join(outputTypes, " "))
builder.WriteString(")")
return builder.String()
}

func (quote *TypeQuote) ToJson() string {
return fmt.Sprintf("{\"input\": %s, \"output\": %s}", TypeListToJson(quote.InputTypes), TypeListToJson(quote.OutputTypes))
}
Expand Down
95 changes: 95 additions & 0 deletions mshell/TypeChecking.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,33 @@ package main

import (
"fmt"
"strings"
)

var typeDefAdd = []TypeDefinition {
{
InputTypes: []MShellType{TypeInt{}, TypeInt{}},
OutputTypes: []MShellType{TypeInt{}},
},
{
InputTypes: []MShellType{TypeString{}, TypeString{}},
OutputTypes: []MShellType{TypeString{}},
},
{
InputTypes: []MShellType{TypeFloat{}, TypeFloat{}},
OutputTypes: []MShellType{TypeFloat{}},
},
}

type TypeCheckError struct {
Token Token
Message string
}

func (err TypeCheckError) String() string {
return fmt.Sprintf("%d:%d: %s", err.Token.Line, err.Token.Column, err.Message)
}

type TypeCheckResult struct {
Errors []TypeCheckError
InputTypes []MShellType
Expand Down Expand Up @@ -53,6 +73,65 @@ type TypeCheckContext struct {
InQuote bool
}

func TypeCheckTypeDef(stack MShellTypeStack, typeDef TypeDefinition) bool {
if (len(typeDef.InputTypes) > stack.Len()) {
return false
}

for i := 0; i < len(typeDef.InputTypes); i++ {
stackIndex := len(stack) - len(typeDef.InputTypes) + i
stackType := stack[stackIndex]
if !stackType.Equals(typeDef.InputTypes[i]) {
return false
}
}

return true
}

func TypeCheckStack(stack MShellTypeStack, typeDefs []TypeDefinition) (int) {
for idx, typeDef := range typeDefs {
if TypeCheckTypeDef(stack, typeDef) {
return idx
}
}

return -1
}

func TypeCheckErrorMessage(stack MShellTypeStack, typeDefs []TypeDefinition, tokenName string) (string) {
// First check for a length mismatch
minInputLength := 10000
maxInputLength := 0
for _, typeDef := range typeDefs {
if len(typeDef.InputTypes) < minInputLength {
minInputLength = len(typeDef.InputTypes)
}

if len(typeDef.InputTypes) > maxInputLength {
maxInputLength = len(typeDef.InputTypes)
}
}

if minInputLength > stack.Len() {
return fmt.Sprintf("Expected at least %d arguments on the stack for %s, but found %d.\n", minInputLength, tokenName, stack.Len())
}

// Start a builder for the error message
var builder strings.Builder
builder.WriteString(fmt.Sprintf("Could not find a matching type definition for %s.\n", tokenName))
builder.WriteString(fmt.Sprintf("Current stack:\n"))
for i := stack.Len() - maxInputLength; i < stack.Len(); i++ {
builder.WriteString(fmt.Sprintf(" %s\n", stack[i].ToMshell()))
}
builder.WriteString(fmt.Sprintf("Expected types:\n"))
for _, typeDef := range typeDefs {
builder.WriteString(fmt.Sprintf(" %s\n", typeDef.ToMshell()))
}

return builder.String()
}

func TypeCheck(objects []MShellParseItem, stack MShellTypeStack, definitions []MShellDefinition, inQuote bool) TypeCheckResult {
// Short circuit if there are no objects to type check
if len(objects) == 0 {
Expand Down Expand Up @@ -137,6 +216,11 @@ MainLoop:
// Search built-in definitions
}

if typeDef == nil {
stack.Push(&TypeString{})
continue MainLoop
}

if inQuote {
if len(typeDef.InputTypes) > stack.Len() {
// For the difference, add the types to the input stack
Expand Down Expand Up @@ -176,6 +260,15 @@ MainLoop:
stack.Push(TypeInt{})
} else if t.Type == STRING || t.Type == SINGLEQUOTESTRING {
stack.Push(TypeString{})
} else if t.Type == PLUS {
idx := TypeCheckStack(stack, typeDefAdd)
if idx == -1 {
message := TypeCheckErrorMessage(stack, typeDefAdd, "+")

typeCheckResult.Errors = append(typeCheckResult.Errors, TypeCheckError{Token: t, Message: message})
return typeCheckResult
// continue MainLoop
}
} else if t.Type == IF {
obj, err := stack.Pop()
if err != nil {
Expand Down Expand Up @@ -253,6 +346,8 @@ MainLoop:
// The the types of the quotes for the conditions:
// They should all be of the same type, and if they consume anything on the stack, they should put it back.
// The top of the stack should be a boolean.
} else {
typeCheckResult.Errors = append(typeCheckResult.Errors, TypeCheckError{Token: t, Message: fmt.Sprintf("Unexpected token %s.\n", t.Lexeme)})
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/test.sh
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
#!/bin/sh
find . -name '*.msh' | parallel ./test_file.sh
find . -maxdepth 1 -name '*.msh' | parallel ./test_file.sh
1 change: 1 addition & 0 deletions tests/typecheck/test1.msh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"asdf" 10 +

0 comments on commit a34ad05

Please sign in to comment.