├── .idea ├── .name ├── .gitignore ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── vcs.xml ├── runConfigurations │ ├── Compiler___no_compile.xml │ ├── samosac__compile_.xml │ ├── samosac__package_.xml │ ├── samosac__generate_sources_.xml │ ├── samosac__compile___debug_.xml │ └── samosac__native_package_graal_.xml ├── compiler.xml ├── samosac_java.iml ├── jarRepositories.xml ├── misc.xml ├── $PRODUCT_WORKSPACE_FILE$ └── uiDesigner.xml ├── lgtm.yml ├── src ├── test │ ├── data │ │ ├── simulated-input-run │ │ │ ├── inputs │ │ │ │ └── SimplePutin1.samo.inputs │ │ │ ├── expected-run-errors │ │ │ │ └── SimplePutin1.samo.run.err.should │ │ │ ├── expected-run-outputs │ │ │ │ └── SimplePutin1.samo.run.log.should │ │ │ └── test-programs │ │ │ │ └── SimplePutin1.samo │ │ ├── positive-compile-run │ │ │ ├── expected-run-errors │ │ │ │ ├── Ifs1.samo.run.err.should │ │ │ │ ├── Scopes2.samo.run.err.should │ │ │ │ ├── Scopes3.samo.run.err.should │ │ │ │ ├── While1.samo.run.err.should │ │ │ │ ├── Builtins1.samo.run.err.should │ │ │ │ ├── Expressions1.samo.run.err.should │ │ │ │ ├── Functions1.samo.run.err.should │ │ │ │ ├── HelloWorld.samo.run.err.should │ │ │ │ ├── Variables.samo.run.err.should │ │ │ │ └── DisplayingOutputs1.samo.run.err.should │ │ │ ├── expected-run-outputs │ │ │ │ ├── Expressions1.samo.run.log.should │ │ │ │ ├── Ifs1.samo.run.log.should │ │ │ │ ├── Scopes2.samo.run.log.should │ │ │ │ ├── Variables.samo.run.log.should │ │ │ │ ├── HelloWorld.samo.run.log.should │ │ │ │ ├── Scopes3.samo.run.log.should │ │ │ │ ├── While1.samo.run.log.should │ │ │ │ ├── Builtins1.samo.run.log.should │ │ │ │ ├── DisplayingOutputs1.samo.run.log.should │ │ │ │ └── Functions1.samo.run.log.should │ │ │ └── test-programs │ │ │ │ ├── HelloWorld.samo │ │ │ │ ├── Scopes2.samo │ │ │ │ ├── DisplayingOutputs1.samo │ │ │ │ ├── While1.samo │ │ │ │ ├── Scopes3.samo │ │ │ │ ├── Builtins1.samo │ │ │ │ ├── Ifs1.samo │ │ │ │ ├── Variables.samo │ │ │ │ ├── Expressions1.samo │ │ │ │ └── Functions1.samo │ │ ├── negative-compile │ │ │ ├── expected-compile-errors │ │ │ │ ├── Scopes1.samo.compile.err.should │ │ │ │ └── VariablesNoIdentifier1.samo.compile.err.should │ │ │ └── test-programs │ │ │ │ ├── Scopes1.samo │ │ │ │ └── VariablesNoIdentifier1.samo │ │ └── README.md │ └── java │ │ └── com │ │ └── sachett │ │ └── samosa │ │ ├── TestNegativeCompile.java │ │ └── TestPositiveCompileRun.java └── main │ ├── kotlin │ └── com │ │ └── sachett │ │ └── samosa │ │ ├── samosac │ │ ├── symbol │ │ │ ├── SymbolUtils.kt │ │ │ ├── symboltable │ │ │ │ └── SymbolTableRecordEntry.kt │ │ │ ├── SymbolType.kt │ │ │ ├── BoolSymbol.kt │ │ │ ├── StringSymbol.kt │ │ │ ├── IntSymbol.kt │ │ │ ├── ISymbol.kt │ │ │ └── FunctionSymbol.kt │ │ ├── staticchecker │ │ │ ├── evaluators │ │ │ │ ├── IStaticExprEvaluator.kt │ │ │ │ ├── IntExpressionEvaluator.kt │ │ │ │ ├── StringExpressionEvaluator.kt │ │ │ │ └── BoolExpressionEvaluator.kt │ │ │ ├── analyzers │ │ │ │ ├── blocks │ │ │ │ │ ├── ControlBlockType.kt │ │ │ │ │ ├── IFunctionInnerBlock.kt │ │ │ │ │ ├── ControlNode.kt │ │ │ │ │ ├── StrayBlock.kt │ │ │ │ │ ├── WhileControlNode.kt │ │ │ │ │ ├── ControlBlock.kt │ │ │ │ │ ├── CodeBlock.kt │ │ │ │ │ └── IfControlNode.kt │ │ │ │ └── FunctionControlPathAnalyzer.kt │ │ │ ├── StringExpressionChecker.kt │ │ │ ├── IntExpressionChecker.kt │ │ │ ├── ExpressionChecker.kt │ │ │ ├── FunctionReturnsChecker.kt │ │ │ ├── ExpressionTypeDetector.kt │ │ │ └── BoolExpressionChecker.kt │ │ └── compiler │ │ │ └── Compiler.kt │ │ └── logging │ │ └── LoggingUtils.kt │ ├── resources │ ├── sampletwo.samo │ ├── sample_nocomments.samo │ └── sample.samo │ ├── java │ └── com │ │ └── sachett │ │ └── samosa │ │ └── samosac │ │ └── codegen │ │ ├── compoundstmt │ │ ├── ControlNodeCodegenType.java │ │ ├── IControlNodeCodegen.java │ │ ├── IfStmtCodegen.java │ │ └── WhileStmtCodegen.java │ │ ├── CodeGenerator.java │ │ ├── utils │ │ └── delegation │ │ │ ├── CodegenDelegatedMethod.java │ │ │ ├── CodegenMethodMap.java │ │ │ ├── CodegenDelegatable.java │ │ │ └── CodegenDelegationManager.java │ │ ├── expressions │ │ ├── IExprCodegen.java │ │ ├── IntExprCodegen.java │ │ └── StringExprCodegen.java │ │ └── function │ │ └── FunctionGenerationContext.java │ └── antlr4 │ └── com │ └── sachett │ └── samosa │ └── parser │ └── Samosa.g4 ├── samosac_java.iml ├── docs ├── images │ ├── samosa-logo.png │ ├── samosa-logo@2x.png │ ├── samosa-logo@3x.png │ ├── samosa-logo@4.png │ ├── samosa-logo@0-5.png │ ├── samosa-logo@0-75.png │ ├── samosa-logo@1-5.png │ ├── samosa-lang-banner.png │ └── samosa-logo-small.png ├── probables │ ├── index.md │ ├── simple_probables.md │ └── nested_probables.md ├── builtins │ └── index.md ├── _config.yml ├── index.md └── syntax │ └── index.md ├── samosac.iml ├── .run └── Compiler - compile.run.xml ├── .gitignore ├── .github └── workflows │ ├── maven.yml │ └── codeql.yml └── NewFeatures.md /.idea/.name: -------------------------------------------------------------------------------- 1 | samosac -------------------------------------------------------------------------------- /lgtm.yml: -------------------------------------------------------------------------------- 1 | extraction: 2 | java: 3 | index: 4 | java_version: 11 -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /src/test/data/simulated-input-run/inputs/SimplePutin1.samo.inputs: -------------------------------------------------------------------------------- 1 | Hello 2 | -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/expected-run-errors/Ifs1.samo.run.err.should: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/expected-run-errors/Scopes2.samo.run.err.should: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/expected-run-errors/Scopes3.samo.run.err.should: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/expected-run-errors/While1.samo.run.err.should: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/expected-run-errors/Builtins1.samo.run.err.should: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/expected-run-errors/Expressions1.samo.run.err.should: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/expected-run-errors/Functions1.samo.run.err.should: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/expected-run-errors/HelloWorld.samo.run.err.should: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/expected-run-errors/Variables.samo.run.err.should: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/expected-run-outputs/Expressions1.samo.run.log.should: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/expected-run-outputs/Ifs1.samo.run.log.should: -------------------------------------------------------------------------------- 1 | i is 9 -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/expected-run-outputs/Scopes2.samo.run.log.should: -------------------------------------------------------------------------------- 1 | 9 -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/expected-run-outputs/Variables.samo.run.log.should: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/data/simulated-input-run/expected-run-errors/SimplePutin1.samo.run.err.should: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/expected-run-errors/DisplayingOutputs1.samo.run.err.should: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/expected-run-outputs/HelloWorld.samo.run.log.should: -------------------------------------------------------------------------------- 1 | Hello world! -------------------------------------------------------------------------------- /samosac_java.iml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/expected-run-outputs/Scopes3.samo.run.log.should: -------------------------------------------------------------------------------- 1 | 4 2 | 8 3 | 9 4 | -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/expected-run-outputs/While1.samo.run.log.should: -------------------------------------------------------------------------------- 1 | 3 2 | 4 3 | 5 4 | 6 5 | -------------------------------------------------------------------------------- /docs/images/samosa-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/souris-dev/samosac-jvm/HEAD/docs/images/samosa-logo.png -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/expected-run-outputs/Builtins1.samo.run.log.should: -------------------------------------------------------------------------------- 1 | Hello! This is the number 10 -------------------------------------------------------------------------------- /src/test/data/simulated-input-run/expected-run-outputs/SimplePutin1.samo.run.log.should: -------------------------------------------------------------------------------- 1 | 2 | Hello 3 | Bye -------------------------------------------------------------------------------- /docs/images/samosa-logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/souris-dev/samosac-jvm/HEAD/docs/images/samosa-logo@2x.png -------------------------------------------------------------------------------- /docs/images/samosa-logo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/souris-dev/samosac-jvm/HEAD/docs/images/samosa-logo@3x.png -------------------------------------------------------------------------------- /docs/images/samosa-logo@4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/souris-dev/samosac-jvm/HEAD/docs/images/samosa-logo@4.png -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/test-programs/HelloWorld.samo: -------------------------------------------------------------------------------- 1 | 2 | ("Hello world!") -> putout. 3 | -------------------------------------------------------------------------------- /docs/images/samosa-logo@0-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/souris-dev/samosac-jvm/HEAD/docs/images/samosa-logo@0-5.png -------------------------------------------------------------------------------- /docs/images/samosa-logo@0-75.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/souris-dev/samosac-jvm/HEAD/docs/images/samosa-logo@0-75.png -------------------------------------------------------------------------------- /docs/images/samosa-logo@1-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/souris-dev/samosac-jvm/HEAD/docs/images/samosa-logo@1-5.png -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/expected-run-outputs/DisplayingOutputs1.samo.run.log.should: -------------------------------------------------------------------------------- 1 | Hello 2 | 5 3 | true 4 | -------------------------------------------------------------------------------- /docs/images/samosa-lang-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/souris-dev/samosac-jvm/HEAD/docs/images/samosa-lang-banner.png -------------------------------------------------------------------------------- /docs/images/samosa-logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/souris-dev/samosac-jvm/HEAD/docs/images/samosa-logo-small.png -------------------------------------------------------------------------------- /src/test/data/negative-compile/expected-compile-errors/Scopes1.samo.compile.err.should: -------------------------------------------------------------------------------- 1 | [Error, Line 10] Unknown identifier j. 2 | -------------------------------------------------------------------------------- /src/test/data/negative-compile/expected-compile-errors/VariablesNoIdentifier1.samo.compile.err.should: -------------------------------------------------------------------------------- 1 | [Error, Line 17] Unknown identifier unknownId. -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/symbol/SymbolUtils.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.symbol 2 | 3 | class SymbolUtils { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/expected-run-outputs/Functions1.samo.run.log.should: -------------------------------------------------------------------------------- 1 | Hello from function1! 2 | Hello human from function3! 3 | 10 4 | -------------------------------------------------------------------------------- /src/test/data/simulated-input-run/test-programs/SimplePutin1.samo: -------------------------------------------------------------------------------- 1 | 2 | 3 | ("") -> putout. 4 | (() -> putinString) -> putout. 5 | ("Bye") -> putout. 6 | 7 | -------------------------------------------------------------------------------- /src/main/resources/sampletwo.samo: -------------------------------------------------------------------------------- 1 | 2 | bro, i: int = 4. 3 | 4 | let sample() { 5 | bro, i: int = 9. 6 | (i) -> putout. 7 | } 8 | 9 | () -> sample. 10 | 11 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/java/com/sachett/samosa/samosac/codegen/compoundstmt/ControlNodeCodegenType.java: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.codegen.compoundstmt; 2 | 3 | public enum ControlNodeCodegenType { 4 | IF, WHILE 5 | } 6 | -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/test-programs/Scopes2.samo: -------------------------------------------------------------------------------- 1 | 2 | bro, i: int = 4. 3 | 4 | let sample() { 5 | bro, i: int = 9. 6 | (i) -> putout. 7 | } 8 | 9 | () -> sample. 10 | 11 | -------------------------------------------------------------------------------- /src/test/data/negative-compile/test-programs/Scopes1.samo: -------------------------------------------------------------------------------- 1 | 2 | 3 | bro, i: int = 3. 4 | 5 | if (i > 0) { 6 | (i) -> putout. 7 | bro, j: int = i + 2. 8 | } 9 | 10 | (j) -> putout. 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/com/sachett/samosa/samosac/codegen/CodeGenerator.java: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.codegen; 2 | 3 | import com.sachett.samosa.parser.SamosaBaseVisitor; 4 | 5 | public abstract class CodeGenerator extends SamosaBaseVisitor {} 6 | -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/test-programs/DisplayingOutputs1.samo: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* Displaying values */ 4 | 5 | /* You can pass any type to putout. */ 6 | ("Hello") -> putout. 7 | (5) -> putout. 8 | (true) -> putout. 9 | 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/staticchecker/evaluators/IStaticExprEvaluator.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.staticchecker.evaluators 2 | 3 | interface IStaticExprEvaluator { 4 | fun checkStaticEvaluable(): T 5 | fun evaluate(): T 6 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/staticchecker/analyzers/blocks/ControlBlockType.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.staticchecker.analyzers.blocks 2 | 3 | enum class ControlBlockType { 4 | IF, 5 | ELSEIF, 6 | ELSE, 7 | WHILE, 8 | FUNCTIONROOT 9 | } -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/test-programs/While1.samo: -------------------------------------------------------------------------------- 1 | 2 | bro, i: int = 3. 3 | 4 | while (i > 0) { 5 | (i) -> putout. 6 | i = i + 1. 7 | bro, j: int = i + 2. 8 | 9 | if (j == 9) { 10 | yamete_kudasai. 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/test-programs/Scopes3.samo: -------------------------------------------------------------------------------- 1 | 2 | 3 | bro, i: int = 9. 4 | 5 | let sample() { 6 | bro, i: int = 8. 7 | if (i > 7 and i < 9) { 8 | bro, i = 4. 9 | (i) -> putout. 10 | } 11 | (i) -> putout. 12 | } 13 | 14 | () -> sample. 15 | (i) -> putout. 16 | 17 | -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/test-programs/Builtins1.samo: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* Convert string to integer. */ 4 | bro, i: int = ("5") -> stoi. 5 | 6 | /* Convert integer to a string. */ 7 | bro, str: string = (5) -> itos. 8 | 9 | /* You can do something like this: */ 10 | ("Hello! This is the number " + (10) -> itos) -> putout. 11 | 12 | -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/test-programs/Ifs1.samo: -------------------------------------------------------------------------------- 1 | 2 | bro, i = 9. 3 | 4 | if (i == 9) { 5 | ("i is 9") -> putout. 6 | } 7 | else if (i == 10) { 8 | ("i is 10") -> putout. 9 | } 10 | else if (i == 11) { 11 | ("i is 11") -> putout. 12 | } 13 | else { 14 | ("I dunno, I just like samosa.") -> putout. 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/main/java/com/sachett/samosa/samosac/codegen/compoundstmt/IControlNodeCodegen.java: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.codegen.compoundstmt; 2 | 3 | /** 4 | * Interface for Codegens of compound statements that serve as Control Nodes, like if, while, etc. 5 | */ 6 | public interface IControlNodeCodegen { 7 | public ControlNodeCodegenType getControlNodeCodegenType(); 8 | } 9 | -------------------------------------------------------------------------------- /samosac.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/staticchecker/analyzers/blocks/IFunctionInnerBlock.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.staticchecker.analyzers.blocks 2 | 3 | import com.sachett.samosa.samosac.symbol.FunctionSymbol 4 | 5 | interface IFunctionInnerBlock { 6 | var doesReturnProperly: Boolean 7 | val parentFnSymbol: FunctionSymbol 8 | val children: ArrayList 9 | val parent: IFunctionInnerBlock? 10 | } -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/test-programs/Variables.samo: -------------------------------------------------------------------------------- 1 | 2 | 3 | bro, i: int = 0. 4 | bro, str: string = "hello!". 5 | bro, aBoolVal: boolie = true. 6 | 7 | /* Variables can also just be declared. */ 8 | bro, j: int. 9 | bro, str2: string. 10 | bro, boolieVal: boolie. 11 | 12 | /* Types will be inferred for these: */ 13 | bro, iInferred = 0. 14 | bro, strInferred = "string". 15 | bro, aBoolValInferred = true. 16 | 17 | -------------------------------------------------------------------------------- /src/test/data/negative-compile/test-programs/VariablesNoIdentifier1.samo: -------------------------------------------------------------------------------- 1 | 2 | 3 | bro, i: int = 0. 4 | bro, str: string = "hello!". 5 | bro, aBoolVal: boolie = true. 6 | 7 | /* Variables can also just be declared. */ 8 | bro, j: int. 9 | bro, str2: string. 10 | bro, boolieVal: boolie. 11 | 12 | /* Types will be inferred for these: */ 13 | bro, iInferred = 0. 14 | bro, strInferred = "string". 15 | bro, aBoolValInferred = true. 16 | 17 | unknownId = !!aBoolValInferred. 18 | 19 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Compiler___no_compile.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | -------------------------------------------------------------------------------- /src/main/java/com/sachett/samosa/samosac/codegen/utils/delegation/CodegenDelegatedMethod.java: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.codegen.utils.delegation; 2 | 3 | public enum CodegenDelegatedMethod { 4 | NORMAL_DECLASSIGN, BOOLEAN_DECLASSIGN, DECL, TYPEINF_DECLASSIGN, TYPEINF_BOOLEAN_DECLASSIGN, 5 | EXPR_ASSIGN, BOOLEAN_EXPR_ASSIGN, BLOCK, FUNCTIONCALL_NOARGS, FUNCTIONCALL_WITHARGS, 6 | WHILE, BREAK, CONTINUE, IF, RETURN_BOOL, RETURN_WITHEXPR, RETURN_NOEXPR, IMPLICIT_RET_FUNCDEF, EXPLICIT_RET_FUNCDEF 7 | } 8 | -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/test-programs/Expressions1.samo: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* String concatenation: */ 4 | bro, str = "string" + "literal". 5 | 6 | /* An integer value will be stored in anIntVal. */ 7 | bro, anIntVal = 3 + 4 / 4. 8 | 9 | /* Boolean expressions: */ 10 | bro, aBoolVal = true or false. 11 | bro, anotherBoolVal = anIntVal > 10. 12 | bro, someBoolVal = anIntVal == 10 and anotherBoolVal. 13 | bro, boolVal = anIntVal != 100. 14 | 15 | aBoolVal = someBoolVal and boolVal. 16 | anIntVal = 5. 17 | str = "bye!". 18 | 19 | -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/symbol/symboltable/SymbolTableRecordEntry.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.symbol.symboltable 2 | 3 | import com.sachett.samosa.samosac.symbol.ISymbol 4 | 5 | data class SymbolTableRecordEntry( 6 | var prevScopeTable: SymbolTableRecordEntry?, 7 | val table: MutableMap = mutableMapOf(), 8 | var prevScopeIndex: Int, 9 | 10 | // TODO: remove scopeIndex because it is redundant 11 | var scopeIndex: Int, 12 | var recordEntryCoordinates: Pair? = null 13 | ) 14 | -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/symbol/SymbolType.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.symbol 2 | 3 | enum class SymbolType( 4 | val asString: String, 5 | val canBeUsedWithRelOp: Boolean, 6 | val canBeUsedWithCompOp: Boolean, 7 | val defaultValue: Any? = null 8 | ) { 9 | FUNCTION("function", false, false), 10 | INT("int", true, true, 0xDEAD), 11 | STRING("string", false, true, "lawl"), 12 | BOOL("boolie", false, true, true), 13 | VOID("void", false, false), 14 | UNSUPPORTED("thing", false, false, null) 15 | } -------------------------------------------------------------------------------- /.run/Compiler - compile.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/staticchecker/analyzers/blocks/ControlNode.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.staticchecker.analyzers.blocks 2 | 3 | import com.sachett.samosa.parser.SamosaBaseListener 4 | import com.sachett.samosa.parser.SamosaParser 5 | import com.sachett.samosa.samosac.symbol.FunctionSymbol 6 | 7 | abstract class ControlNode( 8 | override val parentFnSymbol: FunctionSymbol, 9 | override val parent: IFunctionInnerBlock, 10 | ) : 11 | IFunctionInnerBlock, SamosaBaseListener() { 12 | override val children: ArrayList = arrayListOf() 13 | } -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/probables/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Probable Statements 4 | nav_order: 3 5 | has_children: true 6 | --- 7 | 8 | # Probable Statements 9 | {: .no_toc } 10 | Samosa includes a set of features to assist in seamlessly including randomization in your programs. 11 | You can execute (or not execute) a statement based on a given probability at runtime. You can, alternatively, specify an alternate statement to execute if a statement is not executed because of the probability factor. 12 | 13 | **Note: Variable declarations, function definitions, and return statements cannot be probabilistically executed as of now.** 14 | -------------------------------------------------------------------------------- /.idea/samosac_java.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/staticchecker/analyzers/blocks/StrayBlock.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.staticchecker.analyzers.blocks 2 | 3 | import com.sachett.samosa.samosac.symbol.FunctionSymbol 4 | 5 | /** 6 | * Represents a series of statements (without curly braces around them). 7 | * This is the leaf node type in the function path tree and does not contain any children. 8 | */ 9 | data class StrayBlock( 10 | override val parentFnSymbol: FunctionSymbol, 11 | override var doesReturnProperly: Boolean, 12 | override val children: ArrayList = arrayListOf(), 13 | override val parent: IFunctionInnerBlock? 14 | ) : IFunctionInnerBlock -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/symbol/BoolSymbol.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.symbol 2 | 3 | class BoolSymbol( 4 | override val name: String, 5 | override val firstAppearedLine: Int, 6 | override val isInferredType: Boolean = false, 7 | var value: Boolean = SymbolType.BOOL.defaultValue as Boolean, 8 | override var isInitialValueCalculated: Boolean, 9 | override var initializeExpressionPresent: Boolean, 10 | override var symbolCoordinates: Pair? = null 11 | ) : ISymbol { 12 | override val symbolType: SymbolType = SymbolType.BOOL 13 | 14 | override fun isSymbolType(symbolType: SymbolType): Boolean = symbolType == SymbolType.BOOL 15 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/symbol/StringSymbol.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.symbol 2 | 3 | class StringSymbol( 4 | override val name: String, 5 | override val firstAppearedLine: Int, 6 | override val isInferredType: Boolean = false, 7 | var value: String = SymbolType.STRING.defaultValue as String, 8 | override var isInitialValueCalculated: Boolean, 9 | override var initializeExpressionPresent: Boolean, 10 | override var symbolCoordinates: Pair? = null 11 | ) : ISymbol { 12 | override val symbolType: SymbolType = SymbolType.STRING 13 | 14 | override fun isSymbolType(symbolType: SymbolType): Boolean = symbolType == SymbolType.STRING 15 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/symbol/IntSymbol.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.symbol 2 | 3 | class IntSymbol( 4 | override val name: String, 5 | override val firstAppearedLine: Int, 6 | override val isInferredType: Boolean = false, 7 | var value: Int = SymbolType.INT.defaultValue as Int, 8 | override var isInitialValueCalculated: Boolean, 9 | override var initializeExpressionPresent: Boolean, 10 | override var symbolCoordinates: Pair? = null 11 | ) : ISymbol { 12 | override val symbolType: SymbolType = SymbolType.INT 13 | 14 | override fun isSymbolType(symbolType: SymbolType): Boolean { 15 | return symbolType == SymbolType.INT 16 | } 17 | } -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 12 | 13 | 14 | 16 | 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | out/ 3 | src/test/positive-compile-run/out 4 | src/test/positive-compile-run/test-programs-comp-errors 5 | src/test/positive-compile-run/test-programs-comp-outputs 6 | src/test/positive-compile-run/test-programs-run-outputs 7 | src/test/positive-compile-run/test-programs-run-errors 8 | 9 | src/test/simulated-input-run/out 10 | src/test/simulated-input-run/test-programs-comp-errors 11 | src/test/simulated-input-run/test-programs-comp-outputs 12 | src/test/simulated-input-run/test-programs-run-outputs 13 | src/test/simulated-input-run/test-programs-run-errors 14 | 15 | src/test/negative-compile/test-programs-comp-errors 16 | src/test/negative-compile/test-programs-comp-outputs 17 | src/test/negative-compile/out 18 | src/test/coverage 19 | 20 | .exe -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Set up JDK 11 20 | uses: actions/setup-java@v3 21 | with: 22 | java-version: '11' 23 | distribution: 'temurin' 24 | cache: maven 25 | - name: Build with Maven 26 | run: mvn -B package --file pom.xml 27 | -------------------------------------------------------------------------------- /src/test/data/positive-compile-run/test-programs/Functions1.samo: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* Defining functions */ 4 | 5 | /* Specifying void is not necessary below: */ 6 | let function1(): void { 7 | ("Hello from function1!") -> putout. 8 | } 9 | 10 | /* This function takes an int as an argument and returns an int */ 11 | let function2(var1: int): int { 12 | return var1 + 3. 13 | } 14 | 15 | /* This function returns void (nothing) */ 16 | let function3(var1: int, name: string) { 17 | ("Hello " + name + " from function3!") -> putout. 18 | (var1) -> putout. 19 | } 20 | 21 | /* Calling functions */ 22 | /* Note that a function must be defined before it is called. */ 23 | 24 | () -> function1. 25 | (10, "human") -> function3. 26 | 27 | bro, m = 7. 28 | bro, i: int = 3 + (5 + m) -> function2. 29 | 30 | -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/symbol/ISymbol.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.symbol 2 | 3 | interface ISymbol { 4 | val name: String 5 | val symbolType: SymbolType 6 | val firstAppearedLine: Int 7 | val isInferredType: Boolean // was type inferred for this symbol? 8 | var initializeExpressionPresent: Boolean // was the value 9 | var isInitialValueCalculated: Boolean 10 | var symbolCoordinates: Pair? // coordinates of the symbol in the symbol table 11 | fun isSymbolType(symbolType: SymbolType): Boolean 12 | 13 | /** 14 | * Returns the symbol name augmented with its symbol table coordinates. 15 | */ 16 | fun getAugmentedName(): String { 17 | return name + "__" + symbolCoordinates?.first + "_" + symbolCoordinates?.second 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/symbol/FunctionSymbol.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.symbol 2 | 3 | class FunctionSymbol( 4 | override val name: String, 5 | override val firstAppearedLine: Int, 6 | val paramList: ArrayList, 7 | val returnType: SymbolType = SymbolType.VOID, 8 | override val isInferredType: Boolean = false, 9 | override var isInitialValueCalculated: Boolean = true, 10 | override var initializeExpressionPresent: Boolean = true, 11 | override var symbolCoordinates: Pair? = null 12 | ) : ISymbol { 13 | override val symbolType: SymbolType = SymbolType.FUNCTION 14 | 15 | companion object { 16 | val allowedReturnTypes = 17 | listOf(SymbolType.INT, SymbolType.BOOL, SymbolType.STRING, SymbolType.VOID) 18 | } 19 | 20 | override fun isSymbolType(symbolType: SymbolType): Boolean = symbolType == SymbolType.FUNCTION 21 | } -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/logging/LoggingUtils.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.logging 2 | 3 | import kotlin.system.exitProcess 4 | 5 | enum class Severity { 6 | WARNING, ERROR, FATAL 7 | } 8 | 9 | /** 10 | * Fatal error. 11 | */ 12 | fun err(message: String): Nothing { 13 | System.err.println(message) 14 | exitProcess(-1) 15 | } 16 | 17 | fun fmtfatalerr(message: String, lineNumber: Int): Nothing { 18 | System.err.println("[Error, Line $lineNumber] $message") 19 | exitProcess(-1) 20 | } 21 | 22 | fun fmterror(message: String, lineNumber: Int, severity: Severity = Severity.FATAL) { 23 | when (severity) { 24 | Severity.WARNING -> { 25 | println("[Warning, Line ${lineNumber}] $message") 26 | } 27 | Severity.ERROR -> { 28 | System.err.println("[Error, Line ${lineNumber}] $message") 29 | } 30 | Severity.FATAL -> { 31 | System.err.println("[Error, Line ${lineNumber}] $message") 32 | exitProcess(-1) 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /.idea/runConfigurations/samosac__compile_.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /.idea/runConfigurations/samosac__package_.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /.idea/runConfigurations/samosac__generate_sources_.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /.idea/runConfigurations/samosac__compile___debug_.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /.idea/runConfigurations/samosac__native_package_graal_.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | schedule: 9 | - cron: "19 1 * * 5" 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | permissions: 16 | actions: read 17 | contents: read 18 | security-events: write 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | language: [ java ] 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v3 28 | 29 | - name: Setup Java 30 | uses: actions/setup-java@v3 31 | with: 32 | distribution: temurin 33 | java-version: 11 34 | 35 | - name: Initialize CodeQL 36 | uses: github/codeql-action/init@v2 37 | with: 38 | languages: ${{ matrix.language }} 39 | queries: +security-and-quality 40 | 41 | - name: Autobuild 42 | uses: github/codeql-action/autobuild@v2 43 | 44 | - name: Perform CodeQL Analysis 45 | uses: github/codeql-action/analyze@v2 46 | with: 47 | category: "/language:${{ matrix.language }}" 48 | -------------------------------------------------------------------------------- /src/main/resources/sample_nocomments.samo: -------------------------------------------------------------------------------- 1 | 2 | 3 | needs { 4 | java::util:: { 5 | Scanner, 6 | ArrayList 7 | }, 8 | java::lang::Parser, 9 | } 10 | 11 | bro, initCounter: int = 10 / 2. 12 | bro, nextCounter: int = initCounter + 3. 13 | bro, stringVar: string = "hi". 14 | bro, ball: string = "heh" + stringVar. 15 | 16 | bro, someBoolVal: boolie = false || true. 17 | bro, anotherBoolVal: boolie = someBoolVal and false || (true strictor someBoolVal). 18 | 19 | let main(var1: int, var2: string): int { 20 | bro, sum: int = (1 + 3) * 6. 21 | 22 | /* Loop */ 23 | while ((var1 < 4) and (var1 > 10 + 4)) { 24 | sum = sum + 1. 25 | } 26 | 27 | if (var2 == "lol") { 28 | sum = 0. 29 | 30 | if (var1 > 3) { 31 | sum = 3 + 4. 32 | return sum. 33 | } 34 | else if (var1 < 2) { 35 | sum = 4 + 0. 36 | } 37 | else { 38 | return sum. 39 | } 40 | } 41 | else if (var2 == "big lol") { 42 | sum = 1. 43 | return 5. 44 | } 45 | else { 46 | sum = 10. 47 | } 48 | 49 | return sum. 50 | } 51 | 52 | bro, res: int = (initCounter + 11 + ((initCounter, ball) -> main), ball) -> main. 53 | (res, ball) -> main. 54 | 55 | -------------------------------------------------------------------------------- /.idea/$PRODUCT_WORKSPACE_FILE$: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 12 19 | 20 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /docs/builtins/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Builtin Functions 4 | nav_order: 4 5 | --- 6 | 7 | # Builtin Functions 8 | {: .no_toc } 9 | 10 | Samosa has a few builtin functions (more will be added soon, in addition to a small standard library). Some builtin functions have overloads. 11 | 12 | ## Table of contents 13 | {: .no_toc .text-delta } 14 | 15 | 1. TOC 16 | {:toc} 17 | 18 | ### `putout(expression)` 19 | 20 | This function takes a single argument and prints it to stdout, and prints a newline after it. It returns nothing. 21 | The argument can be a `string`, `int` or a `boolie` (three overloads). 22 | Example: 23 | 24 | ``` 25 | 26 | 27 | bro, i: int = 0. 28 | bro, str: string = "hello ". 29 | bro, boolVal: boolie = "boolieVal". 30 | 31 | (i) -> putout. 32 | (str) -> putout. 33 | (boolVal) -> putout. 34 | 35 | 36 | ``` 37 | 38 | ### `putinInt(): int` 39 | 40 | Takes in an `int` as user input (from stdin). Example: 41 | 42 | ``` 43 | 44 | bro, i = () -> putinInt. 45 | 46 | ``` 47 | 48 | ### `putinBoolie(): boolie` 49 | 50 | Similar to `putinInt` but inputs a boolean value. 51 | 52 | ### `putinString(): string` 53 | 54 | Similar to `putinInt` but inputs a string value. 55 | 56 | ### `stoi(stringexpr): int` 57 | 58 | Converts a `string` to an `int`. Takes a `string` as argument. Will throw an exception if the number is of the wrong format. 59 | 60 | ### `itos(intexpr): string` 61 | 62 | Converts an `int` to a `string`. Takes an `int` as argument. 63 | 64 | ### `exit(intexpr)` 65 | 66 | Exits and stops the program. Takes an integer argument as an exit code. 67 | 68 | _This section will be updated as new builtin functions are added._ 69 | -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/staticchecker/analyzers/blocks/WhileControlNode.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.staticchecker.analyzers.blocks 2 | 3 | import com.sachett.samosa.parser.SamosaParser 4 | import com.sachett.samosa.samosac.symbol.FunctionSymbol 5 | 6 | class WhileControlNode( 7 | override val parentFnSymbol: FunctionSymbol, 8 | override val parent: IFunctionInnerBlock, 9 | whileCtx: SamosaParser.WhileStmtContext 10 | ) : 11 | ControlNode(parentFnSymbol, parent) { 12 | private var doesReturnComputed = false 13 | override val children: ArrayList = arrayListOf() 14 | 15 | init { 16 | // Partially build the WhileControlNode using the whileCtx. 17 | // (add a WhileControlBlock and a stray block to it) 18 | 19 | // The ControlBlock initializes and adds a stray block to itself 20 | val whileControlBlock = ControlBlock(parentFnSymbol, this, ControlBlockType.WHILE) 21 | children.add(whileControlBlock) 22 | } 23 | 24 | /** 25 | * For an if-control-node, all the children blocks must return a value for the node to return a value. 26 | */ 27 | override var doesReturnProperly: Boolean = false 28 | get() { 29 | if (doesReturnComputed) { 30 | return field 31 | } 32 | 33 | var returns = true 34 | 35 | for (child in children) { 36 | returns = returns && child.doesReturnProperly 37 | } 38 | 39 | field = returns 40 | doesReturnComputed = true 41 | return field 42 | } 43 | set(value) { 44 | doesReturnComputed = true 45 | field = value 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/staticchecker/evaluators/IntExpressionEvaluator.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.staticchecker.evaluators 2 | 3 | import com.sachett.samosa.parser.SamosaBaseVisitor 4 | import com.sachett.samosa.parser.SamosaParser 5 | import com.sachett.samosa.samosac.symbol.SymbolType 6 | 7 | import net.objecthunter.exp4j.ExpressionBuilder 8 | 9 | class IntExpressionEvaluator(private var exprContext: SamosaParser.ExprContext): SamosaBaseVisitor() { 10 | private var isExprStaticEvaluable = true 11 | private var isExprStaticEvaluableCalculated = false 12 | /** 13 | * Checks if the expression can be evaluated at compile time. 14 | */ 15 | fun checkStaticEvaluable(): Boolean { 16 | if (isExprStaticEvaluableCalculated) { 17 | return isExprStaticEvaluable 18 | } 19 | 20 | visit(exprContext) 21 | return isExprStaticEvaluable 22 | } 23 | 24 | fun setExprContext(exprContext: SamosaParser.ExprContext) { 25 | this.exprContext = exprContext 26 | isExprStaticEvaluableCalculated = false 27 | isExprStaticEvaluable = true 28 | } 29 | 30 | /** 31 | * Evaluates and returns the integer value. 32 | * Returns default value of the symbol type if not possible to evaluate at compile time. 33 | */ 34 | fun evaluate(): Int { 35 | if (!checkStaticEvaluable()) { 36 | return SymbolType.INT.defaultValue!! as Int 37 | } 38 | 39 | val exp = ExpressionBuilder(exprContext.text).build() 40 | return exp.evaluate().toInt() 41 | } 42 | 43 | override fun visitExprIdentifier(ctx: SamosaParser.ExprIdentifierContext?): Void? { 44 | isExprStaticEvaluable = false 45 | return super.visitExprIdentifier(ctx) 46 | } 47 | 48 | override fun visitExprFunctionCall(ctx: SamosaParser.ExprFunctionCallContext?): Void? { 49 | isExprStaticEvaluable = false 50 | return super.visitExprFunctionCall(ctx) 51 | } 52 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/staticchecker/analyzers/blocks/ControlBlock.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.staticchecker.analyzers.blocks 2 | 3 | import com.sachett.samosa.samosac.symbol.FunctionSymbol 4 | 5 | class ControlBlock( 6 | override val parentFnSymbol: FunctionSymbol, 7 | override val parent: IFunctionInnerBlock?, 8 | val type: ControlBlockType = ControlBlockType.IF 9 | ) : 10 | IFunctionInnerBlock { 11 | private var doesReturnComputed = false 12 | 13 | override val children: ArrayList = arrayListOf() 14 | 15 | init { 16 | // Every ControlBlock has at least one child block that is initially considered an empty block 17 | children.add(StrayBlock(parentFnSymbol, doesReturnProperly = false, parent = this)) 18 | } 19 | 20 | override var doesReturnProperly: Boolean = false 21 | get() { 22 | if (doesReturnComputed) { 23 | return field 24 | } 25 | 26 | calculateReturn() 27 | doesReturnComputed = true 28 | return field 29 | } 30 | set(value) { 31 | doesReturnComputed = true 32 | field = value 33 | } 34 | 35 | private fun calculateReturn() { 36 | if (children.size == 0) { 37 | doesReturnProperly = false 38 | } 39 | 40 | if (children.size == 1) { 41 | doesReturnProperly = children[0].doesReturnProperly 42 | return 43 | } 44 | 45 | // Compute doesReturnProperly for this node recursively by visiting the nodes 46 | var tempDoesReturnProperly = false 47 | for (child in children) { 48 | if (((child is IfControlNode) && !child.hasElseBlock) || (child is WhileControlNode)) { 49 | continue 50 | } 51 | 52 | tempDoesReturnProperly = tempDoesReturnProperly || child.doesReturnProperly 53 | } 54 | 55 | doesReturnProperly = tempDoesReturnProperly 56 | } 57 | } -------------------------------------------------------------------------------- /NewFeatures.md: -------------------------------------------------------------------------------- 1 | # Feature Ideas 2 | 3 | This file lists some feature ideas for the language and the compiler. 4 | 5 | ### Compiler: 6 | 7 | 1. Add custom error handling. (Specifically, add an override of visitErrorNode in FunctionControlPathAnalyzer 8 | and something similar in StaticChecker). 9 | 2. Improve logging format (something like gcc would be good). 10 | 3. Make installation and usage easier. 11 | 12 | ### Upcoming features: 13 | 14 | 1. Probabilistic execution of statements, and then blocks (`idk` operator with probability). 15 | 2. Compilation of all possible pathways. (`idk` operator with no probability). 16 | 17 | ### Features that may be added later: 18 | 19 | 1. An asap operator for functions that will call the function as soon as possible. 20 | This function can be called again later on too of course. 21 | For example: 22 | ``` 23 | let asap doThis() { 24 | // do something here 25 | } 26 | ``` 27 | The above is equivalent to doing this: 28 | 29 | ``` 30 | let doThis() { 31 | // do something here 32 | } 33 | () -> doThis. 34 | ``` 35 | 36 | 37 | 2. Add null check operator. 38 | 3. Add floating point numbers. 39 | 4. Add hexadecimal numbers. 40 | 5. Add a for loop (and other kinds of loops). 41 | 6. Add break and continue statements. 42 | 7. Add the conditional ternary operator. 43 | 8. Add something like a switch statement. 44 | 9. Exception handling? 45 | 10. Lambdas would be so good. 46 | 11. Import statements. Freaking required. 47 | 12. Deferred blocks would be quite cool. Shouldn't be too tough to implement too. 48 | 13. Escaped characters in strings. 49 | 14. Blocks that execute only with a random probability. Like: 50 | ``` 51 | /* 40% chance of executing */ 52 | with chance (40%) -> { 53 | // if it happens, is an int that has the random number generated 54 | } else { 55 | // if it does not happen 56 | } 57 | ``` 58 | 15. What if a function returns a value probabilistically? :-) -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/staticchecker/analyzers/blocks/CodeBlock.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.staticchecker.analyzers.blocks 2 | 3 | import com.sachett.samosa.parser.SamosaBaseListener 4 | import com.sachett.samosa.parser.SamosaParser 5 | import com.sachett.samosa.samosac.staticchecker.ExpressionTypeDetector 6 | import com.sachett.samosa.samosac.symbol.FunctionSymbol 7 | import com.sachett.samosa.samosac.symbol.SymbolType 8 | import com.sachett.samosa.samosac.symbol.symboltable.SymbolTable 9 | import org.antlr.v4.runtime.tree.ParseTreeWalker 10 | 11 | /** 12 | * Represents a code block (a series of statements WITH curly braces around them). 13 | */ 14 | class CodeBlock( 15 | override val parentFnSymbol: FunctionSymbol, 16 | override val parent: IFunctionInnerBlock, 17 | private val symbolTable: SymbolTable, 18 | private val blockCtx: SamosaParser.BlockContext 19 | ) : 20 | IFunctionInnerBlock, SamosaBaseListener() { 21 | private var doesReturnComputed = false 22 | 23 | override val children: ArrayList = arrayListOf() 24 | 25 | override var doesReturnProperly: Boolean = false 26 | get() { 27 | if (doesReturnComputed) { 28 | return field 29 | } 30 | 31 | ParseTreeWalker.DEFAULT.walk(this, blockCtx) 32 | doesReturnComputed = true 33 | return field 34 | } 35 | set(value) { 36 | doesReturnComputed = true 37 | field = value 38 | } 39 | 40 | override fun enterReturnStmtNoExpr(ctx: SamosaParser.ReturnStmtNoExprContext?) { 41 | doesReturnProperly = parentFnSymbol.returnType == SymbolType.VOID 42 | } 43 | 44 | override fun enterReturnStmtWithExpr(ctx: SamosaParser.ReturnStmtWithExprContext?) { 45 | val expressionTypeDetector = ExpressionTypeDetector(symbolTable) 46 | val (homoTypes, expType) = expressionTypeDetector.getType(ctx!!.expr()) 47 | 48 | doesReturnProperly = homoTypes && expType == parentFnSymbol.returnType 49 | } 50 | } -------------------------------------------------------------------------------- /docs/probables/simple_probables.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | nav_order: 1 4 | parent: Probable Statements 5 | --- 6 | 7 | # Simple probable statements 8 | 9 | The following sections explain the basics of how statements can be made to execute probabilistically at runtime in samosa. 10 | 11 | ## Specifying the probability 12 | 13 | The `?[]` at the end of the statement is used to specify the probability of the execution of the statement. 14 | 15 | The `` is an expression (evaluated at runtime) that evaluates to an int between 0 and 100. It can technically have values more than 100 or less than 0 too, but a value of more than 100 is equivalent to 100 and a value less than 0 is equivalent to 0. 16 | 17 | ### Probabilistically execute a single statement 18 | 19 | Using the syntax: `?[]...` at the end of a statement (after the statement end marker, `.`) would specify the chances of executing that statement. 20 | Higher the value yielded by the expression, more the chances of the statement getting executed. 21 | 22 | #### Example 23 | {: .no_toc } 24 | The probability can be a constant: 25 | ``` 26 | 27 | ("This line is printed 35% of times.") -> putout. ?[35]... 28 | 29 | ``` 30 | 31 | or be something that is evaluated at runtime: 32 | 33 | ``` 34 | 35 | bro, prob: int = () -> putinInt. 36 | (prob) -> putout. ?[prob + 5]... 37 | 38 | ``` 39 | 40 | ### Probabilistically execute a statement or an alternative 41 | 42 | You can specify an alternate statement to be executed if a statement does not get executed due to probability factors. 43 | 44 | The syntax for the same is: `. ?[] .` 45 | 46 | #### Example 47 | {: .no_toc } 48 | ``` 49 | 50 | ("This line is printed 35% of times.") -> putout. ?[35] 51 | ("And this line is printed the rest of the time.") -> putout. 52 | 53 | ``` 54 | 55 | ### Probabilistic compound statements 56 | 57 | Samosa also plans to support probabilistic `if` and `while` statements. 58 | 59 | _Probabilistic compound statements are currently under development._ 60 | _There will be other features added in too to assist probabilistic execution (especially conditional probabilities)._ 61 | -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/compiler/Compiler.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.compiler 2 | 3 | import com.sachett.samosa.logging.err 4 | import com.sachett.samosa.parser.SamosaLexer 5 | import com.sachett.samosa.parser.SamosaParser 6 | import com.sachett.samosa.samosac.codegen.ClassFileGenerator 7 | import com.sachett.samosa.samosac.staticchecker.StaticTypesChecker 8 | import com.sachett.samosa.samosac.symbol.symboltable.SymbolTable 9 | import org.antlr.v4.runtime.CharStreams 10 | import org.antlr.v4.runtime.CommonTokenStream 11 | 12 | import kotlinx.coroutines.* 13 | import java.io.File 14 | 15 | fun main(args: Array) { 16 | if (args.isEmpty()) { 17 | err("samosac: No input files!") 18 | } 19 | 20 | var outputDir = File("./out") 21 | for (arg in args) { 22 | if (arg.startsWith("-o")) { 23 | outputDir = File(arg.substring(2)) 24 | } 25 | } 26 | 27 | // parallel generation of class files 28 | runBlocking { 29 | repeat (args.size) { 30 | if (args[it].startsWith("-")) { 31 | return@repeat 32 | } 33 | launch { 34 | val inputStream = CharStreams.fromFileName(args[0]) 35 | val samosaGrammarLexer = SamosaLexer(inputStream) 36 | val commonTokenStream = CommonTokenStream(samosaGrammarLexer) 37 | val samosaParser = SamosaParser(commonTokenStream) 38 | 39 | val programContext = samosaParser.program() 40 | 41 | val symbolTable = SymbolTable() 42 | 43 | println("Visiting declarations...") 44 | val staticTypesChecker = StaticTypesChecker(symbolTable) 45 | staticTypesChecker.visit(programContext) 46 | 47 | println("Beginning class file generation") 48 | 49 | val sourceFile = File(args[it]) 50 | if (!sourceFile.exists()) { 51 | err("samosac: Input source file not found, quitting.") 52 | } 53 | 54 | val classFileGenerator = ClassFileGenerator(programContext, sourceFile, outputDir, symbolTable) 55 | classFileGenerator.generateClass() 56 | classFileGenerator.writeClass() 57 | } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/staticchecker/StringExpressionChecker.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.staticchecker 2 | 3 | import com.sachett.samosa.logging.Severity 4 | import com.sachett.samosa.logging.fmterror 5 | import com.sachett.samosa.parser.SamosaParser 6 | import com.sachett.samosa.samosac.symbol.SymbolType 7 | import com.sachett.samosa.samosac.symbol.symboltable.SymbolTable 8 | 9 | class StringExpressionChecker(symbolTable: SymbolTable) : ExpressionChecker(symbolTable) { 10 | override fun checkExpr(ctx: SamosaParser.ExprContext): Boolean = visit(ctx) 11 | 12 | /* ------------- Visitor methods --------------- */ 13 | 14 | override fun visitExprIdentifier(ctx: SamosaParser.ExprIdentifierContext?): Boolean = 15 | checkIdentifierTypeInExpr(ctx!!, SymbolType.STRING) 16 | 17 | override fun visitExprString(ctx: SamosaParser.ExprStringContext?): Boolean = true 18 | 19 | override fun visitExprPlus(ctx: SamosaParser.ExprPlusContext?): Boolean = true 20 | 21 | override fun visitExprParen(ctx: SamosaParser.ExprParenContext?): Boolean = true 22 | 23 | override fun visitFunctionCallNoArgs(ctx: SamosaParser.FunctionCallNoArgsContext?): Boolean { 24 | val returnSymbolType = FunctionCallExprChecker.getRetTypeOfFunctionCallNoArgs(ctx, symbolTable) 25 | if (returnSymbolType != SymbolType.STRING) { 26 | fmterror( 27 | "Expected return type was ${SymbolType.STRING.asString} but the function call returns " + 28 | "value of type ${returnSymbolType.asString}.", ctx!!.IDENTIFIER().symbol.line, Severity.ERROR 29 | ) 30 | return false 31 | } 32 | 33 | return true 34 | } 35 | 36 | override fun visitFunctionCallWithArgs(ctx: SamosaParser.FunctionCallWithArgsContext?): Boolean { 37 | val returnSymbolType = FunctionCallExprChecker.getRetTypeOfFunctionCallWithArgs(ctx, symbolTable) 38 | if (returnSymbolType != SymbolType.STRING) { 39 | fmterror( 40 | "Expected return type was ${SymbolType.STRING.asString} but the function call returns " + 41 | "value of type ${returnSymbolType.asString}.", ctx!!.IDENTIFIER().symbol.line, Severity.ERROR 42 | ) 43 | return false 44 | } 45 | 46 | return true 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/staticchecker/analyzers/blocks/IfControlNode.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.staticchecker.analyzers.blocks 2 | 3 | import com.sachett.samosa.parser.SamosaParser 4 | import com.sachett.samosa.samosac.symbol.FunctionSymbol 5 | 6 | class IfControlNode( 7 | override val parentFnSymbol: FunctionSymbol, 8 | override val parent: IFunctionInnerBlock, 9 | ifCtx: SamosaParser.IfStmtContext 10 | ) : 11 | ControlNode(parentFnSymbol, parent) { 12 | private var doesReturnComputed = false 13 | override val children: ArrayList = arrayListOf() 14 | var hasElseBlock: Boolean = false 15 | 16 | init { 17 | // Partially build the IfControlNode using the ifCtx. 18 | // This part builds the children of this node (if, else if and else control blocks) 19 | // Note that a stray block is added to each of these control blocks 20 | // by default (in init method of ControlBlock). 21 | 22 | val nIfs = ifCtx.IF().size 23 | val nElses = ifCtx.ELSE().size 24 | 25 | if (nIfs > 0) { 26 | val ifCtrlBlock = ControlBlock(parentFnSymbol, parent = this, type = ControlBlockType.IF) 27 | children.add(ifCtrlBlock) 28 | } 29 | 30 | for (elseIfBlockParse in ifCtx.elseifblocks) { 31 | val elseIfCtrlBlock = ControlBlock(parentFnSymbol, parent = this, type = ControlBlockType.ELSEIF) 32 | children.add(elseIfCtrlBlock) 33 | } 34 | 35 | // Note that number of IF tokens == number of ELSE tokens implies that there's an ELSE block 36 | if (nIfs == nElses) { 37 | hasElseBlock = true 38 | val elseCtrlBlock = ControlBlock(parentFnSymbol, parent = this, type = ControlBlockType.ELSE) 39 | children.add(elseCtrlBlock) 40 | } 41 | } 42 | 43 | /** 44 | * For an if-control-node, all the children blocks must return a value for the node to return a value. 45 | */ 46 | override var doesReturnProperly: Boolean = false 47 | get() { 48 | if (doesReturnComputed) { 49 | return field 50 | } 51 | 52 | var returns = true 53 | 54 | for (child in children) { 55 | returns = returns && child.doesReturnProperly 56 | } 57 | 58 | field = returns 59 | doesReturnComputed = true 60 | return field 61 | } 62 | set(value) { 63 | doesReturnComputed = true 64 | field = value 65 | } 66 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/staticchecker/evaluators/StringExpressionEvaluator.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.staticchecker.evaluators 2 | 3 | import com.sachett.samosa.parser.SamosaBaseVisitor 4 | import com.sachett.samosa.parser.SamosaParser 5 | import com.sachett.samosa.samosac.symbol.SymbolType 6 | 7 | import net.objecthunter.exp4j.ExpressionBuilder 8 | 9 | class StringExpressionEvaluator(private var exprContext: SamosaParser.ExprContext): SamosaBaseVisitor() { 10 | private var isExprStaticEvaluable = true 11 | private var isExprStaticEvaluableCalculated = false 12 | private var evaluationResult: String = "" 13 | /** 14 | * Checks if the expression can be evaluated at compile time. 15 | */ 16 | public fun checkStaticEvaluable(): Boolean { 17 | if (isExprStaticEvaluableCalculated) { 18 | return isExprStaticEvaluable 19 | } 20 | 21 | evaluationResult = visit(exprContext) 22 | return isExprStaticEvaluable 23 | } 24 | 25 | /** 26 | * Evaluates and returns the integer value. 27 | * Returns default value of the symbol type if not possible to evaluate at compile time. 28 | */ 29 | fun evaluate(): String { 30 | if (!checkStaticEvaluable()) { 31 | return SymbolType.STRING.defaultValue!! as String 32 | } 33 | 34 | return evaluationResult 35 | } 36 | 37 | fun setExprContext(exprContext: SamosaParser.ExprContext) { 38 | this.exprContext = exprContext 39 | isExprStaticEvaluableCalculated = false 40 | isExprStaticEvaluable = true 41 | } 42 | 43 | // only for checking purposes 44 | override fun visitExprIdentifier(ctx: SamosaParser.ExprIdentifierContext?): String { 45 | isExprStaticEvaluable = false 46 | return "" 47 | } 48 | 49 | override fun visitExprFunctionCall(ctx: SamosaParser.ExprFunctionCallContext?): String { 50 | isExprStaticEvaluable = false 51 | return "" 52 | } 53 | 54 | // for compile-time evaluation: 55 | override fun visitExprPlus(ctx: SamosaParser.ExprPlusContext?): String { 56 | return visit(ctx!!.expr(0)) + visit(ctx.expr(1)) 57 | } 58 | 59 | override fun visitExprParen(ctx: SamosaParser.ExprParenContext?): String { 60 | return visit(ctx!!.expr()) 61 | } 62 | 63 | override fun visitExprString(ctx: SamosaParser.ExprStringContext?): String? { 64 | val text = ctx!!.text 65 | 66 | if (text.length == 2 && text.equals("\"\"")) { 67 | return "" 68 | } 69 | 70 | return text.substring(1, text.length - 1) 71 | } 72 | } -------------------------------------------------------------------------------- /src/main/java/com/sachett/samosa/samosac/codegen/utils/delegation/CodegenMethodMap.java: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.codegen.utils.delegation; 2 | 3 | import com.sachett.samosa.parser.SamosaParser; 4 | import org.antlr.v4.runtime.tree.ParseTree; 5 | 6 | import java.util.Map; 7 | 8 | import static java.util.Map.entry; 9 | 10 | public class CodegenMethodMap { 11 | private static final Map, CodegenDelegatedMethod> methods = Map.ofEntries( 12 | entry(SamosaParser.NormalDeclAssignStmtContext.class, CodegenDelegatedMethod.NORMAL_DECLASSIGN), 13 | entry(SamosaParser.BooleanDeclAssignStmtContext.class, CodegenDelegatedMethod.BOOLEAN_DECLASSIGN), 14 | entry(SamosaParser.DeclStmtContext.class, CodegenDelegatedMethod.DECL), 15 | entry(SamosaParser.TypeInferredDeclAssignStmtContext.class, CodegenDelegatedMethod.TYPEINF_DECLASSIGN), 16 | entry(SamosaParser.TypeInferredBooleanDeclAssignStmtContext.class, CodegenDelegatedMethod.TYPEINF_BOOLEAN_DECLASSIGN), 17 | entry(SamosaParser.WhileStmtContext.class, CodegenDelegatedMethod.WHILE), 18 | entry(SamosaParser.BreakControlStmtContext.class, CodegenDelegatedMethod.BREAK), 19 | entry(SamosaParser.ContinueControlStmtContext.class, CodegenDelegatedMethod.CONTINUE), 20 | entry(SamosaParser.IfStmtContext.class, CodegenDelegatedMethod.IF), 21 | entry(SamosaParser.ReturnStmtNoExprContext.class, CodegenDelegatedMethod.RETURN_NOEXPR), 22 | entry(SamosaParser.ReturnStmtWithBooleanExprContext.class, CodegenDelegatedMethod.RETURN_BOOL), 23 | entry(SamosaParser.ReturnStmtWithExprContext.class, CodegenDelegatedMethod.RETURN_WITHEXPR), 24 | entry(SamosaParser.ImplicitRetTypeFuncDefContext.class, CodegenDelegatedMethod.IMPLICIT_RET_FUNCDEF), 25 | entry(SamosaParser.ExplicitRetTypeFuncDefContext.class, CodegenDelegatedMethod.EXPLICIT_RET_FUNCDEF), 26 | entry(SamosaParser.ExprAssignContext.class, CodegenDelegatedMethod.EXPR_ASSIGN), 27 | entry(SamosaParser.BooleanExprAssignContext.class, CodegenDelegatedMethod.BOOLEAN_EXPR_ASSIGN), 28 | entry(SamosaParser.BlockContext.class, CodegenDelegatedMethod.BLOCK), 29 | entry(SamosaParser.FunctionCallNoArgsContext.class, CodegenDelegatedMethod.FUNCTIONCALL_NOARGS), 30 | entry(SamosaParser.FunctionCallWithArgsContext.class, CodegenDelegatedMethod.FUNCTIONCALL_WITHARGS) 31 | ); 32 | 33 | public static CodegenDelegatedMethod getMethodFromClass(Class parseTreeClass) { 34 | return methods.get(parseTreeClass); 35 | } 36 | } -------------------------------------------------------------------------------- /src/main/java/com/sachett/samosa/samosac/codegen/expressions/IExprCodegen.java: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.codegen.expressions; 2 | 3 | import com.sachett.samosa.samosac.codegen.ClassFileGenerator; 4 | import com.sachett.samosa.samosac.codegen.function.FunctionGenerationContext; 5 | import com.sachett.samosa.samosac.symbol.ISymbol; 6 | import com.sachett.samosa.samosac.symbol.symboltable.SymbolTable; 7 | import kotlin.Pair; 8 | import org.objectweb.asm.Opcodes; 9 | import org.objectweb.asm.Type; 10 | 11 | public interface IExprCodegen { 12 | void doCodegen(); 13 | 14 | default void doIdentifierCodegen( 15 | String idName, 16 | SymbolTable symbolTable, 17 | Type type, 18 | FunctionGenerationContext functionGenerationContext, 19 | String qualifiedClassName, 20 | int loadInstruction 21 | ) { 22 | Pair lookupInfo = symbolTable.lookupWithNearestScopeValue(idName); 23 | if (lookupInfo.getFirst() == null) { 24 | // lookup failed 25 | return; 26 | } 27 | 28 | if (lookupInfo.getSecond() == 0) { 29 | // we're talking about a global variable 30 | // that should be looked up in the symbol table without the augmented name 31 | // (a static field of the class during generation) 32 | functionGenerationContext.getMv().visitFieldInsn( 33 | Opcodes.GETSTATIC, qualifiedClassName, idName, type.getDescriptor() 34 | ); 35 | } 36 | else if (lookupInfo.getSecond() != 0 && functionGenerationContext 37 | .getParentClassGenerator() 38 | .getStaticVarsAugmentedNames() 39 | .containsKey( 40 | lookupInfo 41 | .getFirst() 42 | .getAugmentedName())) 43 | { 44 | // static variable but stored in symbol table with augmented name 45 | functionGenerationContext.getMv().visitFieldInsn( 46 | Opcodes.GETSTATIC, qualifiedClassName, lookupInfo.getFirst().getAugmentedName(), type.getDescriptor() 47 | ); 48 | } 49 | else { 50 | Integer localVarIndex = functionGenerationContext.getLocalVarIndex(lookupInfo.getFirst().getAugmentedName()); 51 | functionGenerationContext.getMv().visitVarInsn(loadInstruction, localVarIndex); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | # Configurations 2 | title: Samosa 3 | longtitle: Samosa - The Programming Language 4 | author: Souris Ash 5 | email: souris.ash@gmail.com 6 | description: > 7 | A statically-typed general purpose programming language that runs on the JVM. 8 | 9 | version: 1.0 10 | 11 | url: 'https://souris-dev.github.io' 12 | baseurl: '/samosac-jvm' 13 | remote_theme: just-the-docs/just-the-docs 14 | 15 | search_enabled: true 16 | search: 17 | # Split pages into sections that can be searched individually 18 | # Supports 1 - 6, default: 2 19 | heading_level: 2 20 | # Maximum amount of previews per search result 21 | # Default: 3 22 | previews: 2 23 | # Maximum amount of words to display before a matched word in the preview 24 | # Default: 5 25 | preview_words_before: 3 26 | # Maximum amount of words to display after a matched word in the preview 27 | # Default: 10 28 | preview_words_after: 3 29 | # Set the search token separator 30 | # Default: /[\s\-/]+/ 31 | # Example: enable support for hyphenated search words 32 | tokenizer_separator: /[\s/]+/ 33 | # Display the relative url in search results 34 | # Supports true (default) or false 35 | rel_url: true 36 | # Enable or disable the search button that appears in the bottom right corner of every page 37 | # Supports true or false (default) 38 | button: false 39 | 40 | # Enable or disable heading anchors 41 | heading_anchors: true 42 | 43 | # Aux links for the upper right navigation 44 | aux_links: 45 | "Samosa on GitHub": 46 | - "//github.com/souris-dev/samosac-jvm" 47 | 48 | # Makes Aux links open in a new tab. Default is false 49 | aux_links_new_tab: true 50 | 51 | # Sort order for navigation links 52 | # nav_sort: case_insensitive # default, equivalent to nil 53 | nav_sort: case_sensitive # Capital letters sorted before lowercase 54 | 55 | # Footer content 56 | # appears at the bottom of every page's main content 57 | 58 | # Back to top link 59 | back_to_top: true 60 | back_to_top_text: "Back to top" 61 | 62 | footer_content: "Copyright © 2022 Souris Ash. Distributed by an Apache License 2.0." 63 | 64 | # Footer "Edit this page on GitHub" link text 65 | gh_edit_link: true # show or hide edit this page link 66 | gh_edit_link_text: "Edit this page on GitHub" 67 | gh_edit_repository: "https://github.com/souris-dev/samosac-jvm" # the github URL for your repo 68 | gh_edit_branch: "gh-pages" # the branch that your docs is served from 69 | gh_edit_source: docs # the source that your files originate from 70 | gh_edit_view_mode: "tree" # "tree" or "edit" if you want the user to jump into the editor immediately 71 | 72 | # Color scheme currently only supports "dark", "light"/nil (default), or a custom scheme that you define 73 | color_scheme: nil -------------------------------------------------------------------------------- /src/main/resources/sample.samo: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* Imports these classes */ 4 | 5 | needs { 6 | java::util:: { 7 | Scanner, 8 | ArrayList 9 | }, 10 | java::lang::System, 11 | } 12 | 13 | /* This is a comment */ 14 | 15 | /* Variable declarations and definitions: */ 16 | /* note that "bro," or "sis," or the words "def" or "var" can be used for this */ 17 | bro, initCounter: int = 10 / 2. 18 | bro, nextCounter: int = initCounter + 3. 19 | bro, stringVar: string = "hi". 20 | bro, ball: string = "heh" + stringVar. 21 | 22 | bro, someBoolVal: boolie = false || true. 23 | bro, anotherBoolVal: boolie = someBoolVal and false || (true strictor someBoolVal). 24 | bro, somethingA = 3. 25 | bro, somethingB = 2. 26 | bro, relOpValue: boolie = someBoolVal and somethingA > somethingB. 27 | 28 | ball = "myGawd" + stringVar. 29 | relOpValue = nextCounter > nextCounter. 30 | someBoolVal = anotherBoolVal. 31 | 32 | if (someBoolVal) { 33 | ball = "ball1". 34 | } 35 | else if (relOpValue) { 36 | ball = "ball2". 37 | } 38 | else if (somethingA > somethingB) { 39 | ball = "ball3". 40 | } 41 | else { 42 | ball = "myGawd" + "lawl". 43 | } 44 | 45 | while (somethingA < 5) { 46 | if (relOpValue) { 47 | yamete_kudasai. 48 | } 49 | 50 | somethingA = somethingA + 1. 51 | } 52 | 53 | /* Can we use "bhai," or "behen," too :-)) */ 54 | 55 | /* TODO: add support for multiple declarations/definitions 56 | * like: bro, a: int, b: int, c: string = "Hello", d: int = 3. 57 | * and for initializing many items together: 58 | * sis, (a: int, b: int, c: int) = 0, d: int = 3. */ 59 | 60 | /* Function definition */ 61 | let mana (var1: int, var2: string): int { 62 | bro, sum: int = (1 + 3) * 6. 63 | sum = 0. 64 | 65 | /* Loop */ 66 | while ((var1 < 4) and (var1 > 10 + 4)) { 67 | sum = sum + 1. 68 | bro, g: int = 19. 69 | 70 | if (sum > 10) { 71 | yamete_kudasai. 72 | } 73 | 74 | if (sum > 3) { 75 | thanku_next. 76 | } 77 | 78 | sum = sum + 1. 79 | } 80 | 81 | if (var2 == "lol") { 82 | sum = 0. 83 | 84 | if (var1 > 3) { 85 | sum = 3 + 4. 86 | return sum. 87 | } 88 | else if (var1 < 2) { 89 | sum = 4 + 0. 90 | } 91 | else { 92 | return sum. 93 | } 94 | } 95 | else if (var2 == "big lol") { 96 | sum = 1. 97 | return 5. 98 | } 99 | else { 100 | sum = 17. 101 | } 102 | 103 | return sum. 104 | } 105 | 106 | /* Function call expression */ 107 | bro, res: int = (initCounter + 11 + ((initCounter, ball) -> mana), ball) -> mana. 108 | 109 | /* Function call statement */ 110 | (res, ball) -> mana. 111 | 112 | -------------------------------------------------------------------------------- /src/test/data/README.md: -------------------------------------------------------------------------------- 1 | ## Adding tests 2 | 3 | The tests are divided into the following directories: 4 | 5 | 1. *positive-compile-run*: Programs that are supposed to compile successfully and run either successfully or with an error. Should not contain any user input statements. 6 | 2. *negative-compile*: Programs that are not supposed to compile successfully. 7 | 3. *simulated-input-run*: Programs that are supposed to compile successfully and run either successfully or with an error. Can take user input. 8 | 9 | The test classes are named similarly. 10 | 11 | ### Adding a positive compile and run test case 12 | 13 | 1. Write the test program. Put it into *positive-compile-run/test-programs* (ensure that the filename ends with `.samo`). Let us call the filename for this program `` (note that `` ends with *.samo*). 14 | 2. Make a file named as `.run.log.should` in *positive-compile-run/test-programs/expected-run-outputs*. In this file, put the expected output of the program. 15 | 3. Make a file named as `.run.err.should` in *positive-compile-run/test-programs/expected-run-errors*. In this file, put the expected error output of the program. 16 | 17 | In steps 2 and 3, if there is no expected output and/or error, the files can be left blank. (The files still do need to be created.) 18 | 19 | ### Adding a negative compile test case 20 | 21 | 1. Write the test program. Put it into *negative-compile/test-programs* (ensure that the filename ends with `.samo`). Let us call the filename for this program `` (note that `` ends with *.samo*). 22 | 2. Make a file named as `.compile.err.should` in *negative-compile/test-programs/expected-compile-errors*. In this file, put the expected compilation output of the program. 23 | 24 | In step 2, if there is no expected output and/or error, the files can be left blank. (The files still do need to be created.) 25 | 26 | ### Adding a test program that takes user input 27 | 28 | 1. Write the test program. Put it into *simulated-input-run/test-programs* (ensure that the filename ends with `.samo`). Let us call the filename for this program `` (note that `` ends with *.samo*). 29 | 2. Make a file named as `.inputs` in *simulated-input-run/test-programs/expected-run-outputs*. In this file, put the inputs to the program, one input per line. **Do not forget to add an empty newline at the end of the file.** 30 | 3. Make a file named as `.run.log.should` in *simulated-input-run/test-programs/expected-run-outputs*. In this file, put the expected output of the program. 31 | 4. Make a file named as `.run.err.should` in *simulated-input-run/test-programs/expected-run-errors*. In this file, put the expected error output of the program. 32 | 33 | In steps 3 and 4, if there is no expected output and/or error, the files can be left blank. (The files still do need to be created.) 34 | 35 | For step 2, the inputs must be in the order to be fed to the program and each in one line. 36 | -------------------------------------------------------------------------------- /docs/probables/nested_probables.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | nav_order: 2 4 | parent: Probable Statements 5 | --- 6 | 7 | ## Nested probable statements 8 | 9 | Probable statements can be nested. Some examples would help understand how they work. 10 | 11 | ### Single statement, nested probabilities 12 | 13 | In such cases, probabilities are multiplied, starting from **right to left**. 14 | 15 | #### Example 16 | {: .no_toc } 17 | 18 | ``` 19 | 20 | ("Hello world!") -> putout. ?[40]... ?[80]... 21 | 22 | ``` 23 | 24 | In the example above, the probability of execution of the probable statement `("Hello world!") -> putout. ?[40]...` (let's call this event _A_) is 80% (as indicated by `?[80]...` beside it.) 25 | 26 | And further, the probability of execution of the statement `("Hello world!") -> putout.` (let's call this event _B_) is 40% _provided that the probable statement `("Hello world!") -> putout. ?[40]...` itself executes_. 27 | 28 | Hence, the effective probability of `("Hello world!") -> putout.` executing can be expressed as `P(B|A)`. 29 | 30 | One can go on nesting probabilities like this. But **remember, probabilities are nested from _right to left_.** 31 | 32 | Also, as mentioned before, the probabilities specified may not necessarily be constants. They can be expressions that are evaluated to an `int` at compile time. 33 | 34 | ### Multiple statements, nested probabilities 35 | 36 | Just like single probable statements, probable statements with alternatives can also be nested. 37 | 38 | #### Example 39 | {: .no_toc } 40 | 41 | ``` 42 | 43 | 44 | ("nested3a") -> putout. ?[55] ("nested3b") -> putout. ?[70] 45 | ("nested2b") -> putout. ?[60] 46 | ("nested1b") -> putout. 47 | 48 | 49 | ``` 50 | 51 | Again, probabilities are nested from the _right to left_. 52 | 53 | The above example will be easier to understand, if seen this way: 54 | (**the snippet below is not actual code, just a pseudocode to make things easy to understand**) 55 | ``` 56 | Let A signify the statement ("nested1b") -> putout. 57 | 58 | Then, the top level probable statement is: 59 | B ?[60] A 60 | 61 | where B is: 62 | C ?[70] ("nested2b") -> putout. 63 | 64 | where C is: 65 | ("nested3a") -> putout. ?[55] ("nested3b") -> putout. 66 | ``` 67 | 68 | The above should have made things easier to understand :-) 69 | 70 | In case it is still not that clear, here's an attempt at an explanation: 71 | 72 | First, the probability `?[60]` is evaluated. Hence, 40% of the time, `("nested1b") -> putout.` is executed. 73 | The rest 60% of the times: 74 | * The next probability, `?[70]` is evaluated. 30% of times (after the first probability makes this statement run), `("nested2b") -> putout.` gets executed. The rest 70% of the times: 75 | * The next probability, `?[55]` is evaluated. So, 45% of times (after the previous nested probability statement makes this one run), the statement `("nested3b") -> putout.` is executed, while for 55% of times, the statement `("nested3a") -> putout.` gets executed. 76 | 77 | Hopefully that served as a good explanation. 78 | 79 | Mathematically, the probability of `("nested3a") -> putout.` getting executed can be calculated by evaluating `P(exec(C)|exec(B)|~exec(A))` (where `exec(B)` and so on denote the event of that statement executing, and `~exec(A)` denotes the probability of that statement not executing.) -------------------------------------------------------------------------------- /src/main/java/com/sachett/samosa/samosac/codegen/utils/delegation/CodegenDelegatable.java: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.codegen.utils.delegation; 2 | 3 | import com.sachett.samosa.samosac.codegen.CodeGenerator; 4 | import org.antlr.v4.runtime.tree.ParseTree; 5 | import org.antlr.v4.runtime.tree.RuleNode; 6 | 7 | import java.util.HashSet; 8 | 9 | public abstract class CodegenDelegatable extends CodeGenerator { 10 | HashSet delegatedMethods = new HashSet<>(); 11 | CodegenDelegationManager codeGenDelegationManager; 12 | private boolean beingDelegated = false; 13 | private boolean wasBeingDelegated = false; 14 | 15 | public CodegenDelegatable( 16 | HashSet delegatedMethods, 17 | CodegenDelegationManager codeGenDelegationManager 18 | ) { 19 | this.codeGenDelegationManager = codeGenDelegationManager; 20 | this.delegatedMethods = delegatedMethods; 21 | } 22 | 23 | public CodegenDelegatable( 24 | CodegenDelegationManager codeGenDelegationManager 25 | ) { 26 | this.codeGenDelegationManager = codeGenDelegationManager; 27 | } 28 | 29 | public CodegenDelegatable() { 30 | this.codeGenDelegationManager = new CodegenDelegationManager(this); 31 | } 32 | 33 | public void setDelegationManager(CodegenDelegationManager manager) { 34 | this.codeGenDelegationManager = manager; 35 | } 36 | 37 | public CodegenDelegationManager getSharedDelegationManager() { 38 | return codeGenDelegationManager; 39 | } 40 | 41 | // Needs to be public so that delegated CodegenCommons can access the parent's method of startDelegatingTo 42 | public void startDelegatingTo(CodegenDelegatable delegatable) { 43 | wasBeingDelegated = isBeingDelegated(); 44 | setBeingDelegated(false); 45 | delegatable.setBeingDelegated(true); 46 | codeGenDelegationManager.setCurrentDelegated(delegatable); 47 | codeGenDelegationManager.setCurrentDelegator(this); 48 | } 49 | 50 | public void finishDelegating() { 51 | setBeingDelegated(wasBeingDelegated); 52 | codeGenDelegationManager.setCurrentDelegated(null); 53 | codeGenDelegationManager.setCurrentDelegator(this); 54 | } 55 | 56 | public void setBeingDelegated(boolean beingDelegated) { 57 | this.beingDelegated = beingDelegated; 58 | } 59 | 60 | public void undelegateSelf() { 61 | wasBeingDelegated = beingDelegated; 62 | setBeingDelegated(false); 63 | } 64 | 65 | public boolean isBeingDelegated() { 66 | return beingDelegated; 67 | } 68 | 69 | protected void registerDelegatedMethods(HashSet methods) { 70 | this.delegatedMethods.addAll(methods); 71 | } 72 | 73 | public boolean isMethodDelegated(CodegenDelegatedMethod method) { 74 | return delegatedMethods.contains(method); 75 | } 76 | 77 | @Override 78 | public Void visit(ParseTree parseTree) { 79 | System.out.println("Visiting ParseTree \t(type) " + parseTree.getClass().toString() + " \t\t(through) " + this.toString()); 80 | if (isBeingDelegated()) { 81 | return super.visit(parseTree); 82 | } 83 | else { 84 | return codeGenDelegationManager.visit(parseTree); 85 | } 86 | } 87 | 88 | @Override 89 | public Void visitChildren(RuleNode node) { 90 | System.out.println("Visiting RuleNode \t(type) " + node.getClass().toString() + " \t\t(through) " + this.toString()); 91 | if (isBeingDelegated()) { 92 | return super.visitChildren(node); 93 | } 94 | else { 95 | return codeGenDelegationManager.visitChildren(node); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/staticchecker/IntExpressionChecker.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.staticchecker 2 | 3 | import com.sachett.samosa.logging.Severity 4 | import com.sachett.samosa.logging.fmterror 5 | import com.sachett.samosa.parser.SamosaParser 6 | import com.sachett.samosa.samosac.symbol.SymbolType 7 | import com.sachett.samosa.samosac.symbol.symboltable.SymbolTable 8 | 9 | class IntExpressionChecker(symbolTable: SymbolTable) : ExpressionChecker(symbolTable) { 10 | override fun checkExpr(ctx: SamosaParser.ExprContext): Boolean = visit(ctx) 11 | 12 | override fun checkUnaryOp(ctx: T): Boolean { 13 | // use java reflection API 14 | // this would be: visit(ctx?.booleanExpr()) 15 | val ctxClass = ctx!!::class.java 16 | val expr = ctxClass.getDeclaredMethod("expr") 17 | val exprRetObj = expr.invoke(ctx) as SamosaParser.ExprContext 18 | 19 | return visit(exprRetObj) 20 | } 21 | 22 | override fun checkBinaryOp(ctx: T): Boolean { 23 | // use java reflection API 24 | // this would be: visit(ctx?.booleanExpr(0)) && visit(ctx?.booleanExpr(1)) 25 | val ctxClass = ctx!!::class.java 26 | val expr = ctxClass.getDeclaredMethod("expr", Int::class.java) 27 | val exprRetObjLHS = expr.invoke(ctx, 0) as SamosaParser.ExprContext 28 | val exprRetObjRHS = expr.invoke(ctx, 1) as SamosaParser.ExprContext 29 | 30 | return (visit(exprRetObjLHS) && visit(exprRetObjRHS)) 31 | } 32 | 33 | /* --------------- Visitor methods -------------- */ 34 | 35 | override fun visitUnaryMinus(ctx: SamosaParser.UnaryMinusContext?): Boolean = checkUnaryOp(ctx!!) 36 | 37 | override fun visitExprDivide(ctx: SamosaParser.ExprDivideContext?): Boolean = checkBinaryOp(ctx!!) 38 | 39 | override fun visitExprMultiply(ctx: SamosaParser.ExprMultiplyContext?): Boolean = checkBinaryOp(ctx!!) 40 | 41 | override fun visitExprModulo(ctx: SamosaParser.ExprModuloContext?): Boolean = checkBinaryOp(ctx!!) 42 | 43 | override fun visitExprPlus(ctx: SamosaParser.ExprPlusContext?): Boolean = checkBinaryOp(ctx!!) 44 | 45 | override fun visitExprMinus(ctx: SamosaParser.ExprMinusContext?): Boolean = checkBinaryOp(ctx!!) 46 | 47 | override fun visitExprParen(ctx: SamosaParser.ExprParenContext?): Boolean = checkUnaryOp(ctx!!) 48 | 49 | override fun visitExprIdentifier(ctx: SamosaParser.ExprIdentifierContext?): Boolean = 50 | checkIdentifierTypeInExpr(ctx!!, SymbolType.INT) 51 | 52 | override fun visitFunctionCallNoArgs(ctx: SamosaParser.FunctionCallNoArgsContext?): Boolean { 53 | val returnSymbolType = FunctionCallExprChecker.getRetTypeOfFunctionCallNoArgs(ctx, symbolTable) 54 | if (returnSymbolType != SymbolType.INT) { 55 | fmterror( 56 | "Expected return type was ${SymbolType.INT.asString} but the function call returns " + 57 | "value of type ${returnSymbolType.asString}.", ctx!!.IDENTIFIER().symbol.line, Severity.ERROR 58 | ) 59 | return false 60 | } 61 | 62 | return true 63 | } 64 | 65 | override fun visitFunctionCallWithArgs(ctx: SamosaParser.FunctionCallWithArgsContext?): Boolean { 66 | val returnSymbolType = FunctionCallExprChecker.getRetTypeOfFunctionCallWithArgs(ctx, symbolTable) 67 | if (returnSymbolType != SymbolType.INT) { 68 | fmterror( 69 | "Expected return type was ${SymbolType.INT.asString} but the function call returns " + 70 | "value of type ${returnSymbolType.asString}.", ctx!!.IDENTIFIER().symbol.line, Severity.ERROR 71 | ) 72 | return false 73 | } 74 | 75 | return true 76 | } 77 | 78 | override fun visitExprDecint(ctx: SamosaParser.ExprDecintContext?): Boolean = true 79 | } -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | nav_order: 1 4 | title: Getting Started 5 | --- 6 | 7 | 8 | 9 | # Samosa 10 | {: .no_toc} 11 | 12 | [![Total alerts](https://img.shields.io/lgtm/alerts/g/souris-dev/samosac-jvm.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/souris-dev/samosac-jvm/alerts/) [![Language grade: Java](https://img.shields.io/lgtm/grade/java/g/souris-dev/samosac-jvm.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/souris-dev/samosac-jvm/context:java) 13 | 14 | _Welcome, samosa lovers!_ 15 | 16 | **Samosa is a statically-typed general purpose programming language that runs on the JVM.** 17 | 18 | _Note: Samosa, the programming language, is named after an Indian snack called "samosa", and is pronounced as "some-o-saa" (the part "saa" is pronounced like the word "sour", but without the "r")._ 19 | 20 | Samosa is the name of the language, and `samosac` is the name of the compiler. 21 | 22 | [Try it out](http://playground.samosa-lang.org){: .btn .btn-purple } 23 | [View on GitHub](https://github.com/souris-dev/samosac-jvm){: .btn } 24 | 25 | ## Table of contents 26 | {: .no_toc .text-delta } 27 | 28 | 1. TOC 29 | {:toc} 30 | 31 | --- 32 | ## Getting Started 33 | 34 | ### Installation 35 | 36 | _This section will be updated soon with other alternatives for installing samosa._ 37 | 38 | #### Using a pre-built executable jar 39 | 40 | Download the `1.0-SNAPSHOT` (current alpha release) compiler executable jar: 41 | 42 | [Download](https://github.com/souris-dev/samosac-jvm/releases/download/v1.0.0-alpha/samosac-1.0-SNAPSHOT-full.jar){: .btn .btn-purple } 43 | 44 | You really don't need to install anything if you already have JRE (minimum java version 11) installed. Otherwise, you'll have to install that first to use the compiler and to run the programs. 45 | 46 | For other releases, head over to [releases](https://github.com/souris-dev/samosac-jvm/releases). 47 | 48 | You now have the compiler, yay! 49 | 50 | #### Building from source 51 | 52 | **Note: Ensure that you have the following installed (and in your PATH) before using the upcoming commands to build from source:** 53 | 54 | * **git** 55 | * **≥ JDK 11 (the project was developed on JDK 17, but the code is compatible with java version >= 11.)** 56 | * **Apache Maven 3.1 or higher version** 57 | 58 | To download the source and build it using maven, run these in the terminal of your choice: 59 | 60 | ``` 61 | git clone https://github.com/souris-dev/samosac-jvm.git 62 | cd samosac-jvm 63 | mvn compile 64 | ``` 65 | Then, to build the compiler jar, use (from within the project directory): 66 | 67 | ``` 68 | mvn package 69 | ``` 70 | 71 | This will create a `samosac--full.jar` in the `target` folder. This is the compiler jar. 72 | 73 | _Easier installation methods will be provided soon._ 74 | 75 | ### Usage 76 | 77 | **Note: Ensure that you have the JRE (minimum java version 11) installed before starting this section.** 78 | 79 | #### Compiling your program 80 | 81 | Type your samosa program in a file, and name it something (for example samosa.samo). Then use the .jar file of the compiler to compile it **(ensure that you have java in you PATH)**: 82 | 83 | ``` 84 | java -jar samosac--full.jar samosa.samo 85 | ``` 86 | 87 | (Replace `samosac--full.jar` with the relative or absolute path to the compiler jar file, and `samosa.samo` with the relative or absolute path of the file you wrote your program in.) 88 | 89 | _This section will be updated._ 90 | 91 | #### Running the program 92 | 93 | As samosa compiles to JVM bytecode, a `.class` is generated, named as per your filename. So for the above example, a file named `SamosaSamo.class` would be created in the `./out` directory. 94 | 95 | To run it, do this **(ensure that you have java in your PATH)**: 96 | 97 | ``` 98 | cd out 99 | java SamosaSamo 100 | ``` 101 | -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/staticchecker/ExpressionChecker.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.staticchecker 2 | 3 | import com.sachett.samosa.parser.SamosaParser 4 | import com.sachett.samosa.parser.SamosaBaseVisitor 5 | import com.sachett.samosa.samosac.symbol.SymbolType 6 | import com.sachett.samosa.samosac.symbol.symboltable.SymbolTable 7 | 8 | import com.sachett.samosa.logging.err 9 | 10 | abstract class ExpressionChecker(protected val symbolTable: SymbolTable) : SamosaBaseVisitor() { 11 | 12 | /* This is the function that should be called from outside */ 13 | open fun checkExpr(ctx: SamosaParser.ExprContext): Boolean = false 14 | 15 | /* Check if the given unary operations and binary operations are valid in the expression. */ 16 | protected open fun checkUnaryOp(ctx: T) = false 17 | protected open fun checkBinaryOp(ctx: T) = false 18 | 19 | /* Checks if the identifier in the expression is of the expected type */ 20 | protected fun checkIdentifierTypeInExpr( 21 | ctx: T, 22 | expectedType: SymbolType 23 | ): Boolean { 24 | /* Use the java reflection API to get the identifier name and the line number */ 25 | 26 | val ctxClass = ctx::class.java 27 | val identifier = ctxClass.getDeclaredMethod("IDENTIFIER") 28 | val identifierRetObj = identifier.invoke(ctx) 29 | val identifierMtdRetClass = identifierRetObj::class.java 30 | 31 | val getText = identifierMtdRetClass.getDeclaredMethod("getText") 32 | val getSymbol = identifierMtdRetClass.getDeclaredMethod("getSymbol") 33 | 34 | val getSymbolRetObj = getSymbol.invoke(identifierRetObj) 35 | val getSymbolMtdRetClass = getSymbolRetObj::class.java 36 | val getLine = getSymbolMtdRetClass.getMethod("getLine") 37 | 38 | /* Retrieve the identifier name and line number */ 39 | val idName = getText.invoke(identifierRetObj) as String 40 | val lineNumber = getLine.invoke(getSymbolRetObj) as Int 41 | 42 | val symbol = symbolTable.lookup(idName) ?: 43 | err("[Error, Line $lineNumber] Unknown identifier $idName.") 44 | 45 | // if same type (as expected) return true 46 | if (symbol.isSymbolType(expectedType)) { 47 | return true 48 | } 49 | 50 | // else error out 51 | error( 52 | "[Error, Line $lineNumber] The identifier $idName has a type mismatch with the required type in " + 53 | "the expression. The expected type was ${expectedType.asString} but the type found was " + 54 | symbol.symbolType.asString + "." 55 | ) 56 | } 57 | 58 | /* ----------------- Visitor methods -------------------- */ 59 | 60 | override fun visitUnaryMinus(ctx: SamosaParser.UnaryMinusContext?): Boolean = false 61 | override fun visitExprDivide(ctx: SamosaParser.ExprDivideContext?): Boolean = false 62 | override fun visitExprMultiply(ctx: SamosaParser.ExprMultiplyContext?): Boolean = false 63 | override fun visitExprModulo(ctx: SamosaParser.ExprModuloContext?): Boolean = false 64 | override fun visitExprPlus(ctx: SamosaParser.ExprPlusContext?): Boolean = false 65 | override fun visitExprMinus(ctx: SamosaParser.ExprMinusContext?): Boolean = false 66 | override fun visitExprParen(ctx: SamosaParser.ExprParenContext?): Boolean = false 67 | override fun visitExprIdentifier(ctx: SamosaParser.ExprIdentifierContext?): Boolean = false 68 | override fun visitExprDecint(ctx: SamosaParser.ExprDecintContext?): Boolean = false 69 | override fun visitExprString(ctx: SamosaParser.ExprStringContext?): Boolean = false 70 | override fun visitBooleanExprNot(ctx: SamosaParser.BooleanExprNotContext?): Boolean = false 71 | override fun visitBooleanExprOr(ctx: SamosaParser.BooleanExprOrContext?): Boolean = false 72 | override fun visitBooleanExprAnd(ctx: SamosaParser.BooleanExprAndContext?): Boolean = false 73 | override fun visitBooleanExprXor(ctx: SamosaParser.BooleanExprXorContext?): Boolean = false 74 | override fun visitBooleanExprRelOp(ctx: SamosaParser.BooleanExprRelOpContext?): Boolean = false 75 | override fun visitBooleanExprParen(ctx: SamosaParser.BooleanExprParenContext?): Boolean = false 76 | override fun visitBooleanExprIdentifier(ctx: SamosaParser.BooleanExprIdentifierContext?): Boolean = false 77 | override fun visitBooleanTrue(ctx: SamosaParser.BooleanTrueContext?): Boolean = false 78 | override fun visitBooleanFalse(ctx: SamosaParser.BooleanFalseContext?): Boolean = false 79 | override fun visitBooleanFunctionCall(ctx: SamosaParser.BooleanFunctionCallContext?): Boolean = false 80 | override fun visitFunctionCallWithArgs(ctx: SamosaParser.FunctionCallWithArgsContext?): Boolean = false 81 | override fun visitFunctionCallNoArgs(ctx: SamosaParser.FunctionCallNoArgsContext?): Boolean = false 82 | } -------------------------------------------------------------------------------- /src/test/java/com/sachett/samosa/TestNegativeCompile.java: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa; 2 | 3 | import com.sachett.samosa.samosac.compiler.CompilerKt; 4 | import org.apache.commons.io.FileUtils; 5 | import org.junit.jupiter.api.BeforeAll; 6 | import org.junit.jupiter.api.DynamicTest; 7 | import org.junit.jupiter.api.TestFactory; 8 | 9 | import java.io.File; 10 | import java.io.PrintStream; 11 | import java.util.Arrays; 12 | import java.util.stream.Stream; 13 | 14 | import static com.github.stefanbirkner.systemlambda.SystemLambda.catchSystemExit; 15 | import static org.junit.jupiter.api.Assertions.*; 16 | import static org.junit.jupiter.api.Assertions.assertTrue; 17 | 18 | /** 19 | * Tests for programs that are not supposed to compile successfully. 20 | */ 21 | public class TestNegativeCompile { 22 | static final File programsDir = new File("src/test/data/negative-compile/test-programs"); 23 | static final File programsCompOutputDir = new File("src/test/data/negative-compile/test-programs-comp-outputs"); 24 | static final File programsCompErrorDir = new File("src/test/data/negative-compile/test-programs-comp-errors"); 25 | static final File expectedCompErrorDir = new File("src/test/data/negative-compile/expected-compile-errors"); 26 | static final File classFileOutDir = new File("src/test/data/negative-compile/out"); 27 | 28 | @BeforeAll 29 | static void createDirs() { 30 | programsCompOutputDir.mkdirs(); 31 | programsCompErrorDir.mkdirs(); 32 | 33 | classFileOutDir.mkdirs(); 34 | } 35 | 36 | @TestFactory 37 | Stream testCompileErrors() { 38 | // Compile the test cases first 39 | File[] sourceFiles = programsDir.listFiles((dir, name) -> name.toLowerCase().endsWith(".samo")); 40 | 41 | assertNotNull(sourceFiles, "No test source files found!"); 42 | assertTrue(sourceFiles.length > 0, "No test source files found!"); 43 | 44 | return Arrays.stream(sourceFiles).parallel().map((file) -> 45 | DynamicTest.dynamicTest("Test Source File: " + file.getName(), () -> { 46 | // First compile the file 47 | // Store the error and output of the compilation process into designated directories 48 | 49 | if (!System.getProperties().containsKey("nocompile")) { 50 | File compilationOutputFile = new File( 51 | programsCompOutputDir.getPath() + File.separator + file.getName() + ".compile.log" 52 | ); 53 | File compilationErrorFile = new File( 54 | programsCompErrorDir.getPath() + File.separator + file.getName() + ".compile.err" 55 | ); 56 | File expectedCompErrorFile = new File( 57 | expectedCompErrorDir.getPath() + File.separator + file.getName() + ".compile.err.should" 58 | ); 59 | 60 | compilationOutputFile.createNewFile(); 61 | compilationErrorFile.createNewFile(); 62 | 63 | PrintStream prevOut = System.out; 64 | PrintStream prevErr = System.err; 65 | PrintStream redirectedOut = new PrintStream(compilationOutputFile); 66 | PrintStream redirectedErr = new PrintStream(compilationErrorFile); 67 | System.setOut(redirectedOut); 68 | System.setErr(redirectedErr); 69 | 70 | // Do compilation 71 | int exitStatus = catchSystemExit(() -> 72 | CompilerKt.main(new String[]{ file.getAbsolutePath(), "-o" + classFileOutDir.getAbsolutePath() }) 73 | ); 74 | 75 | // Restore streams 76 | System.setErr(prevErr); 77 | System.setOut(prevOut); 78 | assertNotEquals(exitStatus, 0, "Compilation exited with status 0. " + 79 | "\nCompilation was expected to have failed with status code other than 0."); 80 | 81 | // Check if the compilation error is as specified 82 | assertTrue( 83 | FileUtils.contentEqualsIgnoreEOL(compilationErrorFile, expectedCompErrorFile, null), 84 | "Compilation failed with unexpected error for test source file: \n\t" + file.getAbsolutePath() 85 | + "." + "\nExpected compilation error can be found in: " + expectedCompErrorFile.getAbsolutePath() 86 | + "\nActual error received during compilation is written in: " + compilationErrorFile.getAbsolutePath() 87 | ); 88 | } 89 | }) 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/com/sachett/samosa/samosac/codegen/expressions/IntExprCodegen.java: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.codegen.expressions; 2 | 3 | import com.sachett.samosa.parser.SamosaBaseVisitor; 4 | import com.sachett.samosa.parser.SamosaParser; 5 | import com.sachett.samosa.samosac.codegen.function.FunctionCallCodegen; 6 | import com.sachett.samosa.samosac.codegen.function.FunctionGenerationContext; 7 | import com.sachett.samosa.samosac.symbol.symboltable.SymbolTable; 8 | import org.objectweb.asm.Opcodes; 9 | import org.objectweb.asm.Type; 10 | 11 | public class IntExprCodegen extends SamosaBaseVisitor implements IExprCodegen { 12 | private SamosaParser.ExprContext exprContext; 13 | private final FunctionGenerationContext functionGenerationContext; 14 | private final SymbolTable symbolTable; 15 | private final String qualifiedClassName; 16 | private final String className; 17 | private final String packageName; 18 | 19 | public IntExprCodegen( 20 | SamosaParser.ExprContext exprContext, 21 | SymbolTable symbolTable, 22 | FunctionGenerationContext functionGenerationContext, 23 | String className, 24 | String packageName 25 | ) { 26 | this.exprContext = exprContext; 27 | this.functionGenerationContext = functionGenerationContext; 28 | this.symbolTable = symbolTable; 29 | this.qualifiedClassName = packageName.replace(".", "/") + className; 30 | this.className = className; 31 | this.packageName = packageName; 32 | } 33 | 34 | @Override 35 | public void doCodegen() { 36 | visit(exprContext); 37 | } 38 | 39 | public void setExprContext(SamosaParser.ExprContext exprContext) { 40 | this.exprContext = exprContext; 41 | } 42 | 43 | @Override 44 | public Void visitExprDecint(SamosaParser.ExprDecintContext ctx) { 45 | int number = Integer.parseInt(ctx.DECINT().getText()); 46 | functionGenerationContext.getMv().visitLdcInsn(number); 47 | return null; 48 | } 49 | 50 | @Override 51 | public Void visitExprParen(SamosaParser.ExprParenContext ctx) { 52 | visit(ctx.expr()); 53 | return null; 54 | } 55 | 56 | @Override 57 | public Void visitExprPlus(SamosaParser.ExprPlusContext ctx) { 58 | visit(ctx.expr(0)); // visit left operand 59 | visit(ctx.expr(1)); // visit right operand 60 | functionGenerationContext.getMv().visitInsn(Opcodes.IADD); 61 | return null; 62 | } 63 | 64 | @Override 65 | public Void visitExprMinus(SamosaParser.ExprMinusContext ctx) { 66 | visit(ctx.expr(0)); // visit left operand 67 | visit(ctx.expr(1)); // visit right operand 68 | functionGenerationContext.getMv().visitInsn(Opcodes.ISUB); 69 | return null; 70 | } 71 | 72 | @Override 73 | public Void visitExprMultiply(SamosaParser.ExprMultiplyContext ctx) { 74 | visit(ctx.expr(0)); // visit left operand 75 | visit(ctx.expr(1)); // visit right operand 76 | functionGenerationContext.getMv().visitInsn(Opcodes.IMUL); 77 | return null; 78 | } 79 | 80 | @Override 81 | public Void visitExprDivide(SamosaParser.ExprDivideContext ctx) { 82 | visit(ctx.expr(0)); // visit left operand 83 | visit(ctx.expr(1)); // visit right operand 84 | functionGenerationContext.getMv().visitInsn(Opcodes.IDIV); 85 | return null; 86 | } 87 | 88 | @Override 89 | public Void visitExprModulo(SamosaParser.ExprModuloContext ctx) { 90 | visit(ctx.expr(0)); // visit left operand 91 | visit(ctx.expr(1)); // visit right operand 92 | functionGenerationContext.getMv().visitInsn(Opcodes.IREM); 93 | return null; 94 | } 95 | 96 | @Override 97 | public Void visitUnaryMinus(SamosaParser.UnaryMinusContext ctx) { 98 | visit(ctx.expr()); 99 | functionGenerationContext.getMv().visitInsn(Opcodes.INEG); 100 | return null; 101 | } 102 | 103 | @Override 104 | public Void visitExprIdentifier(SamosaParser.ExprIdentifierContext ctx) { 105 | String idName = ctx.IDENTIFIER().getText(); 106 | doIdentifierCodegen(idName, symbolTable, Type.INT_TYPE, functionGenerationContext, qualifiedClassName, Opcodes.ILOAD); 107 | return null; 108 | } 109 | 110 | @Override 111 | public Void visitFunctionCallWithArgs(SamosaParser.FunctionCallWithArgsContext ctx) { 112 | FunctionCallCodegen functionCallCodegen = new FunctionCallCodegen( 113 | symbolTable, className, functionGenerationContext, className, packageName 114 | ); 115 | functionCallCodegen.doWithArgFunctionCallCodegen(ctx, false); // do not discard result 116 | return null; 117 | } 118 | 119 | @Override 120 | public Void visitFunctionCallNoArgs(SamosaParser.FunctionCallNoArgsContext ctx) { 121 | FunctionCallCodegen functionCallCodegen = new FunctionCallCodegen( 122 | symbolTable, className, functionGenerationContext, className, packageName 123 | ); 124 | functionCallCodegen.doNoArgFunctionCallCodegen(ctx, false); // do not discard result 125 | return null; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/staticchecker/FunctionReturnsChecker.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.staticchecker 2 | 3 | import com.sachett.samosa.logging.fmterror 4 | import com.sachett.samosa.logging.fmtfatalerr 5 | import com.sachett.samosa.parser.SamosaBaseListener 6 | import com.sachett.samosa.parser.SamosaParser 7 | import com.sachett.samosa.samosac.symbol.FunctionSymbol 8 | import com.sachett.samosa.samosac.symbol.SymbolType 9 | import com.sachett.samosa.samosac.symbol.symboltable.SymbolTable 10 | import org.antlr.v4.runtime.tree.ParseTreeWalker 11 | 12 | /** 13 | * Checks the function body and return statements in the function body. Currently, it checks: 14 | * 1. Whether the return value is of the correct type 15 | * 2. Whether the function returns a value if it should be returning a value 16 | * 3. Whether there is a nested function inside. 17 | * 18 | * Use the checkReturnStmts() function with the appropriate overload. 19 | */ 20 | class FunctionReturnsChecker( 21 | private val symbolTable: SymbolTable, 22 | private val fnSymbol: FunctionSymbol 23 | ) : SamosaBaseListener() { 24 | private var functionBlockEntered: Boolean = false 25 | private val mustHaveReturn = fnSymbol.returnType != SymbolType.VOID 26 | private var allOk = true 27 | private var foundReturn = false 28 | 29 | fun checkReturnStmts(ctx: SamosaParser.ImplicitRetTypeFuncDefContext): Boolean { 30 | println("###---- Checking returns -----") // DEBUG 31 | ParseTreeWalker.DEFAULT.walk(this, ctx) 32 | println("###----- Finished checking returns -----") // DEBUG 33 | return allOk 34 | } 35 | 36 | fun checkReturnStmts(ctx: SamosaParser.ExplicitRetTypeFuncDefContext): Boolean { 37 | println("###---- Checking returns -----") // DEBUG 38 | ParseTreeWalker.DEFAULT.walk(this, ctx) 39 | println("###----- Finished checking returns -----") // DEBUG 40 | return allOk 41 | } 42 | 43 | private fun checkFunInFun(fnDefinitionLineNum: Int) { 44 | if (!functionBlockEntered) { 45 | functionBlockEntered = true 46 | } else { 47 | fmterror("Functions within functions are not yet supported.", fnDefinitionLineNum) 48 | allOk = false 49 | } 50 | } 51 | 52 | private fun checkReturnedOrNot(fnDefinitionLineNum: Int) { 53 | if (mustHaveReturn && !foundReturn) { 54 | fmterror("Function must return a value of type ${fnSymbol.returnType.asString}.", fnDefinitionLineNum) 55 | allOk = false 56 | } 57 | } 58 | 59 | override fun enterImplicitRetTypeFuncDef(ctx: SamosaParser.ImplicitRetTypeFuncDefContext?) = 60 | checkFunInFun(ctx!!.IDENTIFIER().symbol.line) 61 | 62 | override fun enterExplicitRetTypeFuncDef(ctx: SamosaParser.ExplicitRetTypeFuncDefContext?) = 63 | checkFunInFun(ctx!!.IDENTIFIER().symbol.line) 64 | 65 | override fun exitImplicitRetTypeFuncDef(ctx: SamosaParser.ImplicitRetTypeFuncDefContext?) = 66 | checkReturnedOrNot(ctx!!.IDENTIFIER().symbol.line) 67 | 68 | override fun exitExplicitRetTypeFuncDef(ctx: SamosaParser.ExplicitRetTypeFuncDefContext?) = 69 | checkReturnedOrNot(ctx!!.IDENTIFIER().symbol.line) 70 | 71 | override fun enterReturnStmtNoExpr(ctx: SamosaParser.ReturnStmtNoExprContext?) { 72 | val lineNum = ctx!!.RETURN().symbol.line 73 | foundReturn = true 74 | 75 | if (fnSymbol.returnType != SymbolType.VOID) { 76 | fmterror( 77 | "Expected a return value of type ${fnSymbol.returnType.asString} from the function, " + 78 | "but found return statement with no expression.", 79 | lineNum 80 | ) 81 | allOk = false 82 | } 83 | } 84 | 85 | override fun enterReturnStmtWithExpr(ctx: SamosaParser.ReturnStmtWithExprContext?) { 86 | val lineNum = ctx!!.RETURN().symbol.line 87 | foundReturn = true 88 | 89 | println("Checking return at line ${lineNum}") // DEBUG 90 | val expressionTypeDetector = ExpressionTypeDetector(symbolTable) 91 | val (homoTypes, expType) = expressionTypeDetector.getType(ctx.expr()) 92 | 93 | if (!homoTypes) { 94 | fmterror("Mismatched types in expression in return statement.", lineNum) 95 | allOk = false 96 | } 97 | 98 | if (expType != fnSymbol.returnType) { 99 | fmterror( 100 | "Expected return value of type ${fnSymbol.returnType.asString} " + 101 | "but found expression of type ${expType.asString}", 102 | lineNum 103 | ) 104 | allOk = false 105 | } 106 | } 107 | 108 | override fun enterReturnStmtWithBooleanExpr(ctx: SamosaParser.ReturnStmtWithBooleanExprContext?) { 109 | if (fnSymbol.returnType != SymbolType.BOOL) { 110 | fmtfatalerr("Attempted to return ${SymbolType.BOOL.asString}, but function is expected to " + 111 | "return a value of type ${fnSymbol.returnType.asString}", ctx!!.start.line); 112 | } 113 | foundReturn = true 114 | } 115 | 116 | override fun enterBlock(ctx: SamosaParser.BlockContext?) { 117 | println("Inc scope in FunctionReturnsChecker") // DEBUG 118 | symbolTable.incrementScopeOverrideScopeCreation(false) 119 | } 120 | 121 | override fun exitBlock(ctx: SamosaParser.BlockContext?) { 122 | println("Dec scope in FunctionReturnsChecker") // DEBUG 123 | symbolTable.decrementScope() 124 | } 125 | } -------------------------------------------------------------------------------- /src/main/java/com/sachett/samosa/samosac/codegen/utils/delegation/CodegenDelegationManager.java: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.codegen.utils.delegation; 2 | 3 | import com.sachett.samosa.parser.SamosaBaseVisitor; 4 | import org.antlr.v4.runtime.tree.ParseTree; 5 | import org.antlr.v4.runtime.tree.RuleNode; 6 | 7 | public class CodegenDelegationManager extends SamosaBaseVisitor { 8 | private CodegenDelegatable currentCodeGenDelegator; 9 | private CodegenDelegatable currentCodeGenDelegated; 10 | private boolean beingDelegatedStore = false; 11 | 12 | public CodegenDelegationManager(CodegenDelegatable currentCodeGenDelegator) { 13 | this.currentCodeGenDelegator = currentCodeGenDelegator; 14 | } 15 | 16 | public CodegenDelegationManager( 17 | CodegenDelegatable currentCodeGenDelegator, 18 | CodegenDelegatable currentCodeGenDelegated 19 | ) { 20 | this.currentCodeGenDelegator = currentCodeGenDelegator; 21 | this.currentCodeGenDelegated = currentCodeGenDelegated; 22 | } 23 | 24 | public void setCurrentDelegator(CodegenDelegatable parentDelegator) { 25 | this.currentCodeGenDelegator = parentDelegator; 26 | } 27 | 28 | public void setCurrentDelegated(CodegenDelegatable childDelegated) { 29 | this.currentCodeGenDelegated = childDelegated; 30 | } 31 | 32 | private Void delegateVisitTo(CodegenDelegatable delegated, ParseTree tree) { 33 | delegated.setBeingDelegated(true); 34 | var voidPlaceholder = delegated.visit(tree); 35 | delegated.setBeingDelegated(false); 36 | return voidPlaceholder; 37 | } 38 | 39 | private Void delegateChildrenVisitTo(CodegenDelegatable delegated, RuleNode node) { 40 | delegated.setBeingDelegated(true); 41 | var voidPlaceholder = delegated.visitChildren(node); 42 | delegated.setBeingDelegated(false); 43 | return voidPlaceholder; 44 | } 45 | 46 | private void undelegate(CodegenDelegatable delegatable) { 47 | beingDelegatedStore = delegatable.isBeingDelegated(); 48 | delegatable.setBeingDelegated(false); 49 | } 50 | 51 | private void restoreDelegate(CodegenDelegatable delegatable) { 52 | if (delegatable == null) { 53 | return; 54 | } 55 | delegatable.setBeingDelegated(beingDelegatedStore); 56 | } 57 | 58 | @Override 59 | public Void visit(ParseTree parseTree) { 60 | CodegenDelegatedMethod method = CodegenMethodMap.getMethodFromClass(parseTree.getClass()); 61 | 62 | if (method == CodegenDelegatedMethod.NORMAL_DECLASSIGN) { 63 | System.out.println("Delegating NormalDeclAssign"); //debug 64 | } 65 | 66 | if (method == null) { 67 | return delegateVisitTo(currentCodeGenDelegator, parseTree); 68 | } 69 | 70 | if (currentCodeGenDelegated == null) { 71 | if (currentCodeGenDelegator == null) { 72 | return null; 73 | } 74 | else { 75 | return delegateVisitTo(currentCodeGenDelegator, parseTree); 76 | } 77 | } 78 | 79 | if (currentCodeGenDelegated.isMethodDelegated(method) 80 | || (!currentCodeGenDelegated.isMethodDelegated(method) 81 | && !currentCodeGenDelegated.isMethodDelegated(method))) { 82 | undelegate(currentCodeGenDelegated); 83 | var _void = delegateVisitTo(currentCodeGenDelegated, parseTree); 84 | restoreDelegate(currentCodeGenDelegated); 85 | return _void; 86 | } 87 | else { 88 | if (currentCodeGenDelegator == null) { 89 | return null; 90 | } 91 | else { 92 | return delegateVisitTo(currentCodeGenDelegator, parseTree); 93 | } 94 | } 95 | } 96 | 97 | @Override 98 | public Void visitChildren(RuleNode node) { 99 | CodegenDelegatedMethod method = CodegenMethodMap.getMethodFromClass(node.getClass()); 100 | 101 | if (method == CodegenDelegatedMethod.NORMAL_DECLASSIGN) { 102 | System.out.println("Delegating NormalDeclAssign"); //debug 103 | } 104 | 105 | if (method == null && currentCodeGenDelegated != null) { 106 | undelegate(currentCodeGenDelegator); 107 | var _void = delegateChildrenVisitTo(currentCodeGenDelegated, node); 108 | restoreDelegate(currentCodeGenDelegator); 109 | return _void; 110 | } 111 | else if (method == null) { 112 | return delegateChildrenVisitTo(currentCodeGenDelegator, node); 113 | } 114 | 115 | if (currentCodeGenDelegated == null) { 116 | if (currentCodeGenDelegator == null) { 117 | return null; 118 | } 119 | else { 120 | return delegateChildrenVisitTo(currentCodeGenDelegator, node); 121 | } 122 | } 123 | 124 | if (currentCodeGenDelegated.isMethodDelegated(method) 125 | || (!currentCodeGenDelegated.isMethodDelegated(method) 126 | && !currentCodeGenDelegated.isMethodDelegated(method))) { 127 | undelegate(currentCodeGenDelegator); 128 | var _void = delegateChildrenVisitTo(currentCodeGenDelegated, node); 129 | restoreDelegate(currentCodeGenDelegator); 130 | return _void; 131 | } 132 | else { 133 | if (currentCodeGenDelegator == null) { 134 | return null; 135 | } else { 136 | return delegateChildrenVisitTo(currentCodeGenDelegator, node); 137 | } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/staticchecker/ExpressionTypeDetector.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.staticchecker 2 | 3 | import com.sachett.samosa.logging.Severity 4 | import com.sachett.samosa.logging.err 5 | import com.sachett.samosa.logging.fmterror 6 | import com.sachett.samosa.logging.fmtfatalerr 7 | import com.sachett.samosa.parser.SamosaBaseVisitor 8 | import com.sachett.samosa.parser.SamosaParser 9 | import com.sachett.samosa.samosac.symbol.FunctionSymbol 10 | import com.sachett.samosa.samosac.symbol.SymbolType 11 | import com.sachett.samosa.samosac.symbol.symboltable.SymbolTable 12 | 13 | /** 14 | * Tries to detect the overall expression type given an expression. 15 | * The expression type detection is based on the frequencies of the types in it. 16 | */ 17 | class ExpressionTypeDetector( 18 | private val symbolTable: SymbolTable 19 | ) : SamosaBaseVisitor() { 20 | private var symbolTypesInExpr: MutableMap = mutableMapOf( 21 | SymbolType.INT to 0, 22 | SymbolType.STRING to 0, 23 | SymbolType.BOOL to 0, 24 | SymbolType.VOID to 0, 25 | SymbolType.FUNCTION to 0 26 | ) 27 | 28 | private val emptySymbolTypesInExpr: Map = mapOf( 29 | SymbolType.INT to 0, 30 | SymbolType.STRING to 0, 31 | SymbolType.BOOL to 0, 32 | SymbolType.VOID to 0, 33 | SymbolType.FUNCTION to 0 34 | ) 35 | 36 | /** 37 | * Tries to detect the overall expression type given the ExprContext. 38 | * The expression type detection is based on the frequencies of the types in it. 39 | * @param ctx The ExprContext to work on. 40 | * @return Pair. The first element is true if 41 | * all the terminals are of the same type in the expression and false if not. 42 | * The second element is the SymbolType that appears the most times in the expression. 43 | */ 44 | fun getType(ctx: SamosaParser.ExprContext): Pair { 45 | println("----- Finding expr type for expr -----") // DEBUG 46 | 47 | // clear the table 48 | symbolTypesInExpr = emptySymbolTypesInExpr.toMutableMap() 49 | visit(ctx) 50 | 51 | var maxFreq = 0 52 | var nTerms = 0 53 | 54 | // if there are no terms in expression then it is a void expression (which is invalid) 55 | var maxFreqSymbolType: SymbolType = SymbolType.VOID 56 | 57 | symbolTypesInExpr.forEach { 58 | if (it.value > maxFreq) { 59 | maxFreq = it.value 60 | maxFreqSymbolType = it.key 61 | } 62 | nTerms += it.value 63 | } 64 | 65 | return Pair(maxFreq == nTerms, maxFreqSymbolType) 66 | } 67 | 68 | /* ----------------- Visitor methods -------------------- */ 69 | 70 | // check the terminals and retrieve their types 71 | 72 | override fun visitExprDecint(ctx: SamosaParser.ExprDecintContext?) { 73 | symbolTypesInExpr[SymbolType.INT] = symbolTypesInExpr.getOrDefault(SymbolType.INT, 0) + 1 74 | return super.visitExprDecint(ctx) 75 | } 76 | 77 | override fun visitExprString(ctx: SamosaParser.ExprStringContext?) { 78 | symbolTypesInExpr[SymbolType.STRING] = symbolTypesInExpr.getOrDefault(SymbolType.STRING, 0) + 1 79 | return super.visitExprString(ctx) 80 | } 81 | 82 | override fun visitBooleanTrue(ctx: SamosaParser.BooleanTrueContext?) { 83 | symbolTypesInExpr[SymbolType.BOOL] = symbolTypesInExpr.getOrDefault(SymbolType.BOOL, 0) + 1 84 | return super.visitBooleanTrue(ctx) 85 | } 86 | 87 | override fun visitBooleanFalse(ctx: SamosaParser.BooleanFalseContext?) { 88 | symbolTypesInExpr[SymbolType.BOOL] = symbolTypesInExpr.getOrDefault(SymbolType.BOOL, 0) + 1 89 | return super.visitBooleanFalse(ctx) 90 | } 91 | 92 | /** 93 | * Retrieves the type of identifier. 94 | */ 95 | override fun visitExprIdentifier(ctx: SamosaParser.ExprIdentifierContext?) { 96 | val idName = ctx?.IDENTIFIER()?.text 97 | val lineNumber = ctx?.IDENTIFIER()?.symbol?.line 98 | 99 | val symbol = symbolTable.lookup(idName!!) ?: fmtfatalerr("Unknown identifier ${idName}.", lineNumber!!) 100 | 101 | symbolTypesInExpr[symbol.symbolType] = symbolTypesInExpr.getOrDefault(symbol.symbolType, 0) + 1 102 | return super.visitExprIdentifier(ctx) 103 | } 104 | 105 | // The next two functions check the return types of any function calls in the expression 106 | 107 | override fun visitFunctionCallWithArgs(ctx: SamosaParser.FunctionCallWithArgsContext?) { 108 | val retType: SymbolType = FunctionCallExprChecker.getRetTypeOfFunctionCallWithArgs(ctx, symbolTable) 109 | 110 | val lineNum = ctx!!.IDENTIFIER().symbol.line 111 | if (retType !in FunctionSymbol.allowedReturnTypes.minus(SymbolType.VOID)) { 112 | fmtfatalerr( 113 | "Illegal return type of function call in expression, in call to ${ctx.IDENTIFIER().text}. " + 114 | if (retType == SymbolType.VOID) "The function call returns no value." else "", 115 | lineNum, 116 | ) 117 | } 118 | 119 | symbolTypesInExpr[retType] = symbolTypesInExpr.getOrDefault(retType, 0) + 1 120 | 121 | /* Do not go into the function call expression here (hence super's method isn't called) */ 122 | } 123 | 124 | override fun visitFunctionCallNoArgs(ctx: SamosaParser.FunctionCallNoArgsContext?) { 125 | val retType: SymbolType = FunctionCallExprChecker.getRetTypeOfFunctionCallNoArgs(ctx, symbolTable) 126 | 127 | val lineNum = ctx!!.IDENTIFIER().symbol.line 128 | if (retType !in FunctionSymbol.allowedReturnTypes.minus(SymbolType.VOID)) { 129 | fmtfatalerr( 130 | "Illegal return type of function call in expression, in call to ${ctx.IDENTIFIER().text}. " + 131 | if (retType == SymbolType.VOID) "The function call returns no value." else "", 132 | lineNum, 133 | ) 134 | } 135 | 136 | symbolTypesInExpr[retType] = symbolTypesInExpr.getOrDefault(retType, 0) + 1 137 | /* Do not go into the function call expression here (hence super's method isn't called) */ 138 | } 139 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/staticchecker/analyzers/FunctionControlPathAnalyzer.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.staticchecker.analyzers 2 | 3 | import com.sachett.samosa.parser.SamosaBaseListener 4 | import com.sachett.samosa.parser.SamosaParser 5 | import com.sachett.samosa.samosac.staticchecker.ExpressionTypeDetector 6 | import com.sachett.samosa.samosac.staticchecker.analyzers.blocks.* 7 | import com.sachett.samosa.samosac.symbol.FunctionSymbol 8 | import com.sachett.samosa.samosac.symbol.SymbolType 9 | import com.sachett.samosa.samosac.symbol.symboltable.SymbolTable 10 | import org.antlr.v4.runtime.tree.ErrorNode 11 | import org.antlr.v4.runtime.tree.ParseTreeWalker 12 | import java.util.ArrayDeque 13 | 14 | class FunctionControlPathAnalyzer( 15 | private val symbolTable: SymbolTable, 16 | private val fnSymbol: FunctionSymbol 17 | ) : SamosaBaseListener() { 18 | 19 | fun checkAllControlPathsForReturns(ctx: SamosaParser.ImplicitRetTypeFuncDefContext): Boolean { 20 | println("**--- Starting control paths analysis ---") // DEBUG 21 | // No need to check if it returns void 22 | if (fnSymbol.returnType == SymbolType.VOID) { 23 | return true 24 | } 25 | 26 | ParseTreeWalker.DEFAULT.walk(this, ctx) 27 | 28 | return functionRootBlock.doesReturnProperly 29 | } 30 | 31 | fun checkAllControlPathsForReturns(ctx: SamosaParser.ExplicitRetTypeFuncDefContext): Boolean { 32 | println("**--- Starting control paths analysis ---") // DEBUG 33 | // No need to check if it returns void 34 | if (fnSymbol.returnType == SymbolType.VOID) { 35 | return true 36 | } 37 | 38 | ParseTreeWalker.DEFAULT.walk(this, ctx) 39 | 40 | return functionRootBlock.doesReturnProperly 41 | } 42 | 43 | /** 44 | * Build the tree for analyzing the function's control flow. 45 | */ 46 | private var functionRootBlock = ControlBlock(fnSymbol, null, ControlBlockType.FUNCTIONROOT) 47 | private var currentStrayBlock: StrayBlock = functionRootBlock.children[0] as StrayBlock 48 | private var isInsideControlNode = false 49 | private var currentControlNode: ControlNode? = null 50 | private var strayBlockQueue = ArrayDeque(arrayListOf(currentStrayBlock)) 51 | private var controlNodeStack = ArrayDeque() 52 | 53 | private fun addControlNode(controlNode: ControlNode) { 54 | // first save the currentControlNode onto the stack 55 | if (isInsideControlNode && currentControlNode != null) { 56 | controlNodeStack.addLast(currentControlNode!!) 57 | } 58 | 59 | isInsideControlNode = true 60 | currentStrayBlock.parent?.children?.add(controlNode) 61 | currentControlNode = controlNode 62 | 63 | // any stray blocks inside controlNode should be added to the strayBlockQueue 64 | // so that upcoming blocks' statements are considered to be a part of them 65 | for (child in controlNode.children) { 66 | for (grandChild in child.children) { 67 | if (grandChild is StrayBlock) { 68 | strayBlockQueue.addLast(grandChild) 69 | } 70 | } 71 | } 72 | } 73 | 74 | private fun exitControlNode() { 75 | val strayStrayBlock = StrayBlock(fnSymbol, doesReturnProperly = false, parent = currentControlNode?.parent) 76 | // Even if this StrayBlock is extraneous 77 | // (that is, this if statement was the last statement inside the block), 78 | // it won't matter. 79 | 80 | currentControlNode?.parent?.children?.add(strayStrayBlock) 81 | currentStrayBlock = strayStrayBlock 82 | 83 | if (!controlNodeStack.isEmpty()) { 84 | isInsideControlNode = true 85 | currentControlNode = controlNodeStack.removeFirst() 86 | } else { 87 | isInsideControlNode = false 88 | } 89 | } 90 | 91 | override fun enterReturnStmtWithExpr(ctx: SamosaParser.ReturnStmtWithExprContext?) { 92 | val expressionTypeDetector = ExpressionTypeDetector(symbolTable) 93 | val (homoTypes, expType) = expressionTypeDetector.getType(ctx!!.expr()) 94 | 95 | if (!currentStrayBlock.doesReturnProperly) { 96 | currentStrayBlock.doesReturnProperly = homoTypes && expType == fnSymbol.returnType 97 | } 98 | } 99 | 100 | override fun enterReturnStmtNoExpr(ctx: SamosaParser.ReturnStmtNoExprContext?) { 101 | if (!currentStrayBlock.doesReturnProperly) { 102 | currentStrayBlock.doesReturnProperly = fnSymbol.returnType == SymbolType.VOID 103 | } 104 | } 105 | 106 | override fun enterReturnStmtWithBooleanExpr(ctx: SamosaParser.ReturnStmtWithBooleanExprContext?) { 107 | if (!currentStrayBlock.doesReturnProperly) { 108 | currentStrayBlock.doesReturnProperly = fnSymbol.returnType == SymbolType.BOOL 109 | } 110 | } 111 | 112 | override fun enterIfStmt(ctx: SamosaParser.IfStmtContext?) { 113 | val ifControlNode = IfControlNode(fnSymbol, currentStrayBlock.parent!!, ctx!!) 114 | addControlNode(ifControlNode) 115 | } 116 | 117 | override fun exitIfStmt(ctx: SamosaParser.IfStmtContext?) { 118 | exitControlNode() 119 | } 120 | 121 | override fun enterWhileStmt(ctx: SamosaParser.WhileStmtContext?) { 122 | val whileControlNode = WhileControlNode(fnSymbol, currentStrayBlock.parent!!, ctx!!) 123 | addControlNode(whileControlNode) 124 | } 125 | 126 | override fun exitWhileStmt(ctx: SamosaParser.WhileStmtContext?) { 127 | exitControlNode() 128 | } 129 | 130 | override fun enterBlock(ctx: SamosaParser.BlockContext?) { 131 | symbolTable.incrementScopeOverrideScopeCreation(false) 132 | if (ctx!!.parent is SamosaParser.IfStmtContext 133 | || ctx.parent is SamosaParser.WhileStmtContext 134 | || ctx.parent is SamosaParser.ImplicitRetTypeFuncDefContext? 135 | || ctx.parent is SamosaParser.ExplicitRetTypeFuncDefContext? 136 | ) { 137 | currentStrayBlock = strayBlockQueue.removeFirst() 138 | } 139 | } 140 | 141 | override fun exitBlock(ctx: SamosaParser.BlockContext?) { 142 | symbolTable.decrementScope(false) 143 | } 144 | 145 | override fun visitErrorNode(node: ErrorNode?) { 146 | // TODO: Handle this 147 | super.visitErrorNode(node) 148 | } 149 | } -------------------------------------------------------------------------------- /src/main/java/com/sachett/samosa/samosac/codegen/function/FunctionGenerationContext.java: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.codegen.function; 2 | 3 | import com.sachett.samosa.samosac.codegen.ClassFileGenerator; 4 | import com.sachett.samosa.samosac.symbol.FunctionSymbol; 5 | import com.sachett.samosa.samosac.symbol.ISymbol; 6 | import org.apache.bcel.util.ClassPath; 7 | import org.objectweb.asm.ClassWriter; 8 | import org.objectweb.asm.MethodVisitor; 9 | import org.objectweb.asm.Type; 10 | import org.objectweb.asm.commons.AnalyzerAdapter; 11 | import org.objectweb.asm.commons.LocalVariablesSorter; 12 | 13 | import java.util.HashMap; 14 | 15 | import com.sachett.samosa.logging.LoggingUtilsKt; 16 | import org.objectweb.asm.util.TraceClassVisitor; 17 | 18 | public class FunctionGenerationContext { 19 | private final MethodVisitor methodVisitor; 20 | private final LocalVariablesSorter localVariablesSorter; 21 | private final AnalyzerAdapter analyzerAdapter; 22 | private final HashMap localVariableIndex = new HashMap<>(); 23 | private ClassFileGenerator parentClassGenerator = null; 24 | 25 | /** 26 | * Indicates if the function needs a RETURN instruction (with no expression) 27 | * at the end of the function visit. 28 | */ 29 | private boolean needsNoExprReturn = false; 30 | 31 | public FunctionGenerationContext( 32 | ClassWriter classWriter, 33 | int access, String name, String descriptor, 34 | String signature, String[] exceptions 35 | ) { 36 | this.methodVisitor = classWriter.visitMethod(access, name, descriptor, signature, exceptions); 37 | analyzerAdapter = new AnalyzerAdapter( 38 | FunctionGenerationContext.class.getName(), 39 | access, name, descriptor, this.methodVisitor 40 | ); 41 | localVariablesSorter = new LocalVariablesSorter(access, descriptor, analyzerAdapter); 42 | } 43 | 44 | public FunctionGenerationContext( 45 | TraceClassVisitor classWriter, 46 | int access, String name, String descriptor, 47 | String signature, String[] exceptions 48 | ) { 49 | this.methodVisitor = classWriter.visitMethod(access, name, descriptor, signature, exceptions); 50 | analyzerAdapter = new AnalyzerAdapter( 51 | FunctionGenerationContext.class.getName(), 52 | access, name, descriptor, this.methodVisitor 53 | ); 54 | localVariablesSorter = new LocalVariablesSorter(access, descriptor, this.methodVisitor); 55 | } 56 | 57 | public MethodVisitor getMethodVisitor() { 58 | return methodVisitor; 59 | } 60 | 61 | public MethodVisitor getMv() { 62 | return analyzerAdapter; 63 | } 64 | 65 | public AnalyzerAdapter getAnalyzerAdapter() { 66 | return analyzerAdapter; 67 | } 68 | 69 | public void setParentClassGenerator(ClassFileGenerator classFileGenerator) { 70 | this.parentClassGenerator = classFileGenerator; 71 | } 72 | 73 | public ClassFileGenerator getParentClassGenerator() { 74 | return this.parentClassGenerator; 75 | } 76 | 77 | public static class FrameStackMap { 78 | public int numLocals; 79 | public Object[] locals; 80 | public int numStack; 81 | public Object[] stack; 82 | 83 | public FrameStackMap(int numLocals, Object[] locals, int numStack, Object[] stack) { 84 | this.numLocals = numLocals; 85 | this.locals = locals; 86 | this.numStack = numStack; 87 | this.stack = stack; 88 | } 89 | } 90 | 91 | /** 92 | * Should be used before a visitJump or visitLabel call to retrieve status of current stack and locals. 93 | * @return Locals and stack of current frame 94 | */ 95 | public FrameStackMap getCurrentFrameStackInfo() { 96 | var localsSize = analyzerAdapter.locals == null ? 0 : analyzerAdapter.locals.size(); 97 | var locals = analyzerAdapter.locals == null ? null : analyzerAdapter.locals.toArray(); 98 | var stackSize = analyzerAdapter.stack == null ? 0 : analyzerAdapter.stack.size(); 99 | var stack = analyzerAdapter.stack == null ? null : analyzerAdapter.stack.toArray(); 100 | 101 | return new FrameStackMap(localsSize, locals, stackSize, stack); 102 | } 103 | 104 | public LocalVariablesSorter getLocalVariablesSorter() { 105 | return localVariablesSorter; 106 | } 107 | 108 | public void newLocal(String name, Type type) { 109 | localVariableIndex.put(name, localVariablesSorter.newLocal(type)); 110 | } 111 | 112 | public void registerLocal(String name, int index) { 113 | localVariableIndex.put(name, index); 114 | } 115 | 116 | static public String generateDescriptor(FunctionSymbol functionSymbol) { 117 | StringBuilder descriptorString = new StringBuilder("("); 118 | 119 | for (ISymbol symbol : functionSymbol.getParamList()) { 120 | switch (symbol.getSymbolType()) { 121 | case INT: 122 | descriptorString.append("I"); 123 | break; 124 | case STRING: 125 | descriptorString.append("Ljava/lang/String;"); 126 | break; 127 | case BOOL: 128 | descriptorString.append("Z"); 129 | break; 130 | } 131 | } 132 | descriptorString.append(")"); 133 | switch (functionSymbol.getReturnType()) { 134 | case INT: 135 | descriptorString.append("I"); 136 | break; 137 | case STRING: 138 | descriptorString.append("Ljava/lang/String;"); 139 | break; 140 | case BOOL: 141 | descriptorString.append("Z"); 142 | break; 143 | case VOID: 144 | descriptorString.append("V"); 145 | break; 146 | } 147 | 148 | return descriptorString.toString(); 149 | } 150 | 151 | public Integer getLocalVarIndex(String name) { 152 | if (localVariableIndex.containsKey(name)) { 153 | return localVariableIndex.get(name); 154 | } 155 | else { 156 | LoggingUtilsKt.err("Internal error: Invalid local variable demanded."); 157 | throw new RuntimeException(); 158 | } 159 | } 160 | 161 | public void setNeedsNoExprReturn(boolean needsNoExprReturn) { 162 | this.needsNoExprReturn = needsNoExprReturn; 163 | } 164 | 165 | public boolean getNeedsNoExprReturn() { 166 | return this.needsNoExprReturn; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/main/java/com/sachett/samosa/samosac/codegen/expressions/StringExprCodegen.java: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.codegen.expressions; 2 | 3 | import com.sachett.samosa.logging.LoggingUtilsKt; 4 | import com.sachett.samosa.parser.SamosaBaseVisitor; 5 | import com.sachett.samosa.parser.SamosaParser; 6 | import com.sachett.samosa.samosac.codegen.function.FunctionCallCodegen; 7 | import com.sachett.samosa.samosac.codegen.function.FunctionGenerationContext; 8 | import com.sachett.samosa.samosac.symbol.symboltable.SymbolTable; 9 | import org.objectweb.asm.Opcodes; 10 | import org.objectweb.asm.Type; 11 | 12 | import java.lang.reflect.Constructor; 13 | import java.lang.reflect.Method; 14 | 15 | public class StringExprCodegen extends SamosaBaseVisitor implements IExprCodegen { 16 | private SamosaParser.ExprContext exprContext; 17 | private final FunctionGenerationContext functionGenerationContext; 18 | private final SymbolTable symbolTable; 19 | private final String qualifiedClassName; 20 | private final String className; 21 | private final String packageName; 22 | 23 | public StringExprCodegen( 24 | SamosaParser.ExprContext exprContext, 25 | SymbolTable symbolTable, 26 | FunctionGenerationContext functionGenerationContext, 27 | String className, 28 | String packageName 29 | ) { 30 | this.exprContext = exprContext; 31 | this.functionGenerationContext = functionGenerationContext; 32 | this.symbolTable = symbolTable; 33 | this.qualifiedClassName = packageName.replace(".", "/") + className; 34 | this.className = className; 35 | this.packageName = packageName; 36 | } 37 | 38 | @Override 39 | public void doCodegen() { 40 | visit(this.exprContext); 41 | } 42 | 43 | @Override 44 | public Void visitExprString(SamosaParser.ExprStringContext ctx) { 45 | String strText = ctx.getText(); 46 | String str = strText.substring(1, strText.length() - 1); 47 | functionGenerationContext.getMv().visitLdcInsn(str); 48 | return null; 49 | } 50 | 51 | @Override 52 | public Void visitExprIdentifier(SamosaParser.ExprIdentifierContext ctx) { 53 | String idName = ctx.IDENTIFIER().getText(); 54 | doIdentifierCodegen(idName, symbolTable, Type.getType(String.class), 55 | functionGenerationContext, qualifiedClassName, Opcodes.ALOAD); 56 | return super.visitExprIdentifier(ctx); 57 | } 58 | 59 | public void setExprContext(SamosaParser.ExprContext exprContext) { 60 | this.exprContext = exprContext; 61 | } 62 | 63 | @Override 64 | public Void visitExprPlus(SamosaParser.ExprPlusContext ctx) { 65 | // Java equivalent: 66 | // StringBuilder sb = new StringBuilder("stringLeft"); 67 | // sb.append("stringLeft") 68 | 69 | // Make a StringBuilder object and duplicate it on the stack 70 | // Note that we need to duplicate it ONE time because we will make three calls on it as follows: 71 | // 1. : pops off object ref 72 | // 2. append: pops off object ref, does append, then pushes it back (see descriptor of append) 73 | // 3. toString: pops off object ref, pushes string representation onto stack 74 | functionGenerationContext.getMv().visitTypeInsn(Opcodes.NEW, Type.getType(StringBuilder.class).getInternalName()); 75 | functionGenerationContext.getMv().visitInsn(Opcodes.DUP); 76 | // Process and put the left operand on the stack 77 | visit(ctx.expr(0)); 78 | 79 | // Using Reflection API so that I don't have to write descriptor strings by hand 80 | Constructor stringBuilderConstructor = null; 81 | Method stringBuilderAppend = null; 82 | Method stringBuilderToString = null; 83 | 84 | try { 85 | stringBuilderConstructor = StringBuilder.class.getConstructor(String.class); 86 | stringBuilderAppend = StringBuilder.class.getMethod("append", String.class); 87 | stringBuilderToString = StringBuilder.class.getMethod("toString"); 88 | } catch (NoSuchMethodException e) { 89 | LoggingUtilsKt.err(String.valueOf(e)); 90 | } 91 | 92 | // Invoke the constructor of StringBuilder with the left operand 93 | assert stringBuilderConstructor != null; 94 | functionGenerationContext.getMv().visitMethodInsn( 95 | Opcodes.INVOKESPECIAL, 96 | Type.getType(StringBuilder.class).getInternalName(), 97 | "", 98 | Type.getConstructorDescriptor(stringBuilderConstructor), 99 | false 100 | ); 101 | 102 | // Now process the right operand and put it on the stack 103 | visit(ctx.expr(1)); 104 | 105 | // Now invoke append on the StringBuilder object with the right operand 106 | assert stringBuilderAppend != null; 107 | functionGenerationContext.getMv().visitMethodInsn( 108 | Opcodes.INVOKEVIRTUAL, 109 | Type.getType(StringBuilder.class).getInternalName(), 110 | "append", 111 | Type.getMethodDescriptor(stringBuilderAppend), 112 | false 113 | ); 114 | 115 | // Now get the string representation using toString() 116 | assert stringBuilderToString != null; 117 | functionGenerationContext.getMv().visitMethodInsn( 118 | Opcodes.INVOKEVIRTUAL, 119 | Type.getType(StringBuilder.class).getInternalName(), 120 | "toString", 121 | Type.getMethodDescriptor(stringBuilderToString), 122 | false 123 | ); 124 | 125 | return null; 126 | } 127 | 128 | @Override 129 | public Void visitExprParen(SamosaParser.ExprParenContext ctx) { 130 | visit(ctx.expr()); 131 | return null; 132 | } 133 | 134 | @Override 135 | public Void visitFunctionCallWithArgs(SamosaParser.FunctionCallWithArgsContext ctx) { 136 | FunctionCallCodegen functionCallCodegen = new FunctionCallCodegen( 137 | symbolTable, className, functionGenerationContext, className, packageName 138 | ); 139 | functionCallCodegen.doWithArgFunctionCallCodegen(ctx, false); // do not discard result 140 | return null; 141 | } 142 | 143 | @Override 144 | public Void visitFunctionCallNoArgs(SamosaParser.FunctionCallNoArgsContext ctx) { 145 | FunctionCallCodegen functionCallCodegen = new FunctionCallCodegen( 146 | symbolTable, className, functionGenerationContext, className, packageName 147 | ); 148 | functionCallCodegen.doNoArgFunctionCallCodegen(ctx, false); // do not discard result 149 | return null; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/main/antlr4/com/sachett/samosa/parser/Samosa.g4: -------------------------------------------------------------------------------- 1 | grammar Samosa; 2 | 3 | // Use when separating lexer and parser grammars: 4 | //options { 5 | // tokenVocab = 'libs/SamosaLexer'; 6 | //} 7 | 8 | // Lexer Grammar 9 | 10 | PROGSTART: ''; 11 | PROGEND: ''; 12 | 13 | LPAREN: '('; 14 | RPAREN: ')'; 15 | LCURLYBR: '{'; 16 | RCURLYBR: '}'; 17 | LSQBR: '['; 18 | RSQBR: ']'; 19 | 20 | PLUS: '+'; 21 | MINUS: '-'; 22 | DIVIDE: '/'; 23 | MULT: '*'; 24 | MODULO: '%'; 25 | EQUAL: '='; 26 | 27 | LT: '<'; 28 | GT: '>'; 29 | LTEQ: '<='; 30 | GTEQ: '>='; 31 | COMP: '=='; 32 | COMPNOTEQ: '!='; 33 | 34 | MARKERCOMMULTILINESTART: '/*'; 35 | MARKERCOMMULTILINEEND: '*/'; 36 | MARKERCOMSINGLELINE: ('//' | 'binod'); 37 | COMMENTSL : MARKERCOMSINGLELINE ~[\r\n]* '\r'? '\n' -> skip ; 38 | COMMENTML: MARKERCOMMULTILINESTART .*? MARKERCOMMULTILINEEND -> skip; 39 | 40 | LOGICALAND: ('and' | '&&'); 41 | LOGICALOR: ('or' | '||'); 42 | LOGICALXOR: ('strictor' | '||!'); 43 | LOGICALNOT: ('not' | '!!'); 44 | 45 | TRUE: ('true' | 'yes' | 'True' | 'TRUE'); 46 | FALSE: ('false' | 'nope' | 'False' | 'FALSE'); 47 | 48 | IF: 'if'; 49 | ELSE: 'else'; 50 | FUNCDEF: 'let'; 51 | VARDEF: ('bro,'); 52 | 53 | BINAND: '&'; 54 | BINOR: '|'; 55 | BINXOR: '|!'; 56 | BINNOT: '!'; 57 | 58 | RIGHTARROW: '->'; 59 | TILDE: '~'; 60 | COLON: ':'; 61 | DOUBLEDOT: '..'; 62 | STATEMENTEND: '.'; 63 | WHILE: 'while'; 64 | RETURN: 'return'; 65 | 66 | INTTYPE: 'int'; 67 | STRINGTYPE: 'string'; 68 | BOOLTYPE: 'boolie'; 69 | VOIDTYPE: 'void'; 70 | NULLVALUE: 'null'; 71 | 72 | TRIPLEDOT: '...'; 73 | IDK: 'idk'; 74 | QUESTIONMARK: '?'; 75 | SEMICOLON: ';'; 76 | 77 | IMPORT: 'needs'; 78 | BREAK: 'yamete_kudasai'; 79 | CONTINUE: 'thanku_next'; 80 | 81 | DECINT: [0-9]+; 82 | IDENTIFIER: ([_a-zA-Z][_a-zA-Z0-9]+ | [_a-zA-Z]); 83 | STRING: '"' ('\\"'|.)*? '"'; 84 | COMMA: ','; 85 | 86 | NEWLINE: ('\r'? '\n' | '\r' | '\n')+ -> skip; 87 | TAB: ('t' | ' ' | ' ') -> skip; 88 | WHITESPACE: ' ' -> skip; 89 | 90 | OTHER: .; 91 | 92 | 93 | // Parser Grammar 94 | 95 | program: EOF 96 | | PROGSTART PROGEND 97 | | PROGSTART (needsStmt)? statements PROGEND 98 | | (needsStmt)? statements EOF; 99 | 100 | needsStmt: IMPORT LCURLYBR (classToImport+=qualifiedClassIdentifier COMMA)* 101 | (classToImport+=qualifiedClassIdentifier)? RCURLYBR; 102 | 103 | qualifiedClassIdentifier: (IDENTIFIER COLON COLON)*? IDENTIFIER 104 | | (IDENTIFIER COLON COLON)+ LCURLYBR 105 | (classListInPackage+=IDENTIFIER COMMA)* (classListInPackage+=IDENTIFIER)? RCURLYBR; 106 | 107 | statements: (statement | compoundStmt | uncertainCompoundStmt | funcDef | COMMENTSL | COMMENTML)+; 108 | 109 | statement: statement QUESTIONMARK LSQBR expr RSQBR statement #uncertainStatementMultiple 110 | | statement QUESTIONMARK LSQBR expr RSQBR TRIPLEDOT # uncertainStatementSingle 111 | | (declStmt | assignStmt | declAssignStmt | functionCall | returnStmt | loopcontrolStmt) STATEMENTEND #regularStmt; 112 | 113 | loopcontrolStmt: BREAK #breakControlStmt 114 | | CONTINUE #continueControlStmt; 115 | 116 | returnStmt: RETURN #returnStmtNoExpr 117 | | RETURN expr #returnStmtWithExpr 118 | | RETURN booleanExpr #returnStmtWithBooleanExpr; // TODO: STATIC VERIFICATION TO BE ADDED!! 119 | 120 | assignStmt: IDENTIFIER EQUAL expr #exprAssign 121 | | IDENTIFIER EQUAL booleanExpr #booleanExprAssign; 122 | 123 | expr: MINUS expr #unaryMinus 124 | | expr DIVIDE expr #exprDivide 125 | | expr MULT expr #exprMultiply 126 | | expr MODULO expr #exprModulo 127 | | expr PLUS expr #exprPlus 128 | | expr MINUS expr #exprMinus 129 | | LPAREN expr RPAREN #exprParen 130 | | IDENTIFIER #exprIdentifier 131 | | DECINT #exprDecint 132 | | STRING #exprString 133 | | functionCall #exprFunctionCall; 134 | 135 | declStmt: VARDEF IDENTIFIER COLON typeName; 136 | typeName: INTTYPE | STRINGTYPE | VOIDTYPE | BOOLTYPE; 137 | 138 | declAssignStmt: VARDEF IDENTIFIER COLON BOOLTYPE EQUAL booleanExpr #booleanDeclAssignStmt 139 | | VARDEF IDENTIFIER COLON typeName EQUAL expr #normalDeclAssignStmt 140 | | VARDEF IDENTIFIER EQUAL expr #typeInferredDeclAssignStmt 141 | | VARDEF IDENTIFIER EQUAL booleanExpr #typeInferredBooleanDeclAssignStmt; 142 | 143 | block: LCURLYBR RCURLYBR 144 | | LCURLYBR statements RCURLYBR; 145 | 146 | ifStmt: IF LPAREN booleanExpr RPAREN block (ELSE IF LPAREN booleanExpr RPAREN elseifblocks+=block)*? (ELSE elseblock+=block)?; 147 | 148 | whileStmt: WHILE LPAREN booleanExpr RPAREN block; 149 | 150 | booleanExpr: LOGICALNOT booleanExpr #booleanExprNot 151 | | booleanExpr LOGICALOR booleanExpr #booleanExprOr 152 | | booleanExpr LOGICALAND booleanExpr #booleanExprAnd 153 | | booleanExpr LOGICALXOR booleanExpr #booleanExprXor 154 | | expr relOp expr #booleanExprRelOp 155 | | expr compOp expr #booleanExprCompOp // TODO: add rule booleanExpr compOp booleanExpr 156 | | LPAREN booleanExpr RPAREN #booleanExprParen 157 | | IDENTIFIER #booleanExprIdentifier 158 | | TRUE #booleanTrue 159 | | FALSE #booleanFalse 160 | | functionCall #booleanFunctionCall; 161 | 162 | compOp: (COMP | COMPNOTEQ); 163 | 164 | relOp: (LT | GT | LTEQ | GTEQ); 165 | 166 | compoundStmt: (ifStmt | whileStmt); 167 | 168 | uncertainCompoundStmt: compoundStmt QUESTIONMARK LSQBR expr RSQBR STATEMENTEND #uncertainCompoundStmtSingle 169 | | compoundStmt QUESTIONMARK LSQBR expr RSQBR compoundStmt STATEMENTEND #uncertainCompoundStmtMultiple; 170 | 171 | funcDef: (FUNCDEF IDENTIFIER block 172 | | FUNCDEF IDENTIFIER LPAREN RPAREN block 173 | | FUNCDEF IDENTIFIER LPAREN funcArgList RPAREN block) (STATEMENTEND)? #implicitRetTypeFuncDef 174 | | (FUNCDEF IDENTIFIER COLON typeName block 175 | | FUNCDEF IDENTIFIER LPAREN RPAREN COLON typeName block 176 | | FUNCDEF IDENTIFIER LPAREN funcArgList RPAREN COLON typeName block) (STATEMENTEND)? #explicitRetTypeFuncDef; 177 | 178 | funcArgList: args+=argParam 179 | | (args+=argParam COMMA)+ args+=argParam; 180 | argParam: IDENTIFIER COLON typeName; 181 | 182 | callArgList: (callParams+=expr COMMA)* (booleanCallParams+=booleanExpr COMMA)* 183 | (callParams+=expr COMMA)* (booleanCallParams+=booleanExpr COMMA)* 184 | (callParams+=expr | booleanCallParams+=booleanExpr)?; 185 | functionCall: LPAREN RPAREN RIGHTARROW IDENTIFIER #functionCallNoArgs 186 | | LPAREN callArgList RPAREN RIGHTARROW IDENTIFIER #functionCallWithArgs 187 | | LPAREN RPAREN RIGHTARROW qualifiedIdentifier #qualifiedFunctionCallNoArgs 188 | | LPAREN callArgList RPAREN RIGHTARROW qualifiedIdentifier #qualifiedFunctionCallWithArgs; 189 | 190 | qualifiedIdentifier: idList+=IDENTIFIER (DOUBLEDOT idList+=IDENTIFIER)+; -------------------------------------------------------------------------------- /src/main/java/com/sachett/samosa/samosac/codegen/compoundstmt/IfStmtCodegen.java: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.codegen.compoundstmt; 2 | 3 | import com.sachett.samosa.parser.SamosaParser; 4 | import com.sachett.samosa.samosac.codegen.ClassFileGenerator; 5 | import com.sachett.samosa.samosac.codegen.expressions.BooleanExprCodegen; 6 | import com.sachett.samosa.samosac.codegen.function.FunctionGenerationContext; 7 | import com.sachett.samosa.samosac.codegen.utils.delegation.CodegenDelegatable; 8 | import com.sachett.samosa.samosac.codegen.utils.delegation.CodegenDelegatedMethod; 9 | import com.sachett.samosa.samosac.symbol.symboltable.SymbolTable; 10 | import kotlin.Pair; 11 | import org.objectweb.asm.Label; 12 | import org.objectweb.asm.Opcodes; 13 | 14 | import java.util.ArrayList; 15 | import java.util.HashSet; 16 | import java.util.List; 17 | 18 | public class IfStmtCodegen extends CodegenDelegatable implements IControlNodeCodegen { 19 | FunctionGenerationContext functionGenerationContext; 20 | ControlNodeCodegenType controlNodeCodegenType = ControlNodeCodegenType.IF; 21 | private final CodegenDelegatable delegatedParentCodegen; 22 | 23 | private final String className; 24 | private final String packageName; 25 | private final SymbolTable symbolTable; 26 | 27 | @Override 28 | public ControlNodeCodegenType getControlNodeCodegenType() { 29 | return controlNodeCodegenType; 30 | } 31 | 32 | public IfStmtCodegen( 33 | CodegenDelegatable delegatedParentCodegen, 34 | FunctionGenerationContext functionGenerationContext, 35 | SymbolTable symbolTable, 36 | String className, 37 | String packageName 38 | ) { 39 | super(delegatedParentCodegen.getSharedDelegationManager()); 40 | 41 | // Register the stuff that this generator generates with the shared delegation manager. 42 | HashSet delegatedMethodHashSet = new HashSet<>(List.of( 43 | CodegenDelegatedMethod.BLOCK, 44 | CodegenDelegatedMethod.IF 45 | )); 46 | this.registerDelegatedMethods(delegatedMethodHashSet); 47 | 48 | this.functionGenerationContext = functionGenerationContext; 49 | this.delegatedParentCodegen = delegatedParentCodegen; 50 | this.className = className; 51 | this.packageName = packageName; 52 | this.symbolTable = symbolTable; 53 | } 54 | 55 | private boolean isGlobalBlock() { 56 | return delegatedParentCodegen instanceof ClassFileGenerator; 57 | } 58 | 59 | public void generateIfStmt(SamosaParser.IfStmtContext ctx) { 60 | SamosaParser.BooleanExprContext booleanExprContext = ctx.booleanExpr(0); 61 | BooleanExprCodegen booleanExprCodegen = new BooleanExprCodegen( 62 | booleanExprContext, symbolTable, functionGenerationContext, className, packageName); 63 | 64 | booleanExprCodegen.setJumpToFalseLabel(false); 65 | ArrayList> labels = new ArrayList<>(); 66 | int nElseIfs = ctx.elseifblocks.size(); 67 | 68 | // Create labels corresponding to all branches 69 | // For an if statement with no else-ifs, no. of labels required = 2 (regardless of presence of else block) 70 | // For an if statement with else-ifs, no. of labels required = 2 + no. of else-if blocks 71 | // No. of booleanExprContexts = no. of else-ifs + 1 (for if) 72 | for (int i = 0; i < nElseIfs + 2; i++) { 73 | if (i != nElseIfs + 1) { 74 | // for the if and else-if statements' labels 75 | labels.add(new Pair<>(new Label(), ctx.booleanExpr(i))); 76 | } else { 77 | // for the label corresponding to the next statement after if 78 | // (afterIf label) 79 | labels.add(new Pair<>(new Label(), null)); 80 | } 81 | } 82 | 83 | Label afterIf = labels.get(labels.size() - 1).getFirst(); 84 | ArrayList frameStackMaps = new ArrayList<>(); 85 | 86 | // First generate the boolean expressions and if branch statements 87 | for (int i = 0; i < labels.size(); i++) { 88 | var labelCtx = labels.get(i); 89 | if (labelCtx.getSecond() != null) { 90 | booleanExprCodegen.setBooleanExprContext(labelCtx.getSecond()); 91 | if (!(booleanExprContext instanceof SamosaParser.BooleanExprRelOpContext) 92 | && !(booleanExprContext instanceof SamosaParser.BooleanExprCompOpContext)) { 93 | booleanExprCodegen.doCodegen(); 94 | // In this case, after codegen of the booleanExpr, the stack should contain 95 | // a bool value on top, on whose basis we can jump 96 | 97 | // Note that IFEQ jumps if top of stack == 0 and IFNE jumps if top of stack != 0 98 | functionGenerationContext.getMv().visitJumpInsn(Opcodes.IFNE, labelCtx.getFirst()); 99 | } else { 100 | booleanExprCodegen.setFalseLabel(labelCtx.getFirst()); 101 | booleanExprCodegen.setJumpLabelsHaveBlocks(true); 102 | booleanExprCodegen.doCodegen(); 103 | 104 | // update the labelCtx with the right jump label 105 | labels.set(i, new Pair<>(booleanExprCodegen.getActualJumpLabel(), labelCtx.getSecond())); 106 | } 107 | } else { 108 | // TODO: Generate else block code here 109 | if (ctx.elseblock.size() > 0) { 110 | // else block is present 111 | this.startDelegatingTo(delegatedParentCodegen); 112 | delegatedParentCodegen.visit(ctx.elseblock.get(0)); 113 | this.finishDelegating(); 114 | } 115 | // the label corresponding to the next statement after the if construct 116 | frameStackMaps.add(functionGenerationContext.getCurrentFrameStackInfo()); 117 | functionGenerationContext.getMv().visitJumpInsn(Opcodes.GOTO, afterIf); 118 | } 119 | } 120 | 121 | // Now generate the code for inside the blocks 122 | for (int i = 0; i < labels.size(); i++) { 123 | // visit the label and generate code for that block 124 | Pair labelCtx = labels.get(i); 125 | functionGenerationContext.getMv().visitLabel(labelCtx.getFirst()); 126 | functionGenerationContext.getMv().visitFrame( 127 | Opcodes.F_NEW, 128 | frameStackMaps.get(0).numLocals, frameStackMaps.get(0).locals, 129 | frameStackMaps.get(0).numStack, frameStackMaps.get(0).stack 130 | ); 131 | 132 | // generate codes for the corresponding blocks 133 | if (i < labels.size() - 1) { 134 | this.startDelegatingTo(delegatedParentCodegen); 135 | delegatedParentCodegen.visit(ctx.block(i)); 136 | this.finishDelegating(); 137 | // after execution, skip other labels and go to afterIf 138 | frameStackMaps.add(functionGenerationContext.getCurrentFrameStackInfo()); 139 | functionGenerationContext.getMv().visitJumpInsn(Opcodes.GOTO, afterIf); 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /docs/syntax/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Syntax 4 | nav_order: 2 5 | --- 6 | 7 | # Syntax 8 | {: .no_toc } 9 | 10 | As samosa is still in its first release, it has limited features. More features will be added soon in upcoming releases. 11 | 12 | ## Table of contents 13 | {: .no_toc .text-delta } 14 | 15 | 1. TOC 16 | {:toc} 17 | 18 | ## Starting a program 19 | 20 | Any samosa program must start with `` and end with ``. 21 | Note that statements in samosa end with a period (`.`). For example: 22 | 23 | ``` 24 | 25 | ("Hello World!") -> putout. 26 | 27 | ``` 28 | 29 | (Note: in the example above, we are making a function call to `putout`, a built-in function. That line is equivalent to `System.out.println("Hello World!")` in Java.) 30 | 31 | ## Comments 32 | 33 | Comments in samosa can span multiple lines. They start with `/*` and end with `*/`. 34 | 35 | Example: 36 | 37 | ``` 38 | 39 | /* This line is a comment and will not be executed. */ 40 | ("This line is executed.") -> putout. 41 | 42 | ``` 43 | 44 | ## Variables 45 | 46 | Variables are declared with the keyword `bro,`  (yes, the comma is necessary :-)). Currently, variables can be only of three types: `int` (for integers), `string` (for strings), or `boolie` (for boolean values). Some examples of declaration and initialization: 47 | 48 | ``` 49 | 50 | 51 | bro, i: int = 0. 52 | bro, str: string = "hello!". 53 | bro, aBoolVal: boolie = true. 54 | 55 | /* Variables can also just be declared. */ 56 | bro, j: int. 57 | bro, str2: string. 58 | bro, boolieVal: boolie. 59 | 60 | 61 | ``` 62 | 63 | If a variable is only declared, the variable is assigned the default value for that type: 64 | 65 | * for `int`, the default value is 57005 (in hex, `0xDEAD`). 66 | * for `string`, the default value is `lawl` 67 | * for `boolie`, the default value is `true`. 68 | 69 | If you're initializing a variable at the same time when you are declaring it, you can skip writing its type: 70 | 71 | ``` 72 | 73 | 74 | /* Types will be inferred for these: */ 75 | bro, i = 0. 76 | bro, str = "string". 77 | bro, aBoolVal = true. 78 | 79 | 80 | ``` 81 | 82 | ### Scope of variables 83 | 84 | The rules of scope in samosa are similar to most other programming languages. 85 | Any variable defined outside a function is in the global scope, and can be accessed from anywhere in the program. 86 | Variables declared inside a block are accessible only inside it. 87 | 88 | Note that when you try to use a variable (or an identifier), the variable closest in scope will be used. 89 | 90 | For example, in the following case: 91 | 92 | ``` 93 | 94 | bro, i: int = 4. 95 | 96 | let sample() { 97 | bro, i: int = 9. 98 | (i) -> putout. 99 | } 100 | 101 | () -> sample. 102 | 103 | 104 | ``` 105 | 106 | the output would be `9` and not `4`. 107 | 108 | ## Expressions 109 | 110 | Expressions in samosa work in pretty much the same way as in Java/C++ or many other languages. 111 | 112 | Integer literals allow digits from 0 to 9\. Hex or octal number systems are not yet supported. If your result is a floating point number, it will be converted to an int by taking off the part after the decimal point. 113 | 114 | String literals start and end with double quotes. You can use the `+` operator for string concatenation. 115 | 116 | Some example expressions: 117 | 118 | ``` 119 | 120 | 121 | bro, str = "string" + "literal". 122 | 123 | bro, anIntVal = 3 + 4 / 4. 124 | 125 | /* Boolean expressions: */ 126 | bro, aBoolVal = true or false. 127 | bro, anotherBoolVal = anIntVal > 10. 128 | bro, someBoolVal = anIntVal == 10 and anotherBoolVal. 129 | bro, boolval = anIntVal != 100. 130 | 131 | 132 | ``` 133 | 134 | For boolean expressions, any of `true`, `True`, `yes`, `TRUE` can be used for a truthy value. 135 | For a falsy value, any of `false`, `False`, `nope`, `FALSE` can be used. In boolean expressions: 136 | 137 | * `||` or the keyword `or` stands for a **logical OR (not short-circuited)** 138 | * `&&` or the keyword`and` stands for a **logical AND (not short-circuited)** 139 | * `||!` or the keyword`strictor` stands for a **logical XOR** 140 | * `!!` or the keyword`not` stands for a **logical NOT** 141 | 142 | ## Conditional statements 143 | 144 | Samosa supports `if` statements (and if-else if-else ladders). The syntax for `if` statements in samosa is similar to that found in many other languages. 145 | An example: 146 | 147 | ``` 148 | 149 | bro, i = 9. 150 | 151 | if (i == 9) { 152 | ("i is 9") -> putout. 153 | } 154 | else if (i == 10) { 155 | ("i is 10") -> putout. 156 | } 157 | else if (i == 11) { 158 | ("i is 11") -> putout. 159 | } 160 | else { 161 | ("I dunno, I just like samosa.") -> putout. 162 | } 163 | 164 | 165 | ``` 166 | 167 | **Disclaimer: The example above is just for demonstration purposes. Please do not use such lame conditional statements. Thanks.** 168 | 169 | ## Loops 170 | 171 | Samosa currently supports only one kind of loops: `while` loops. It works in a similar way as in other languages: 172 | The following example prints the numbers 3, 2, 1 sequentially on three lines. 173 | 174 | ``` 175 | 176 | 177 | bro, i: int = 3. 178 | 179 | while (i > 0) { 180 | (i) -> putout. 181 | i = i - 1. 182 | } 183 | 184 | 185 | ``` 186 | 187 | The equivalent of the `break` keyword in other programming languages (that exits from the innermost loop it is found in) in samosa is `yamete_kudasai`. 188 | 189 | And the equivalent of the `continue` keyword is `thanku_next`. 190 | 191 | For example: 192 | 193 | ``` 194 | 195 | bro, i: int = 3. 196 | 197 | while (i > 0) { 198 | (i) -> putout. 199 | bro, j: int = () -> putinInt. 200 | 201 | if (j == 9) { 202 | yamete_kudasai. 203 | } 204 | } 205 | 206 | ``` 207 | 208 | Other kinds of loops will also be added in subsequent releases. 209 | 210 | ## Functions 211 | 212 | Yep, samosa also supports functions! 213 | 214 | (_Samosa does not yet support first-class functions though, but support for the same is planned._) 215 | 216 | ### Defining a function 217 | 218 | A function in samosa is defined using the keyword `let`. A function may declare some formal parameters, and can either return no value or return a value of a supported type (varargs are not yet supported). Some examples: 219 | 220 | ``` 221 | 222 | 223 | let function1(var1: int, var2: string): void { 224 | /* do something here */ 225 | } 226 | 227 | let function2(var1: int): int { 228 | return var1 + 3. 229 | } 230 | 231 | /* If your function returns nothing, you need not specify a return type. */ 232 | let function3(var1: int) { 233 | /* do something */ 234 | } 235 | 236 | 237 | ``` 238 | 239 | ### Calling a function 240 | 241 | A function can be called as a standalone statement or within an expression, like in many languages. The syntax for the same is: `() -> `, where `` is the name of the function to be called and `` is the list of passed arguments to the function, separated by commas. 242 | 243 | **Note: This syntax is will probably be changed as it sometimes causes readability issues.** 244 | 245 | An extended example of the program above would demonstrate this: 246 | 247 | ``` 248 | 249 | 250 | let function1(var1: int, var2: string): void { 251 | /* do something here */ 252 | } 253 | 254 | let function2(var1: int): int { 255 | return var1 + 3. 256 | } 257 | 258 | let function3(var1: int) { 259 | /* do something */ 260 | } 261 | 262 | (10) -> function3. 263 | 264 | bro, m = 7. 265 | bro, i: int = 3 + (5 + m) -> function2. 266 | 267 | 268 | ``` 269 | 270 | **Note: To call a function, it must be defined before the point where it is being called. So, the following program will not work:** 271 | 272 | ``` 273 | 274 | 275 | let function1(var1: int, var2: string): void { 276 | /* do something here */ 277 | } 278 | 279 | let function2(var1: int): int { 280 | return var1 + 3. 281 | } 282 | 283 | bro, m = 7. 284 | 285 | /* The next line works as function2 is defined earlier. */ 286 | bro, i: int = 3 + (5 + m) -> function2. 287 | 288 | /* The next line does not work as function3 is defined later. */ 289 | (10) -> function3. 290 | 291 | let function3(var1: int) { 292 | /* do something */ 293 | } 294 | 295 | 296 | ``` 297 | 298 | Recursion is supported, but the compiler does not currently perform tail-call optimization (support is planned for later releases). Function overloading is not currently supported for user defined functions (but is supported for builtin functions). 299 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /src/test/java/com/sachett/samosa/TestPositiveCompileRun.java: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa; 2 | 3 | import com.sachett.samosa.samosac.compiler.CompilerKt; 4 | import org.apache.commons.io.FileUtils; 5 | import org.junit.jupiter.api.BeforeAll; 6 | import org.junit.jupiter.api.DynamicTest; 7 | import org.junit.jupiter.api.TestFactory; 8 | 9 | import java.io.File; 10 | import java.io.PrintStream; 11 | import java.nio.charset.StandardCharsets; 12 | import java.util.Arrays; 13 | import java.util.Objects; 14 | import java.util.concurrent.TimeUnit; 15 | import java.util.stream.Stream; 16 | 17 | import static com.github.stefanbirkner.systemlambda.SystemLambda.catchSystemExit; 18 | import static org.junit.jupiter.api.Assertions.*; 19 | 20 | /** 21 | * This class has tests for programs that are supposed to compile and run successfully. 22 | */ 23 | public class TestPositiveCompileRun { 24 | 25 | static final File programsDir = new File("src/test/data/positive-compile-run/test-programs"); 26 | static final File programsCompOutputDir = new File("src/test/data/positive-compile-run/test-programs-comp-outputs"); 27 | static final File programsCompErrorDir = new File("src/test/data/positive-compile-run/test-programs-comp-errors"); 28 | static final File programsRunOutputDir = new File("src/test/data/positive-compile-run/test-programs-run-outputs"); 29 | static final File programsRunErrorDir = new File("src/test/data/positive-compile-run/test-programs-run-errors"); 30 | 31 | static final File expectedRunOutputDir = new File("src/test/data/positive-compile-run/expected-run-outputs"); 32 | static final File expectedRunErrorDir = new File("src/test/data/positive-compile-run/expected-run-errors"); 33 | static final File classFileOutDir = new File("src/test/data/positive-compile-run/out"); 34 | 35 | @BeforeAll 36 | static void createDirs() { 37 | programsCompOutputDir.mkdirs(); 38 | programsCompErrorDir.mkdirs(); 39 | 40 | programsRunOutputDir.mkdirs(); 41 | programsRunErrorDir.mkdirs(); 42 | 43 | classFileOutDir.mkdirs(); 44 | } 45 | 46 | @TestFactory 47 | Stream testRunOutputsOfSourceFiles() { 48 | // Compile the test cases first 49 | File[] sourceFiles = programsDir.listFiles((dir, name) -> name.toLowerCase().endsWith(".samo")); 50 | 51 | assertNotNull(sourceFiles, "No test source files found!"); 52 | assertTrue(sourceFiles.length > 0, "No test source files found!"); 53 | 54 | return Arrays.stream(sourceFiles).parallel().map((file) -> 55 | DynamicTest.dynamicTest("Test Source File: " + file.getName(), () -> { 56 | // First compile the file 57 | // Store the error and output of the compilation process into designated directories 58 | 59 | if (!System.getProperties().containsKey("nocompile")) { 60 | File compilationOutputFile = new File( 61 | programsCompOutputDir.getPath() + File.separator + file.getName() + ".compile.log" 62 | ); 63 | File compilationErrorFile = new File( 64 | programsCompErrorDir.getPath() + File.separator + file.getName() + ".compile.err" 65 | ); 66 | 67 | compilationOutputFile.createNewFile(); 68 | compilationErrorFile.createNewFile(); 69 | 70 | PrintStream prevOut = System.out; 71 | PrintStream prevErr = System.err; 72 | PrintStream redirectedOut = new PrintStream(compilationOutputFile); 73 | PrintStream redirectedErr = new PrintStream(compilationErrorFile); 74 | System.setOut(redirectedOut); 75 | System.setErr(redirectedErr); 76 | 77 | // Do compilation 78 | int compileStatusCode = catchSystemExit(() -> { 79 | CompilerKt.main(new String[]{ file.getAbsolutePath(), "-o" + classFileOutDir.getAbsolutePath() }); 80 | System.exit(0); // test will fail otherwise 81 | }); 82 | 83 | // Restore streams 84 | System.setErr(prevErr); 85 | System.setOut(prevOut); 86 | 87 | // Check if there was any compilation error 88 | assertTrue(FileUtils.readFileToString(compilationErrorFile, StandardCharsets.UTF_8).strip().equals("") 89 | && (compileStatusCode != -1), 90 | "Compilation failed for test source file: \n\t" + file.getAbsolutePath() 91 | + "." + "\n" + "See " + compilationErrorFile.getAbsolutePath() + " for details." 92 | ); 93 | } 94 | 95 | // Then run the file 96 | // Store the error and output of the run process too 97 | 98 | if (!System.getProperties().containsKey("norun")) { 99 | File runOutputFile = new File( 100 | programsRunOutputDir.getPath() + File.separator + file.getName() + ".run.log" 101 | ); 102 | File runErrorFile = new File( 103 | programsRunErrorDir.getPath() + File.separator + file.getName() + ".run.err" 104 | ); 105 | File expectedRunOutputFile = new File( 106 | expectedRunOutputDir.getPath() + File.separator + file.getName() + ".run.log.should" 107 | ); 108 | File expectedRunErrorFile = new File( 109 | expectedRunErrorDir.getPath() + File.separator + file.getName() + ".run.err.should" 110 | ); 111 | 112 | runOutputFile.createNewFile(); 113 | runErrorFile.createNewFile(); 114 | 115 | String classFileName = getClassFileNameFromFileName(file.getName()); 116 | ProcessBuilder runProcess = new ProcessBuilder("java", classFileName); 117 | classFileOutDir.mkdirs(); 118 | runProcess.directory(classFileOutDir); 119 | runProcess.redirectOutput(runOutputFile); 120 | runProcess.redirectError(runErrorFile); 121 | 122 | // Check for execution timeout 123 | assertTrue(runProcess.start().waitFor(200, TimeUnit.SECONDS), 124 | "Execution timed out for test source file: \n\t" + file.getAbsolutePath() 125 | ); 126 | 127 | // Check if there was any run error 128 | assertTrue( 129 | FileUtils.contentEqualsIgnoreEOL(runErrorFile, expectedRunErrorFile, null), 130 | "Execution failed unexpectedly for test source file: \n\t" + file.getAbsolutePath() 131 | + "." + "\n" + "See " + runErrorFile.getAbsolutePath() + " for details." 132 | ); 133 | 134 | // Check if there is any difference in output (expected vs. result) 135 | assertTrue( 136 | FileUtils.contentEqualsIgnoreEOL(runOutputFile, expectedRunOutputFile, null), 137 | "Unexpected output for test source file: \n\t" + file.getAbsolutePath() 138 | + "." + "\n" + "Expected output is in file: \n\t" + expectedRunOutputFile.getAbsolutePath() 139 | + "\n" + "Output received is written to file: \n\t" + runOutputFile.getAbsolutePath() 140 | ); 141 | } 142 | }) 143 | ); 144 | } 145 | 146 | // -------------- Utils ------------------ 147 | 148 | public static String getClassFileNameFromFileName(String fileName) { 149 | String[] fileNameParts = fileName.split("\\."); 150 | StringBuilder genClassNameBuilder = new StringBuilder(); 151 | 152 | for (String fileNamePart : fileNameParts) { 153 | // Capitalize the first letter of each part of the filename 154 | 155 | if (Objects.equals(fileNamePart, "")) { 156 | continue; 157 | } 158 | 159 | if (fileNamePart.contains("/")) { 160 | String[] dirParts = fileNamePart.split("[/\\\\]"); 161 | fileNamePart = dirParts.length > 0 ? dirParts[dirParts.length - 1] : fileNamePart; 162 | } 163 | 164 | String modFileNamePart = fileNamePart.substring(0, 1).toUpperCase() + fileNamePart.substring(1); 165 | genClassNameBuilder.append(modFileNamePart); 166 | } 167 | 168 | // if the class name has a number, then put it to the end of Samo 169 | // remove all other than numbers: 170 | var tempClassNameNumbers = genClassNameBuilder 171 | .toString().replaceAll("[^\\d]", ""); 172 | var tempClassNameNoNumbers = genClassNameBuilder.toString().replaceAll("[\\d]", ""); 173 | 174 | // in the last step, replace any other non-alphanumeric symbols in the name 175 | return (tempClassNameNoNumbers + tempClassNameNumbers).replaceAll("[^0-9a-zA-Z]", ""); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/main/java/com/sachett/samosa/samosac/codegen/compoundstmt/WhileStmtCodegen.java: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.codegen.compoundstmt; 2 | 3 | import com.sachett.samosa.parser.SamosaParser; 4 | import com.sachett.samosa.samosac.codegen.ClassFileGenerator; 5 | import com.sachett.samosa.samosac.codegen.expressions.BooleanExprCodegen; 6 | import com.sachett.samosa.samosac.codegen.function.FunctionGenerationContext; 7 | import com.sachett.samosa.samosac.codegen.utils.delegation.CodegenDelegatable; 8 | import com.sachett.samosa.samosac.codegen.utils.delegation.CodegenDelegatedMethod; 9 | import com.sachett.samosa.samosac.symbol.symboltable.SymbolTable; 10 | import org.antlr.v4.runtime.tree.TerminalNode; 11 | import org.objectweb.asm.Label; 12 | import org.objectweb.asm.Opcodes; 13 | 14 | import java.util.HashSet; 15 | import java.util.List; 16 | import java.util.Objects; 17 | 18 | public class WhileStmtCodegen extends CodegenDelegatable implements IControlNodeCodegen { 19 | private FunctionGenerationContext functionGenerationContext; 20 | private final ControlNodeCodegenType controlNodeCodegenType = ControlNodeCodegenType.WHILE; 21 | 22 | public ControlNodeCodegenType getControlNodeCodegenType() { 23 | return controlNodeCodegenType; 24 | } 25 | 26 | // TODO: refactor these out to an abstract class or interface 27 | private String className; 28 | private String packageName; 29 | private SymbolTable symbolTable; 30 | 31 | private boolean generatingWhileBlock = false; 32 | private Label whileLoopStartLabel = null; 33 | private Label whileLoopExitLabel = null; 34 | 35 | private CodegenDelegatable delegatedParentCodegen; 36 | 37 | public WhileStmtCodegen( 38 | CodegenDelegatable delegatedParentCodegen, 39 | FunctionGenerationContext functionGenerationContext, 40 | SymbolTable symbolTable, 41 | String className, 42 | String packageName 43 | ) { 44 | super(delegatedParentCodegen.getSharedDelegationManager()); 45 | 46 | /** 47 | * Register the stuff that this generator generates with the shared delegation manager. 48 | */ 49 | HashSet delegatedMethodHashSet = new HashSet<>(List.of( 50 | CodegenDelegatedMethod.BLOCK, 51 | CodegenDelegatedMethod.BREAK, 52 | CodegenDelegatedMethod.CONTINUE 53 | )); 54 | this.registerDelegatedMethods(delegatedMethodHashSet); 55 | 56 | this.functionGenerationContext = functionGenerationContext; 57 | this.delegatedParentCodegen = delegatedParentCodegen; 58 | this.className = className; 59 | this.packageName = packageName; 60 | this.symbolTable = symbolTable; 61 | } 62 | 63 | private boolean isGlobalBlock() { 64 | return delegatedParentCodegen instanceof ClassFileGenerator; 65 | } 66 | 67 | // Delegate methods: 68 | @Override 69 | public Void visitBooleanExprAssign(SamosaParser.BooleanExprAssignContext ctx) { 70 | return delegatedParentCodegen.visitBooleanExprAssign(ctx); 71 | } 72 | 73 | @Override 74 | public Void visitDeclStmt(SamosaParser.DeclStmtContext ctx) { 75 | return delegatedParentCodegen.visitDeclStmt(ctx); 76 | } 77 | 78 | @Override 79 | public Void visitBooleanDeclAssignStmt(SamosaParser.BooleanDeclAssignStmtContext ctx) { 80 | return delegatedParentCodegen.visitBooleanDeclAssignStmt(ctx); 81 | } 82 | 83 | @Override 84 | public Void visitNormalDeclAssignStmt(SamosaParser.NormalDeclAssignStmtContext ctx) { 85 | return delegatedParentCodegen.visitNormalDeclAssignStmt(ctx); 86 | } 87 | 88 | @Override 89 | public Void visitTypeInferredDeclAssignStmt(SamosaParser.TypeInferredDeclAssignStmtContext ctx) { 90 | return delegatedParentCodegen.visitTypeInferredDeclAssignStmt(ctx); 91 | } 92 | 93 | @Override 94 | public Void visitTypeInferredBooleanDeclAssignStmt(SamosaParser.TypeInferredBooleanDeclAssignStmtContext ctx) { 95 | return delegatedParentCodegen.visitTypeInferredBooleanDeclAssignStmt(ctx); 96 | } 97 | 98 | @Override 99 | public Void visitBlock(SamosaParser.BlockContext ctx) { 100 | return delegatedParentCodegen.visitBlock(ctx); 101 | } 102 | 103 | @Override 104 | public Void visitIfStmt(SamosaParser.IfStmtContext ctx) { 105 | return delegatedParentCodegen.visitIfStmt(ctx); 106 | } 107 | 108 | @Override 109 | public Void visitWhileStmt(SamosaParser.WhileStmtContext ctx) { 110 | return delegatedParentCodegen.visitWhileStmt(ctx); 111 | } 112 | 113 | @Override 114 | public Void visitUncertainCompoundStmtSingle(SamosaParser.UncertainCompoundStmtSingleContext ctx) { 115 | return delegatedParentCodegen.visitUncertainCompoundStmtSingle(ctx); 116 | } 117 | 118 | @Override 119 | public Void visitUncertainCompoundStmtMultiple(SamosaParser.UncertainCompoundStmtMultipleContext ctx) { 120 | return delegatedParentCodegen.visitUncertainCompoundStmtMultiple(ctx); 121 | } 122 | 123 | @Override 124 | public Void visitUncertainStatementSingle(SamosaParser.UncertainStatementSingleContext ctx) { 125 | return delegatedParentCodegen.visitUncertainStatementSingle(ctx); 126 | } 127 | 128 | @Override 129 | public Void visitUncertainStatementMultiple(SamosaParser.UncertainStatementMultipleContext ctx) { 130 | return delegatedParentCodegen.visitUncertainStatementMultiple(ctx); 131 | } 132 | 133 | @Override 134 | public Void visitFunctionCallWithArgs(SamosaParser.FunctionCallWithArgsContext ctx) { 135 | return delegatedParentCodegen.visitFunctionCallWithArgs(ctx); 136 | } 137 | 138 | @Override 139 | public Void visitFunctionCallNoArgs(SamosaParser.FunctionCallNoArgsContext ctx) { 140 | return delegatedParentCodegen.visitFunctionCallNoArgs(ctx); 141 | } 142 | 143 | public void setDelegatedParentCodegen(CodegenDelegatable delegatedParentCodegen) { 144 | this.delegatedParentCodegen = delegatedParentCodegen; 145 | } 146 | 147 | public void setFunctionCodegen(FunctionGenerationContext functionGenerationContext) { 148 | this.functionGenerationContext = functionGenerationContext; 149 | } 150 | 151 | // Methods handled by this class (not delegated to parent): 152 | 153 | @Override 154 | public Void visitBreakControlStmt(SamosaParser.BreakControlStmtContext ctx) { 155 | if (generatingWhileBlock && whileLoopExitLabel != null && whileLoopStartLabel != null) { 156 | functionGenerationContext.getMv().visitJumpInsn(Opcodes.GOTO, whileLoopExitLabel); 157 | } 158 | undelegateSelf(); 159 | return null; 160 | } 161 | 162 | @Override 163 | public Void visitContinueControlStmt(SamosaParser.ContinueControlStmtContext ctx) { 164 | if (generatingWhileBlock && whileLoopExitLabel != null && whileLoopStartLabel != null) { 165 | functionGenerationContext.getMv().visitJumpInsn(Opcodes.GOTO, whileLoopStartLabel); 166 | } 167 | undelegateSelf(); 168 | return null; 169 | } 170 | 171 | @Override 172 | public Void visitExprAssign(SamosaParser.ExprAssignContext ctx) { 173 | return delegatedParentCodegen.visitExprAssign(ctx); 174 | } 175 | 176 | public void generateWhileStmt(SamosaParser.WhileStmtContext ctx) { 177 | Label loopLabel = new Label(); 178 | Label exitLoopLabel = new Label(); 179 | this.whileLoopStartLabel = loopLabel; 180 | this.whileLoopExitLabel = exitLoopLabel; 181 | 182 | var currentStackFrame = functionGenerationContext.getCurrentFrameStackInfo(); 183 | 184 | functionGenerationContext.getMv().visitLabel(loopLabel); 185 | functionGenerationContext.getMv().visitFrame(Opcodes.F_NEW, 186 | currentStackFrame.numLocals, currentStackFrame.locals, 187 | currentStackFrame.numStack, currentStackFrame.stack 188 | ); 189 | 190 | // check condition 191 | BooleanExprCodegen booleanExprCodegen = new BooleanExprCodegen( 192 | ctx.booleanExpr(), 193 | symbolTable, 194 | functionGenerationContext, 195 | className, 196 | packageName 197 | ); 198 | booleanExprCodegen.doCodegen(); 199 | 200 | // if condition is false, exit loop 201 | currentStackFrame = functionGenerationContext.getCurrentFrameStackInfo(); 202 | functionGenerationContext.getMv().visitJumpInsn(Opcodes.IFEQ, exitLoopLabel); 203 | 204 | this.generatingWhileBlock = true; 205 | undelegateSelf(); 206 | visit(ctx.block()); 207 | 208 | this.generatingWhileBlock = false; 209 | this.whileLoopStartLabel = null; 210 | this.whileLoopExitLabel = null; 211 | 212 | // start next iteration 213 | functionGenerationContext.getMv().visitJumpInsn(Opcodes.GOTO, loopLabel); 214 | functionGenerationContext.getMv().visitLabel(exitLoopLabel); 215 | functionGenerationContext.getMv().visitFrame(Opcodes.F_NEW, 216 | currentStackFrame.numLocals, currentStackFrame.locals, 217 | currentStackFrame.numStack, currentStackFrame.stack 218 | ); 219 | } 220 | 221 | @Override 222 | public Void visitTerminal(TerminalNode node) { 223 | if (Objects.equals(node.getSymbol().getText(), "}")) { 224 | undelegateSelf(); 225 | } 226 | return super.visitTerminal(node); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/staticchecker/evaluators/BoolExpressionEvaluator.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.staticchecker.evaluators 2 | 3 | import com.sachett.samosa.logging.err 4 | import com.sachett.samosa.parser.SamosaBaseVisitor 5 | import com.sachett.samosa.parser.SamosaParser 6 | import com.sachett.samosa.samosac.staticchecker.ExpressionTypeDetector 7 | import com.sachett.samosa.samosac.symbol.SymbolType 8 | import com.sachett.samosa.samosac.symbol.symboltable.SymbolTable 9 | 10 | class BoolExpressionEvaluator( 11 | private var exprContext: SamosaParser.BooleanExprContext, 12 | private val symbolTable: SymbolTable 13 | ) : SamosaBaseVisitor(), IStaticExprEvaluator { 14 | private var isExprStaticEvaluable = true 15 | private var isExprStaticEvaluableCalculated = false 16 | private var evaluationResult: Boolean = false 17 | 18 | /** 19 | * Checks if the expression can be evaluated at compile time. 20 | */ 21 | override fun checkStaticEvaluable(): Boolean { 22 | if (isExprStaticEvaluableCalculated) { 23 | return isExprStaticEvaluable 24 | } 25 | 26 | evaluationResult = visit(exprContext) 27 | return isExprStaticEvaluable 28 | } 29 | 30 | /** 31 | * Evaluates and returns the integer value. 32 | * Returns default value of the symbol type if not possible to evaluate at compile time. 33 | */ 34 | override fun evaluate(): Boolean { 35 | if (!checkStaticEvaluable()) { 36 | return SymbolType.BOOL.defaultValue!! as Boolean 37 | } 38 | 39 | return evaluationResult 40 | } 41 | 42 | fun setBooleanExprContext(booleanExprContext: SamosaParser.BooleanExprContext) { 43 | this.exprContext = booleanExprContext 44 | isExprStaticEvaluableCalculated = false 45 | isExprStaticEvaluable = true 46 | evaluationResult = false 47 | } 48 | 49 | override fun visitBooleanExprIdentifier(ctx: SamosaParser.BooleanExprIdentifierContext?): Boolean { 50 | isExprStaticEvaluable = false 51 | return false 52 | } 53 | 54 | override fun visitBooleanFunctionCall(ctx: SamosaParser.BooleanFunctionCallContext?): Boolean { 55 | isExprStaticEvaluable = false 56 | return false 57 | } 58 | 59 | override fun visitBooleanExprRelOp(ctx: SamosaParser.BooleanExprRelOpContext?): Boolean { 60 | var comparisonResult = false 61 | 62 | val lhs = ctx!!.expr(0) 63 | val rhs = ctx.expr(1) 64 | 65 | val typeDetector = ExpressionTypeDetector(symbolTable) 66 | val lhsType = typeDetector.getType(lhs) 67 | val rhsType = typeDetector.getType(rhs) 68 | 69 | // check for incompatible types 70 | if ((!lhsType.first || !rhsType.first) 71 | || (lhsType.second != rhsType.second) 72 | || !lhsType.second.canBeUsedWithRelOp 73 | ) { 74 | return false 75 | } 76 | 77 | val theRelOp = ctx.relOp() 78 | 79 | // RelOp expressions have two operands 80 | var leftVal: Any? = null 81 | var rightVal: Any? = null 82 | 83 | // Since we only have int expressions that can be compared using relops right now 84 | if (lhsType.second == SymbolType.INT && rhsType.second == SymbolType.INT) { 85 | val intExpressionEvaluator = IntExpressionEvaluator(ctx.expr(0)) 86 | if (!intExpressionEvaluator.checkStaticEvaluable()) { 87 | isExprStaticEvaluable = false 88 | return false 89 | } 90 | leftVal = intExpressionEvaluator.evaluate() 91 | 92 | intExpressionEvaluator.setExprContext(ctx.expr(1)) 93 | if (!intExpressionEvaluator.checkStaticEvaluable()) { 94 | isExprStaticEvaluable = false 95 | return false 96 | } 97 | rightVal = intExpressionEvaluator.evaluate() 98 | } 99 | 100 | if (theRelOp.GT() != null) { 101 | if (lhsType.second == SymbolType.INT && rhsType.second == SymbolType.INT) { 102 | comparisonResult = (leftVal!! as Int) > (rightVal!! as Int) 103 | } 104 | } else if (theRelOp.GTEQ() != null) { 105 | if (lhsType.second == SymbolType.INT && rhsType.second == SymbolType.INT) { 106 | comparisonResult = (leftVal!! as Int) >= (rightVal!! as Int) 107 | } 108 | } else if (theRelOp.LT() != null) { 109 | if (lhsType.second == SymbolType.INT && rhsType.second == SymbolType.INT) { 110 | comparisonResult = (leftVal!! as Int) < (rightVal!! as Int) 111 | } 112 | } else if (theRelOp.LTEQ() != null) { 113 | if (lhsType.second == SymbolType.INT && rhsType.second == SymbolType.INT) { 114 | comparisonResult = (leftVal!! as Int) <= (rightVal!! as Int) 115 | } 116 | } else { 117 | err("[Error] Unknown relational operator.") 118 | } 119 | 120 | return comparisonResult 121 | } 122 | 123 | override fun visitBooleanExprOr(ctx: SamosaParser.BooleanExprOrContext?): Boolean { 124 | val left = visit(ctx!!.booleanExpr(0)) // left operand 125 | val right = visit(ctx.booleanExpr(1)) // right operand 126 | 127 | return left || right 128 | } 129 | 130 | override fun visitBooleanExprCompOp(ctx: SamosaParser.BooleanExprCompOpContext?): Boolean { 131 | var comparisonResult = false 132 | 133 | val lhs = ctx!!.expr(0) 134 | val rhs = ctx.expr(1) 135 | 136 | val typeDetector = ExpressionTypeDetector(symbolTable) 137 | val lhsType = typeDetector.getType(lhs) 138 | val rhsType = typeDetector.getType(rhs) 139 | 140 | // check for incompatible types 141 | if ((!lhsType.first || !rhsType.first) 142 | || (lhsType.second != rhsType.second) 143 | || !lhsType.second.canBeUsedWithRelOp 144 | ) { 145 | return false 146 | } 147 | 148 | val theCompOp = ctx.compOp() 149 | 150 | // RelOp expressions have two operands 151 | var leftVal: Any? = null 152 | var rightVal: Any? = null 153 | 154 | // Currently, we can only compare strings and ints using == and != 155 | if (lhsType.second == SymbolType.INT && rhsType.second == SymbolType.INT) { 156 | val intExpressionEvaluator = IntExpressionEvaluator(ctx.expr(0)) 157 | if (!intExpressionEvaluator.checkStaticEvaluable()) { 158 | isExprStaticEvaluable = false 159 | return false 160 | } 161 | leftVal = intExpressionEvaluator.evaluate() 162 | 163 | intExpressionEvaluator.setExprContext(ctx.expr(1)) 164 | if (!intExpressionEvaluator.checkStaticEvaluable()) { 165 | isExprStaticEvaluable = false 166 | return false 167 | } 168 | rightVal = intExpressionEvaluator.evaluate() 169 | } else if (lhsType.second == SymbolType.STRING && rhsType.second == SymbolType.STRING) { 170 | val stringExpressionEvaluator = StringExpressionEvaluator(ctx.expr(0)) 171 | if (!stringExpressionEvaluator.checkStaticEvaluable()) { 172 | isExprStaticEvaluable = false 173 | return false 174 | } 175 | leftVal = stringExpressionEvaluator.evaluate() 176 | 177 | stringExpressionEvaluator.setExprContext(ctx.expr(1)) 178 | if (!stringExpressionEvaluator.checkStaticEvaluable()) { 179 | isExprStaticEvaluable = false 180 | return false 181 | } 182 | rightVal = stringExpressionEvaluator.evaluate() 183 | } 184 | 185 | if (theCompOp.COMP() != null) { 186 | if (lhsType.second == SymbolType.INT && rhsType.second == SymbolType.INT) { 187 | comparisonResult = (leftVal!! as Int) == (rightVal!! as Int) 188 | } else if (lhsType.second == SymbolType.STRING && rhsType.second == SymbolType.STRING) { 189 | comparisonResult = (leftVal!! as String) == (rightVal!! as String) 190 | } 191 | } else if (theCompOp.COMPNOTEQ() != null) { 192 | if (lhsType.second == SymbolType.INT && rhsType.second == SymbolType.INT) { 193 | comparisonResult = (leftVal!! as Int) != (rightVal!! as Int) 194 | } else if (lhsType.second == SymbolType.STRING && rhsType.second == SymbolType.STRING) { 195 | comparisonResult = (leftVal!! as String) != (rightVal!! as String) 196 | } 197 | } else { 198 | err("[Error] Unknown comparison operator.") 199 | } 200 | 201 | return comparisonResult 202 | } 203 | 204 | override fun visitBooleanExprParen(ctx: SamosaParser.BooleanExprParenContext?): Boolean { 205 | return visit(ctx!!.booleanExpr()) 206 | } 207 | 208 | override fun visitBooleanTrue(ctx: SamosaParser.BooleanTrueContext?): Boolean { 209 | return true 210 | } 211 | 212 | override fun visitBooleanFalse(ctx: SamosaParser.BooleanFalseContext?): Boolean { 213 | return false 214 | } 215 | 216 | override fun visitBooleanExprNot(ctx: SamosaParser.BooleanExprNotContext?): Boolean { 217 | return !(visit(ctx!!.booleanExpr())) 218 | } 219 | 220 | override fun visitBooleanExprXor(ctx: SamosaParser.BooleanExprXorContext?): Boolean { 221 | val left = visit(ctx!!.booleanExpr(0)) // left operand 222 | val right = visit(ctx.booleanExpr(1)) // right operand 223 | 224 | return ((left || right) && !(left && right)) 225 | } 226 | 227 | override fun visitBooleanExprAnd(ctx: SamosaParser.BooleanExprAndContext?): Boolean { 228 | val left = visit(ctx!!.booleanExpr(0)) // left operand 229 | val right = visit(ctx.booleanExpr(1)) // right operand 230 | 231 | return left && right 232 | } 233 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/sachett/samosa/samosac/staticchecker/BoolExpressionChecker.kt: -------------------------------------------------------------------------------- 1 | package com.sachett.samosa.samosac.staticchecker 2 | 3 | import com.sachett.samosa.logging.Severity 4 | import com.sachett.samosa.logging.err 5 | import com.sachett.samosa.logging.fmterror 6 | import com.sachett.samosa.parser.SamosaParser 7 | import com.sachett.samosa.samosac.symbol.SymbolType 8 | import com.sachett.samosa.samosac.symbol.symboltable.SymbolTable 9 | 10 | /** 11 | * Provides type checking methods for boolean expressions. 12 | */ 13 | class BoolExpressionChecker(symbolTable: SymbolTable) : ExpressionChecker(symbolTable) { 14 | 15 | /** 16 | * Type checks the provided boolean expression. 17 | * This checkExpr is an overload (and not an override), and should be used instead 18 | * of the superclass ExpressionChecker's checkExpr() for checking 19 | * boolean expressions. 20 | * @param ctx The BooleanExprContext to check. 21 | * @return true if all OK else false. 22 | */ 23 | fun checkExpr(ctx: SamosaParser.BooleanExprContext): Boolean = visit(ctx) 24 | 25 | /* ----------------- Visitor methods -------------------- */ 26 | 27 | override fun checkUnaryOp(ctx: T): Boolean { 28 | // use java reflection API 29 | // this would be: visit(ctx?.booleanExpr()) 30 | val ctxClass = ctx!!::class.java 31 | val booleanExpr = ctxClass.getDeclaredMethod("booleanExpr") 32 | val booleanExprRetObj = booleanExpr.invoke(ctx) as SamosaParser.BooleanExprContext 33 | 34 | return visit(booleanExprRetObj) 35 | } 36 | 37 | override fun checkBinaryOp(ctx: T): Boolean { 38 | // use java reflection API 39 | // this would be: visit(ctx?.booleanExpr(0)) && visit(ctx?.booleanExpr(1)) 40 | val ctxClass = ctx!!::class.java 41 | val booleanExpr = ctxClass.getDeclaredMethod("booleanExpr", Int::class.java) 42 | val booleanExprRetObjLHS = booleanExpr.invoke(ctx, 0) as SamosaParser.BooleanExprContext 43 | val booleanExprRetObjRHS = booleanExpr.invoke(ctx, 1) as SamosaParser.BooleanExprContext 44 | 45 | return (visit(booleanExprRetObjLHS) && visit(booleanExprRetObjRHS)) 46 | } 47 | 48 | override fun visitBooleanExprNot(ctx: SamosaParser.BooleanExprNotContext?): Boolean = checkUnaryOp(ctx!!) 49 | 50 | override fun visitBooleanExprOr(ctx: SamosaParser.BooleanExprOrContext?): Boolean = checkBinaryOp(ctx!!) 51 | 52 | override fun visitBooleanExprAnd(ctx: SamosaParser.BooleanExprAndContext?): Boolean = checkBinaryOp(ctx!!) 53 | 54 | override fun visitBooleanExprXor(ctx: SamosaParser.BooleanExprXorContext?): Boolean = checkBinaryOp(ctx!!) 55 | 56 | override fun visitBooleanExprRelOp(ctx: SamosaParser.BooleanExprRelOpContext?): Boolean { 57 | val lhs = ctx!!.expr(0) 58 | val rhs = ctx.expr(1) 59 | 60 | val typeDetector = ExpressionTypeDetector(symbolTable) 61 | val lhsType = typeDetector.getType(lhs) 62 | val rhsType = typeDetector.getType(rhs) 63 | 64 | // utility part to retrieve the line number and relop representation as string 65 | // for showing errors if required 66 | val lineNum: Int 67 | val op: String 68 | val theRelOp = ctx.relOp() 69 | 70 | if (theRelOp.GT() != null) { 71 | val gtOp = theRelOp.GT()!!.symbol!! 72 | lineNum = gtOp.line 73 | op = gtOp.text 74 | } else if (theRelOp.GTEQ() != null) { 75 | val gtEqOp = theRelOp.GTEQ()!!.symbol!! 76 | lineNum = gtEqOp.line 77 | op = gtEqOp.text 78 | } else if (theRelOp.LT() != null) { 79 | val ltOp = theRelOp.LT()!!.symbol!! 80 | lineNum = ltOp.line 81 | op = ltOp.text 82 | } else if (theRelOp.LTEQ() != null) { 83 | val ltEqOp = theRelOp.LTEQ()!!.symbol!! 84 | lineNum = ltEqOp.line 85 | op = ltEqOp.text 86 | } else { 87 | err("[Error] Unknown relational operator.") 88 | } 89 | 90 | // check if the LHS and RHS individually have homogenous types within 91 | if (!lhsType.first || !rhsType.first) { 92 | fmterror( 93 | "The expression on ${if (!lhsType.first) "LHS" else "RHS"} " + 94 | "${if (!rhsType.first) "and RHS" else ""} " + 95 | "has mixed types in it and cannot be evaluated.", lineNum, Severity.ERROR 96 | ) 97 | return false 98 | } 99 | 100 | // note that the ExpressionTypeDetector already checks if there are function calls that return 101 | // no value within the expression(s) 102 | 103 | // now check if the expression return types on LHS and RHS match 104 | if (lhsType.second != rhsType.second) { 105 | fmterror( 106 | "The expression return types on LHS and RHS do not match. " + 107 | "Cannot compare ${lhsType.second.asString} on LHS with ${rhsType.second.asString} on RHS.", 108 | lineNum, Severity.ERROR 109 | ) 110 | return false 111 | } 112 | 113 | // check if relational operators can be used with the given types 114 | if (!lhsType.second.canBeUsedWithRelOp) { 115 | fmterror( 116 | "The operator '${op}' cannot be used for the type returned by these expressions " + 117 | "(cannot compare two ${lhsType.second.asString} values).", 118 | lineNum, 119 | Severity.ERROR 120 | ) 121 | return false 122 | } 123 | 124 | // if all checks passed, return true 125 | return true 126 | } 127 | 128 | override fun visitBooleanExprCompOp(ctx: SamosaParser.BooleanExprCompOpContext?): Boolean { 129 | val lhs = ctx!!.expr(0) 130 | val rhs = ctx.expr(1) 131 | 132 | val typeDetector = ExpressionTypeDetector(symbolTable) 133 | val lhsType = typeDetector.getType(lhs) 134 | val rhsType = typeDetector.getType(rhs) 135 | 136 | // utility part to retrieve the line number and compOp representation as string 137 | // for showing errors if required 138 | val lineNum: Int 139 | val op: String 140 | val theCompOp = ctx.compOp() 141 | 142 | if (theCompOp.COMP() != null) { 143 | val gtOp = theCompOp.COMP()!!.symbol!! 144 | lineNum = gtOp.line 145 | op = gtOp.text 146 | } else if (theCompOp.COMPNOTEQ() != null) { 147 | val gtEqOp = theCompOp.COMPNOTEQ()!!.symbol!! 148 | lineNum = gtEqOp.line 149 | op = gtEqOp.text 150 | } else { 151 | err("[Error] Unknown equality test operator.") 152 | } 153 | 154 | // check if the LHS and RHS individually have homogenous types within 155 | if (!lhsType.first || !rhsType.first) { 156 | fmterror( 157 | "The expression on ${if (!lhsType.first) "LHS" else "RHS"} " + 158 | "${if (!rhsType.first) "and RHS" else ""} " + 159 | "has mixed types in it and cannot be evaluated.", lineNum, Severity.ERROR 160 | ) 161 | return false 162 | } 163 | 164 | // note that the ExpressionTypeDetector already checks if there are function calls that return 165 | // no value within the expression(s) 166 | 167 | // now check if the expression return types on LHS and RHS match 168 | if (lhsType.second != rhsType.second) { 169 | fmterror( 170 | "The expression return types on LHS and RHS do not match. " + 171 | "Cannot compare ${lhsType.second.asString} on LHS with ${rhsType.second.asString} on RHS.", 172 | lineNum, Severity.ERROR 173 | ) 174 | return false 175 | } 176 | 177 | // check if relational operators can be used with the given types 178 | if (!lhsType.second.canBeUsedWithCompOp) { 179 | fmterror( 180 | "The operator '${op}' cannot be used for the type returned by these expressions " + 181 | "(cannot compare two ${lhsType.second.asString} values).", 182 | lineNum, 183 | Severity.ERROR 184 | ) 185 | return false 186 | } 187 | 188 | // if all checks passed, return true 189 | return true 190 | } 191 | 192 | override fun visitBooleanExprParen(ctx: SamosaParser.BooleanExprParenContext?): Boolean = checkUnaryOp(ctx!!) 193 | 194 | override fun visitBooleanExprIdentifier(ctx: SamosaParser.BooleanExprIdentifierContext?): Boolean = 195 | checkIdentifierTypeInExpr(ctx!!, SymbolType.BOOL) 196 | 197 | override fun visitBooleanTrue(ctx: SamosaParser.BooleanTrueContext?): Boolean = true 198 | 199 | // since we are just checking types, it returns true: 200 | override fun visitBooleanFalse(ctx: SamosaParser.BooleanFalseContext?): Boolean = true 201 | 202 | override fun visitFunctionCallWithArgs(ctx: SamosaParser.FunctionCallWithArgsContext?): Boolean { 203 | // check if the function call returns a boolean value 204 | val returnType = FunctionCallExprChecker.getRetTypeOfFunctionCallWithArgs(ctx, symbolTable) 205 | if (returnType != SymbolType.BOOL) { 206 | fmterror( 207 | "Expected return type was ${SymbolType.BOOL.asString} but the function call returns " + 208 | "value of type ${returnType.asString}.", ctx!!.IDENTIFIER().symbol.line, Severity.ERROR 209 | ) 210 | return false 211 | } 212 | 213 | return true 214 | } 215 | 216 | override fun visitFunctionCallNoArgs(ctx: SamosaParser.FunctionCallNoArgsContext?): Boolean { 217 | val returnType = FunctionCallExprChecker.getRetTypeOfFunctionCallNoArgs(ctx, symbolTable) 218 | if (returnType != SymbolType.BOOL) { 219 | fmterror( 220 | "Expected return type was ${SymbolType.BOOL.asString} but the function call returns " + 221 | "value of type ${returnType.asString}.", ctx!!.IDENTIFIER().symbol.line, Severity.ERROR 222 | ) 223 | return false 224 | } 225 | 226 | return true 227 | } 228 | 229 | override fun visitBooleanFunctionCall(ctx: SamosaParser.BooleanFunctionCallContext?): Boolean { 230 | return visitChildren(ctx); 231 | } 232 | } --------------------------------------------------------------------------------