diff --git a/core-lib/TestSuite/LanguageTests.som b/core-lib/TestSuite/LanguageTests.som index 59b69c456..b94a713a9 100644 --- a/core-lib/TestSuite/LanguageTests.som +++ b/core-lib/TestSuite/LanguageTests.som @@ -709,4 +709,34 @@ class LanguageTests usingPlatform: platform testFramework: minitest = Value ( self assert: 42 equals: (a at: i) aNumber ] ) ) : ( TEST_CONTEXT = () ) + + public class InitializerForLocalVariables = TestContext ()( + public testSimpleInitializers = ( + | cnt ::= 0. + inc = 1. | + + assert: cnt equals: 0. + assert: inc equals: 1. + + cnt:: cnt + inc. + cnt:: cnt + inc. + + assert: cnt equals: 2. + assert: inc equals: 1. + ) + + private myself = ( ^ self ) + private ret: o = ( ^ o ) + + public testMoreComplexExpressions = ( + | zero ::= 4 - 8 + 4. + obj = myself. + o = ret: obj. | + + assert: zero equals: 0. + assert: obj is: self. + + assert: o is: self. + ) + ) : ( TEST_CONTEXT = () ) ) diff --git a/src/som/compiler/Lexer.java b/src/som/compiler/Lexer.java index d7bc3fa4a..bb93457e1 100644 --- a/src/som/compiler/Lexer.java +++ b/src/som/compiler/Lexer.java @@ -92,7 +92,7 @@ int incPtr(final int val) { } } - private final String content; + protected final String content; private boolean peekDone; private LexerState state; @@ -194,7 +194,7 @@ private Symbol doSym() { if (isDigit(nextChar())) { lexNumber(); } else { - match(Symbol.Minus); + lexOperator(); } } else if (currentChar() == '<') { state.incPtr(); @@ -213,7 +213,7 @@ private Symbol doSym() { } } else if (isOperator(currentChar())) { lexOperator(); - } else if (Character.isLetter(currentChar())) { + } else if (Character.isLetter(currentChar()) || currentChar() == '_') { state.set(Symbol.Identifier); while (isIdentifierChar(currentChar())) { state.text.append(bufchar(state.incPtr())); @@ -325,6 +325,8 @@ private void lexOperator() { match(Symbol.At); } else if (currentChar() == '%') { match(Symbol.Per); + } else if (currentChar() == '-') { + match(Symbol.Minus); } } diff --git a/src/som/compiler/MethodBuilder.java b/src/som/compiler/MethodBuilder.java index 99798dcb2..bdfce921d 100644 --- a/src/som/compiler/MethodBuilder.java +++ b/src/som/compiler/MethodBuilder.java @@ -41,8 +41,10 @@ import som.compiler.MixinBuilder.MixinDefinitionId; import som.compiler.ProgramDefinitionError.SemanticDefinitionError; import som.compiler.Variable.Argument; +import som.compiler.Variable.ImmutableLocal; import som.compiler.Variable.Internal; import som.compiler.Variable.Local; +import som.compiler.Variable.MutableLocal; import som.interpreter.InliningVisitor; import som.interpreter.LexicalScope.MethodScope; import som.interpreter.LexicalScope.MixinScope; @@ -337,21 +339,26 @@ public void addArgument(final String arg, final SourceSection source) { arguments.put(arg, argument); } - public Local addLocal(final String name, final SourceSection source) - throws MethodDefinitionError { + public Local addLocal(final String name, final boolean immutable, + final SourceSection source) throws MethodDefinitionError { if (arguments.containsKey(name)) { throw new MethodDefinitionError("Method already defines argument " + name + ". Can't define local variable with same name.", source); } - Local l = new Local(name, source); + Local l; + if (immutable) { + l = new ImmutableLocal(name, source); + } else { + l = new MutableLocal(name, source); + } l.init(currentScope.getFrameDescriptor().addFrameSlot(l)); locals.put(name, l); return l; } - public Local addLocalAndUpdateScope(final String name, + public Local addLocalAndUpdateScope(final String name, final boolean immutable, final SourceSection source) throws MethodDefinitionError { - Local l = addLocal(name, source); + Local l = addLocal(name, immutable, source); currentScope.addVariable(l); return l; } diff --git a/src/som/compiler/NumeralParser.java b/src/som/compiler/NumeralParser.java index 48d922296..0dce74692 100644 --- a/src/som/compiler/NumeralParser.java +++ b/src/som/compiler/NumeralParser.java @@ -5,6 +5,8 @@ import java.math.BigInteger; +import som.VM; + public class NumeralParser { @@ -88,7 +90,13 @@ private long calculateNumberWithRadixLong() { v = c - 'A' + 10 /* A has value 10 */; } - result = Math.addExact(result, (long) (Math.pow(r, length - i - 1) * v)); + try { + result = Math.addExact(result, (long) (Math.pow(r, length - i - 1) * v)); + } catch (ArithmeticException e) { + // TODO: need to overflow into BigInteger + VM.errorPrintln("Warning: Parsed Integer literal which did not fit into long. " + lexer.getCurrentLineNumber() + ":" + lexer.getCurrentColumn()); + return result; + } } return result; } diff --git a/src/som/compiler/Parser.java b/src/som/compiler/Parser.java index 0fff21124..86da2104d 100644 --- a/src/som/compiler/Parser.java +++ b/src/som/compiler/Parser.java @@ -124,12 +124,12 @@ public class Parser { - private final Lexer lexer; + protected final Lexer lexer; private final Source source; private final SomLanguage language; - private Symbol sym; + protected Symbol sym; private String text; private Symbol nextSym; private String nextText; @@ -288,7 +288,16 @@ public SourceCoordinate getCoordinate() { return lexer.getStartCoordinate(); } + private void compatibilityNewspeakVersionAndFileCategory() throws ParseError { + if (sym == Identifier && "Newspeak3".equals(text)) { + expect(Identifier, KeywordTag.class); + expect(STString, LiteralTag.class); + } + } + public MixinBuilder moduleDeclaration() throws ProgramDefinitionError { + compatibilityNewspeakVersionAndFileCategory(); + comments(); return classDeclaration(null, AccessModifier.PUBLIC); } @@ -579,6 +588,8 @@ private void slotDefinition(final MixinBuilder mxnBuilder) SourceCoordinate coord = getCoordinate(); AccessModifier acccessModifier = accessModifier(); + comments(); + String slotName = slotDecl(); boolean immutable; ExpressionNode init; @@ -609,7 +620,10 @@ private AccessModifier accessModifier() { } private String slotDecl() throws ParseError { - return identifier(); + String id = identifier(); + + new TypeParser(this).parseType(); + return id; } private void initExprs(final MixinBuilder mxnBuilder) throws ProgramDefinitionError { @@ -726,7 +740,7 @@ private void expect(final Symbol s, final String msg, throw new ParseError(msg, s, this); } - private void expect(final Symbol s, final Class tag) throws ParseError { + protected void expect(final Symbol s, final Class tag) throws ParseError { expect(s, "Unexpected symbol. Expected %(expected)s, but found %(found)s", tag); } @@ -784,6 +798,8 @@ private void messagePattern(final MethodBuilder builder) throws ParseError { binaryPattern(builder); break; } + + new TypeParser(this).parseReturnType(); } protected void unaryPattern(final MethodBuilder builder) throws ParseError { @@ -873,6 +889,9 @@ protected String setterKeyword() throws ParseError { private String argument() throws ParseError { SourceCoordinate coord = getCoordinate(); String id = identifier(); + + new TypeParser(this).parseType(); + language.getVM().reportSyntaxElement(ArgumentTag.class, getSource(coord)); return id; } @@ -880,27 +899,64 @@ private String argument() throws ParseError { private ExpressionNode blockContents(final MethodBuilder builder) throws ProgramDefinitionError { comments(); + List expressions = new ArrayList(); + if (accept(Or, DelimiterOpeningTag.class)) { - locals(builder); + locals(builder, expressions); expect(Or, DelimiterClosingTag.class); } builder.setVarsOnMethodScope(); - return blockBody(builder); + return blockBody(builder, expressions); } - private void locals(final MethodBuilder builder) throws ParseError, MethodDefinitionError { - while (sym == Identifier) { - SourceCoordinate coord = getCoordinate(); - String id = identifier(); - SourceSection source = getSource(coord); - builder.addLocal(id, source); - language.getVM().reportSyntaxElement(LocalVariableTag.class, source); + private void locals(final MethodBuilder builder, + final List expressions) throws ProgramDefinitionError { + // Newspeak-speak: we do not support simSlotDecls, i.e., + // simultaneous slots clauses (spec 6.3.2) + while (sym != Or) { + localDefinition(builder, expressions); } } - private ExpressionNode blockBody(final MethodBuilder builder) throws ProgramDefinitionError { + private void localDefinition(final MethodBuilder builder, + final List expressions) throws ProgramDefinitionError { + comments(); + if (sym == Or) { return; } + + SourceCoordinate coord = getCoordinate(); + String slotName = slotDecl(); + SourceSection source = getSource(coord); + + language.getVM().reportSyntaxElement(LocalVariableTag.class, source); + + boolean immutable; + ExpressionNode initializer; + + if (accept(Equal, KeywordTag.class)) { + immutable = true; + initializer = expression(builder); + expect(Period, StatementSeparatorTag.class); + } else if (accept(SlotMutableAssign, KeywordTag.class)) { + immutable = false; + initializer = expression(builder); + expect(Period, StatementSeparatorTag.class); + } else { + immutable = false; + initializer = null; + } + + Local local = builder.addLocal(slotName, immutable, source); + + if (initializer != null) { + SourceSection write = getSource(coord); + ExpressionNode writeNode = local.getWriteNode(0, initializer, write); + expressions.add(writeNode); + } + } + + private ExpressionNode blockBody(final MethodBuilder builder, + final List expressions) throws ProgramDefinitionError { SourceCoordinate coord = getCoordinate(); - List expressions = new ArrayList(); boolean sawPeriod = true; @@ -1006,12 +1062,17 @@ private ExpressionNode primary(final MethodBuilder builder) throws ProgramDefini // Parse true, false, and nil as keyword-like constructs // (cf. Newspeak spec on reserved words) if (acceptIdentifier("true", LiteralTag.class)) { + comments(); return new TrueLiteralNode(getSource(coord)); } + if (acceptIdentifier("false", LiteralTag.class)) { + comments(); return new FalseLiteralNode(getSource(coord)); } + if (acceptIdentifier("nil", LiteralTag.class)) { + comments(); return new NilLiteralNode(getSource(coord)); } if ("outer".equals(text)) { @@ -1019,6 +1080,9 @@ private ExpressionNode primary(final MethodBuilder builder) throws ProgramDefini } SSymbol selector = unarySelector(); + + comments(); + return builder.getImplicitReceiverSend(selector, getSource(coord)); } case NewTerm: { @@ -1059,8 +1123,12 @@ private ExpressionNode outerSend(final MethodBuilder builder) expectIdentifier("outer", KeywordTag.class); String outer = identifier(); + comments(); + ExpressionNode operand = builder.getOuterRead(outer, getSource(coord)); operand = binaryConsecutiveMessages(builder, operand, false, null); + + comments(); return operand; } @@ -1117,6 +1185,8 @@ protected ExpressionNode unaryMessage(final ExpressionNode receiver, final boolean eventualSend, final SourceSection sendOperator) throws ParseError { SourceCoordinate coord = getCoordinate(); SSymbol selector = unarySelector(); + + comments(); return createMessageSend(selector, new ExpressionNode[] {receiver}, eventualSend, getSource(coord), sendOperator, language); } @@ -1187,7 +1257,10 @@ protected ExpressionNode keywordMessage(final MethodBuilder builder, do { kw.append(keyword()); + comments(); + arguments.add(formula(builder)); + comments(); } while (sym == Keyword); @@ -1314,7 +1387,7 @@ private Local getLoopIdx(final MethodBuilder builder, // if it is a literal, we still need a memory location for counting, so, // add a synthetic local loopIdx = builder.addLocalAndUpdateScope( - "!i" + SourceCoordinate.getLocationQualifier(source), source); + "!i" + SourceCoordinate.getLocationQualifier(source), false, source); } return loopIdx; } diff --git a/src/som/compiler/TypeParser.java b/src/som/compiler/TypeParser.java new file mode 100644 index 000000000..19c2bed3c --- /dev/null +++ b/src/som/compiler/TypeParser.java @@ -0,0 +1,154 @@ +package som.compiler; + +import static som.compiler.Symbol.Colon; +import static som.compiler.Symbol.Comma; +import static som.compiler.Symbol.Div; +import static som.compiler.Symbol.EndBlock; +import static som.compiler.Symbol.EndTerm; +import static som.compiler.Symbol.Exit; +import static som.compiler.Symbol.Identifier; +import static som.compiler.Symbol.LCurly; +import static som.compiler.Symbol.Less; +import static som.compiler.Symbol.More; +import static som.compiler.Symbol.NewBlock; +import static som.compiler.Symbol.NewTerm; +import static som.compiler.Symbol.Or; +import static som.compiler.Symbol.RCurly; + +import som.compiler.Parser.ParseError; + + +public class TypeParser { + + private final Parser parser; + + public TypeParser(final Parser parser) { + this.parser = parser; + } + + public void parseType() throws ParseError { + if (parser.sym == Less) { + expectType(); + } + } + + private void nonEmptyBlockArgList() throws ParseError { + while (parser.sym == Colon) { + parser.expect(Colon, null); + typeTerm(); + } + + if (parser.sym == Or) { + blockReturnType(); + } + } + + private void blockReturnType() throws ParseError { + typeExpr(); + } + + private void blockType() throws ParseError { + parser.expect(NewBlock, null); + + if (parser.sym == Colon) { + nonEmptyBlockArgList(); + } else if (parser.sym != EndBlock) { + blockReturnType(); + } + + parser.expect(EndBlock, null); + } + + private void typeArguments() throws ParseError { + parser.expect(NewBlock, null); + + while (true) { + typeExpr(); + + if (parser.sym == Comma) { + parser.expect(Comma, null); + } else { + break; + } + } + + parser.expect(EndBlock, null); + } + + private void typePrimary() throws ParseError { + parser.expect(Identifier, null); + if (parser.sym == NewBlock) { + typeArguments(); + } + } + + private void typeFactor() throws ParseError { + if (parser.sym == Identifier) { + typePrimary(); + } else if (parser.sym == LCurly) { + tupleType(); + } else if (parser.sym == NewBlock) { + blockType(); + } else if (parser.sym == NewTerm) { + parser.expect(NewTerm, null); + typeExpr(); + parser.expect(EndTerm, null); + } + } + + private void typeTerm() throws ParseError { + typeFactor(); + + while (parser.sym == Identifier) { + parser.expect(Identifier, null); + } + } + + private void tupleType() throws ParseError { + parser.expect(LCurly, null); + + if (parser.sym != RCurly) { + while (true) { + typeExpr(); + + if (parser.sym == Comma) { + parser.expect(Comma, null); + } else { + break; + } + } + } + + parser.expect(RCurly, null); + } + + private void typeExpr() throws ParseError { + typeTerm(); + + if (parser.sym == Or) { + parser.expect(Or, null); + typeExpr(); + } else if (parser.sym == LCurly) { + tupleType(); + } else if (parser.sym == Colon) { + parser.expect(Colon, null); + typeExpr(); + } else if (parser.sym == Div) { + parser.expect(Div, null); + typeExpr(); + } + } + + private void expectType() throws ParseError { + parser.expect(Less, null); + typeExpr(); + parser.expect(More, null); + } + + public void parseReturnType() throws ParseError { + if (parser.sym == Exit) { + parser.expect(Exit, null); + expectType(); + } + } +} diff --git a/src/som/compiler/Variable.java b/src/som/compiler/Variable.java index c3248c3af..bfad5d78c 100644 --- a/src/som/compiler/Variable.java +++ b/src/som/compiler/Variable.java @@ -121,7 +121,7 @@ public Local splitToMergeIntoOuterScope(final FrameDescriptor descriptor) { return null; } - Local l = new Local(name, source); + Local l = new ImmutableLocal(name, source); l.init(descriptor.addFrameSlot(l)); return l; } @@ -148,7 +148,7 @@ public Object read(final Frame frame) { * Represents a local variable, i.e., local to a specific scope. * Locals are stored in {@link FrameSlot}s inside a {@link Frame}. */ - public static final class Local extends Variable { + public abstract static class Local extends Variable { @CompilationFinal private FrameSlot slot; Local(final String name, final SourceSection source) { @@ -174,9 +174,11 @@ public FrameSlot getSlot() { return slot; } + protected abstract Local create(); + @Override public Local split(final FrameDescriptor descriptor) { - Local newLocal = new Local(name, source); + Local newLocal = create(); newLocal.init(descriptor.addFrameSlot(newLocal)); return newLocal; } @@ -204,6 +206,28 @@ public Object read(final Frame frame) { } } + public static final class MutableLocal extends Local { + MutableLocal(final String name, final SourceSection source) { + super(name, source); + } + + @Override + public Local create() { + return new MutableLocal(name, source); + } + } + + public static final class ImmutableLocal extends Local { + ImmutableLocal(final String name, final SourceSection source) { + super(name, source); + } + + @Override + public Local create() { + return new ImmutableLocal(name, source); + } + } + /** * Represents a variable that is used for internal purposes only. * Does not hold language-level values, and thus, is ignored in the debugger. diff --git a/tests/java/som/compiler/TypeGrammarParserTest.java b/tests/java/som/compiler/TypeGrammarParserTest.java new file mode 100644 index 000000000..30eb232d4 --- /dev/null +++ b/tests/java/som/compiler/TypeGrammarParserTest.java @@ -0,0 +1,67 @@ +package som.compiler; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import com.oracle.truffle.api.source.MissingMIMETypeException; +import com.oracle.truffle.api.source.MissingNameException; +import com.oracle.truffle.api.source.Source; + +import som.compiler.Parser.ParseError; +import som.interpreter.SomLanguage; +import tools.language.StructuralProbe; + + +@RunWith(Parameterized.class) +public class TypeGrammarParserTest { + + private final String content; + + public TypeGrammarParserTest(final String content) { + this.content = content; + } + + @Parameters(name = "{0}") + public static String[] data() { + return new String[] { + "", "", "", "<[:FooBar]>", + "<[:String :ObjectMirror]>", "", + "", + "<[:V | V2 def]>", "<[:Promise | R def] | [R def]>", + "<{String, Foobar}>"}; + } + + @Test + public void testNormalType() throws RuntimeException, + MissingMIMETypeException, MissingNameException, ParseError { + // add a space so that lexer stops lexing + String testString = content + " "; + TypeParser tp = createParser(testString); + tp.parseType(); + } + + @Test + public void testReturnType() throws RuntimeException, + MissingMIMETypeException, MissingNameException, ParseError { + // add a space so that lexer stops lexing + String testString = "^ " + content + " "; + TypeParser tp = createParser(testString); + tp.parseReturnType(); + } + + private TypeParser createParser(final String testString) + throws MissingMIMETypeException, MissingNameException, ParseError { + Source s = Source.newBuilder(testString). + name("test.som"). + mimeType(SomLanguage.MIME_TYPE).build(); + Parser p = new Parser( + testString, testString.length(), s, new StructuralProbe(), + new SomLanguage()); + + TypeParser tp = new TypeParser(p); + return tp; + } + +}