| @@ -0,0 +1,91 @@ | ||
| /* | ||
| * To change this license header, choose License Headers in Project Properties. | ||
| * To change this template file, choose Tools | Templates | ||
| * and open the template in the editor. | ||
| */ | ||
| grammar Smalltalk; | ||
| script : sequence ws EOF; | ||
| sequence : temps? ws statements? ws; | ||
| ws : (SEPARATOR | COMMENT)*; | ||
| temps : ws PIPE (ws IDENTIFIER)+ ws PIPE; | ||
| statements : answer ws # StatementAnswer | ||
| | expressions ws PERIOD ws answer # StatementExpressionsAnswer | ||
| | expressions PERIOD? ws # StatementExpressions | ||
| ; | ||
| answer : CARROT ws expression ws PERIOD?; | ||
| expression : assignment | cascade | keywordSend | binarySend | primitive; | ||
| expressions : expression expressionList*; | ||
| expressionList : PERIOD ws expression; | ||
| cascade : (keywordSend | binarySend) (ws SEMI_COLON ws message)+; | ||
| message : binaryMessage | unaryMessage | keywordMessage; | ||
| assignment : variable ws ASSIGNMENT ws expression; | ||
| variable : IDENTIFIER; | ||
| binarySend : unarySend binaryTail?; | ||
| unarySend : operand ws unaryTail?; | ||
| keywordSend : binarySend keywordMessage; | ||
| keywordMessage : ws (keywordPair ws)+; | ||
| keywordPair : KEYWORD ws binarySend ws; | ||
| operand : literal | reference | subexpression; | ||
| subexpression : OPEN_PAREN ws expression ws CLOSE_PAREN; | ||
| literal : runtimeLiteral | parsetimeLiteral; | ||
| runtimeLiteral : dynamicDictionary | dynamicArray | block; | ||
| block : BLOCK_START blockParamList? ws (PIPE ws)? sequence BLOCK_END; | ||
| blockParamList : (ws BLOCK_PARAM)+; | ||
| dynamicDictionary : DYNDICT_START ws expressions? ws DYNARR_END; | ||
| dynamicArray : DYNARR_START ws expressions? ws DYNARR_END; | ||
| parsetimeLiteral : charConstant | pseudoVariable | number | literalArray | string | symbol; | ||
| number : numberExp | hex | stFloat | stInteger; | ||
| numberExp : (stFloat | stInteger) EXP stInteger; | ||
| charConstant : CHARACTER_CONSTANT; | ||
| hex : MINUS? HEX HEXDIGIT+; | ||
| stInteger : MINUS? DIGIT+; | ||
| stFloat : MINUS? DIGIT+ PERIOD DIGIT+; | ||
| pseudoVariable : RESERVED_WORD; | ||
| string : STRING; | ||
| symbol : HASH bareSymbol; | ||
| primitive : LT ws KEYWORD ws DIGIT+ ws GT; | ||
| bareSymbol : (IDENTIFIER | BINARY_SELECTOR) | KEYWORD+ | string | PIPE+; | ||
| literalArray : LITARR_START literalArrayRest; | ||
| literalArrayRest : (ws (parsetimeLiteral | bareLiteralArray | bareSymbol))* ws CLOSE_PAREN; | ||
| bareLiteralArray : OPEN_PAREN literalArrayRest; | ||
| unaryTail : unaryMessage ws unaryTail? ws; | ||
| unaryMessage : ws unarySelector; | ||
| unarySelector : IDENTIFIER; | ||
| keywords : KEYWORD+; | ||
| reference : variable; | ||
| binaryTail : binaryMessage binaryTail?; | ||
| binaryMessage : ws BINARY_SELECTOR ws (unarySend | operand); | ||
| SEPARATOR : [ \t\r\n]; | ||
| STRING : '\'' (.)*? '\''; | ||
| COMMENT : '"' (.)*? '"'; | ||
| BLOCK_START : '['; | ||
| BLOCK_END : ']'; | ||
| CLOSE_PAREN : ')'; | ||
| OPEN_PAREN : '('; | ||
| PIPE : '|'; | ||
| PERIOD : '.'; | ||
| SEMI_COLON : ';'; | ||
| BINARY_SELECTOR : ('\\' | '+' | '*' | '/' | '=' | '>' | '<' | ',' | '@' | '%' | '~' | PIPE | '&' | '-' | '?')+; | ||
| LT : '<'; | ||
| GT : '>'; | ||
| MINUS : '-'; | ||
| RESERVED_WORD : 'nil' | 'true' | 'false' | 'self' | 'super'; | ||
| IDENTIFIER : [a-zA-Z]+[a-zA-Z0-9_]*; | ||
| CARROT : '^'; | ||
| COLON : ':'; | ||
| ASSIGNMENT : ':='; | ||
| HASH : '#'; | ||
| DOLLAR : '$'; | ||
| EXP : 'e'; | ||
| HEX : '16r'; | ||
| LITARR_START : '#('; | ||
| DYNDICT_START : '#{'; | ||
| DYNARR_END : '}'; | ||
| DYNARR_START : '{'; | ||
| DIGIT : [0-9]; | ||
| HEXDIGIT : [0-9a-fA-F]; | ||
| KEYWORD : IDENTIFIER COLON; | ||
| BLOCK_PARAM : COLON IDENTIFIER; | ||
| CHARACTER_CONSTANT : DOLLAR .; |
| @@ -0,0 +1,5 @@ | ||
| java -jar ../../../lib/antlr-4.1-complete.jar -o ../java/st/redline/compiler -no-listener -visitor -package st.redline.compiler Smalltalk.g4 | ||
| # byte[] generatedClassBytes(); SmalltalkVisitor | ||
| # public byte[] generatedClassBytes() { return null; } SmalltalkBaseVisitor | ||
| @@ -0,0 +1,3 @@ | ||
| ExampleClass class methodAt: #sayHello put: [ | ||
| Transcript show: 'Hello World.'; cr. | ||
| ]. |
| @@ -0,0 +1,3 @@ | ||
| ExampleClass methodAt: #sayHello put: [ | ||
| Transcript show: 'Hello World.'; cr. | ||
| ]. |
| @@ -0,0 +1,3 @@ | ||
| ExampleClass methodAt: #sayHello put: [ | foo bar | | ||
| Transcript show: 'Hello World.'; cr. | ||
| ]. |
| @@ -0,0 +1,3 @@ | ||
| ExampleClass methodAt: #sayHello put: [ :arg1 :arg2 | tmp1 tmp2 | | ||
| Transcript show: 'Hello World.'; cr. | ||
| ]. |
| @@ -0,0 +1 @@ | ||
| ^ []. |
| @@ -0,0 +1 @@ | ||
| ^ [ ]. |
| @@ -0,0 +1 @@ | ||
| a := 123 |
| @@ -0,0 +1 @@ | ||
| [ :foo :bar | ^ self. ] |
| @@ -0,0 +1 @@ | ||
| [ :foo :bar | baz | ] |
| @@ -0,0 +1 @@ | ||
| [ :foo :bar | baz | ^ self ] |
| @@ -0,0 +1 @@ | ||
| [ | baz | ] |
| @@ -0,0 +1,4 @@ | ||
| Transcript | ||
| open; | ||
| show: 'message'; | ||
| cr |
| @@ -0,0 +1,7 @@ | ||
| Transcript | ||
| open; | ||
| show: 10; | ||
| show: #Symbol; | ||
| show: #(123 $X 'hello' Sunday); | ||
| show: $E; | ||
| cr |
| @@ -0,0 +1 @@ | ||
| a == nil ifTrue: []. |
| @@ -0,0 +1,6 @@ | ||
| (a == nil) | ||
| ifTrue: [ | ||
| ^ true | ||
| ] ifFalse: [ | ||
| ^ false | ||
| ] |
| @@ -0,0 +1 @@ | ||
| b < (a - c) ifTrue: [ ^ true ] |
| @@ -0,0 +1 @@ | ||
| [] |
| @@ -0,0 +1 @@ | ||
| [ ] |
| @@ -0,0 +1 @@ | ||
| #( 1234 $E 'hello' Symbol $$ $; $4 $. ) |
| @@ -0,0 +1 @@ | ||
| #() |
| @@ -0,0 +1 @@ | ||
| | foo bar | |
| @@ -1,55 +1,75 @@ | ||
| /* Redline Smalltalk, Copyright (c) James C. Ladd. All rights reserved. See LICENSE in the root of this distribution. */ | ||
| package st.redline; | ||
| import st.redline.classloader.*; | ||
| import java.io.*; | ||
| public class Stic { | ||
| private SticConfiguration configuration; | ||
| private final String[] args; | ||
| public static void main(String[] args) throws Exception { | ||
| new Stic(args).run(); | ||
| } | ||
| public Stic(String[] args) { | ||
| this.args = args; | ||
| } | ||
| private void run() throws ClassNotFoundException, InstantiationException, IllegalAccessException { | ||
| run(loadScript(scriptName())); | ||
| } | ||
| private void run(Class cls) throws IllegalAccessException, InstantiationException { | ||
| cls.newInstance(); | ||
| } | ||
| public static void main(String[] args) { | ||
| SticConfiguration configuration = configuration(args); | ||
| try { | ||
| new Stic(configuration).run(); | ||
| } catch (Exception e) { | ||
| print(e.getCause() != null ? e.getCause() : e); | ||
| } | ||
| private Class loadScript(String name) throws ClassNotFoundException { | ||
| return classLoader().loadScript(name); | ||
| } | ||
| private static void print(Throwable throwable) { | ||
| throwable.printStackTrace(); | ||
| private SmalltalkClassLoader classLoader() { | ||
| return new SmalltalkClassLoader(currentClassLoader(), sourceFinder(), bootstrapper()); | ||
| } | ||
| private static SticConfiguration configuration(String[] args) { | ||
| return new SticConfiguration(args); | ||
| private Bootstrapper bootstrapper() { | ||
| return new Bootstrapper(); | ||
| } | ||
| public Stic(SticConfiguration configuration) { | ||
| this.configuration = configuration; | ||
| private SourceFinder sourceFinder() { | ||
| return new SmalltalkSourceFinder(sourceFactory(), classPaths()); | ||
| } | ||
| public void run() throws Exception { | ||
| runProgram(scriptName()); | ||
| private SourceFactory sourceFactory() { | ||
| return new SourceFactory(); | ||
| } | ||
| private void runProgram(String program) throws Exception { | ||
| run(loadProgram(program)); | ||
| public String[] classPaths() { | ||
| return classPath().split(File.pathSeparator); | ||
| } | ||
| private void run(Class aClass) throws Exception { | ||
| if (aClass != null) | ||
| aClass.newInstance(); | ||
| private String classPath() { | ||
| return System.getProperty("java.class.path"); | ||
| } | ||
| public Class loadProgram(String program) throws Exception { | ||
| ClassLoader classLoader = classLoader(); | ||
| return Class.forName(program, true, classLoader); | ||
| private ClassLoader currentClassLoader() { | ||
| return Thread.currentThread().getContextClassLoader(); | ||
| } | ||
| private String scriptName() { | ||
| String scriptName = configuration.scriptName(); | ||
| return "".equals(scriptName) ? "st.redline.NoScript" : scriptName; | ||
| return hasArguments() ? firstArgument() : defaultScriptName(); | ||
| } | ||
| private String defaultScriptName() { | ||
| return "st.redline.script.NoArguments"; | ||
| } | ||
| private String firstArgument() { | ||
| return args[0]; | ||
| } | ||
| private ClassLoader classLoader() throws Exception { | ||
| return configuration.classLoader(); | ||
| private boolean hasArguments() { | ||
| return args.length > 0; | ||
| } | ||
| } |
| @@ -1,104 +1,143 @@ | ||
| /* Redline Smalltalk, Copyright (c) James C. Ladd. All rights reserved. See LICENSE in the root of this distribution. */ | ||
| package st.redline.classloader; | ||
| import st.redline.lang.*; | ||
| import st.redline.core.*; | ||
| import java.io.File; | ||
| import static st.redline.core.PrimSubclass.PRIM_SUBCLASS; | ||
| public class Bootstrapper { | ||
| private final SourceFinder sourceFinder; | ||
| public void bootstrap(SmalltalkClassLoader classLoader) { | ||
| setContextClassLoader(classLoader); | ||
| public Bootstrapper(SourceFinder sourceFinder) { | ||
| this.sourceFinder = sourceFinder; | ||
| classLoader.beginBootstrapping(); | ||
| createPrimObject(classLoader); | ||
| createKernelObjectsHierarchy(classLoader); | ||
| classLoader.importAll("st.redline.kernel"); | ||
| loadKernelObjects(classLoader); | ||
| classLoader.endBootstrapping(); | ||
| } | ||
| public void bootstrap(SmalltalkClassLoader smalltalkClassLoader) throws Exception { | ||
| bootstrapNil(smalltalkClassLoader); | ||
| bootstrapMetaclass(smalltalkClassLoader); | ||
| bootstrapProtoObject(smalltalkClassLoader); | ||
| bootstrapSymbol(smalltalkClassLoader); | ||
| bootstrapObject(smalltalkClassLoader); | ||
| bootstrapMetaclassHierarchy(smalltalkClassLoader); | ||
| smalltalkClassLoader.stoppedBootstrapping(); | ||
| bootstrapNilHierarchy(smalltalkClassLoader); | ||
| bootstrapBooleans(smalltalkClassLoader); | ||
| private void createKernelObjectsHierarchy(SmalltalkClassLoader classLoader) { | ||
| // Create Kernel Objects and Classes we need to start Runtime. | ||
| PrimObject primObject = classLoader.cachedObject("st.redline.kernel.PrimObject"); | ||
| PrimClass object = createKernelObject("Object", primObject); | ||
| PrimClass behavior = createKernelObject("Behavior", object); | ||
| PrimClass classDescription = createKernelObject("ClassDescription", behavior); | ||
| PrimClass klass = createKernelObject("Class", classDescription); | ||
| PrimClass metaclass = createKernelObject("Metaclass", classDescription); | ||
| PrimClass undefinedObject = createKernelObject("UndefinedObject", object, metaclass); | ||
| PrimClass blockClosure = createKernelObject("BlockClosure", object, metaclass); | ||
| PrimClass compiledMethod = createKernelObject("CompiledMethod", object, metaclass); | ||
| PrimClass booleanObject = createKernelObject("Boolean", object, metaclass); | ||
| PrimClass trueObject = createKernelObject("True", booleanObject, metaclass); | ||
| PrimClass falseObject = createKernelObject("False", booleanObject, metaclass); | ||
| PrimClass collection = createKernelObject("Collection", object, metaclass); | ||
| PrimClass sequenceableCollection = createKernelObject("SequenceableCollection", collection, metaclass); | ||
| PrimClass arrayedCollection = createKernelObject("ArrayedCollection", sequenceableCollection, metaclass); | ||
| PrimClass string = createKernelObject("String", arrayedCollection, metaclass); | ||
| PrimClass symbol = createKernelObject("Symbol", string, metaclass); | ||
| // Fix up bootstrapped Kernel Objects Metaclass instance. | ||
| klass.selfClass().selfClass(metaclass); | ||
| classDescription.selfClass().selfClass(metaclass); | ||
| behavior.selfClass().selfClass(metaclass); | ||
| object.selfClass().selfClass(metaclass); | ||
| // Initialise special Smalltalk circular hierarchy. | ||
| ((PrimClass) object.selfClass()).superclass(klass); | ||
| // Let subclass primitive know the Metaclass instance - used when subclassing. | ||
| ((PrimSubclass) PRIM_SUBCLASS).metaclass(metaclass); | ||
| // Add basicAddSelector:withMethod: to Behaviour | ||
| ((PrimClass) behavior).addMethod("basicAddSelector:withMethod:", new PrimAddMethod()); | ||
| // Create special instances, referred to with pseudo variables. | ||
| PrimObject nil = new PrimObject(); | ||
| nil.selfClass(undefinedObject); | ||
| classLoader.nilInstance(nil); | ||
| PrimObject trueInstance = new PrimObject(); | ||
| trueInstance.selfClass(trueObject); | ||
| classLoader.trueInstance(trueInstance); | ||
| PrimObject falseInstance = new PrimObject(); | ||
| falseInstance.selfClass(falseObject); | ||
| classLoader.falseInstance(falseInstance); | ||
| // Load the hierarchy which will attached their methods. | ||
| classLoader.cacheObject("st.redline.kernel.Object", object); | ||
| classLoader.cacheObject("st.redline.kernel.Behavior", behavior); | ||
| classLoader.cacheObject("st.redline.kernel.ClassDescription", classDescription); | ||
| classLoader.cacheObject("st.redline.kernel.Class", klass); | ||
| classLoader.cacheObject("st.redline.kernel.Metaclass", metaclass); | ||
| classLoader.cacheObject("st.redline.kernel.UndefinedObject", undefinedObject); | ||
| classLoader.cacheObject("st.redline.kernel.BlockClosure", blockClosure); | ||
| classLoader.cacheObject("st.redline.kernel.CompiledMethod", compiledMethod); | ||
| classLoader.cacheObject("st.redline.kernel.Boolean", booleanObject); | ||
| classLoader.cacheObject("st.redline.kernel.True", trueObject); | ||
| classLoader.cacheObject("st.redline.kernel.False", falseObject); | ||
| classLoader.cacheObject("st.redline.kernel.Collection", collection); | ||
| classLoader.cacheObject("st.redline.kernel.SequenceableCollection", sequenceableCollection); | ||
| classLoader.cacheObject("st.redline.kernel.ArrayedCollection", arrayedCollection); | ||
| classLoader.cacheObject("st.redline.kernel.String", string); | ||
| classLoader.cacheObject("st.redline.kernel.Symbol", symbol); | ||
| } | ||
| private void bootstrapBooleans(SmalltalkClassLoader smalltalkClassLoader) throws Exception { | ||
| ProtoObject Metaclass = smalltalkClassLoader.METACLASS; | ||
| ProtoObject TrueClass = Metaclass.resolveObject("st.redline.kernel.True"); | ||
| smalltalkClassLoader.TRUE = TrueClass.perform("new"); | ||
| ProtoObject FalseClass = Metaclass.resolveObject("st.redline.kernel.False"); | ||
| smalltalkClassLoader.FALSE = FalseClass.perform("new"); | ||
| private PrimClass createKernelObject(String name, PrimObject superclass) { | ||
| PrimClass primMeta = new PrimClass(name,true); | ||
| primMeta.superclass(superclass.selfClass()); | ||
| PrimClass primClass = new PrimClass(name); | ||
| primClass.superclass(superclass); | ||
| primClass.selfClass(primMeta); | ||
| return primClass; | ||
| } | ||
| private void bootstrapNilHierarchy(SmalltalkClassLoader smalltalkClassLoader) throws Exception { | ||
| ProtoObject Metaclass = smalltalkClassLoader.METACLASS; | ||
| smalltalkClassLoader.NIL.selfclass = Metaclass.resolveObject("st.redline.kernel.UndefinedObject"); | ||
| private PrimClass createKernelObject(String name, PrimObject superclass, PrimObject metaclass) { | ||
| PrimClass primClass = createKernelObject(name, superclass); | ||
| primClass.selfClass().selfClass(metaclass); | ||
| return primClass; | ||
| } | ||
| private void bootstrapNil(SmalltalkClassLoader smalltalkClassLoader) { | ||
| smalltalkClassLoader.NIL = new ProtoObject(); | ||
| private void setContextClassLoader(SmalltalkClassLoader classLoader) { | ||
| Thread.currentThread().setContextClassLoader(classLoader); | ||
| } | ||
| private void bootstrapMetaclassHierarchy(SmalltalkClassLoader smalltalkClassLoader) throws Exception { | ||
| ProtoObject Metaclass = smalltalkClassLoader.METACLASS; | ||
| ProtoObject classDescription = Metaclass.resolveObject("st.redline.kernel.ClassDescription"); | ||
| ((ProtoClass) Metaclass).superclass(classDescription); | ||
| private void loadKernelObjects(SmalltalkClassLoader classLoader) { | ||
| loadObject(classLoader, "st.redline.kernel.Object"); | ||
| loadObject(classLoader, "st.redline.kernel.Behavior"); | ||
| loadObject(classLoader, "st.redline.kernel.ClassDescription"); | ||
| loadObject(classLoader, "st.redline.kernel.Class"); | ||
| loadObject(classLoader, "st.redline.kernel.Metaclass"); | ||
| loadObject(classLoader, "st.redline.kernel.UndefinedObject"); | ||
| loadObject(classLoader, "st.redline.kernel.BlockClosure"); | ||
| loadObject(classLoader, "st.redline.kernel.CompiledMethod"); | ||
| loadObject(classLoader, "st.redline.kernel.Boolean"); | ||
| loadObject(classLoader, "st.redline.kernel.True"); | ||
| loadObject(classLoader, "st.redline.kernel.False"); | ||
| loadObject(classLoader, "st.redline.kernel.Collection"); | ||
| loadObject(classLoader, "st.redline.kernel.SequenceableCollection"); | ||
| loadObject(classLoader, "st.redline.kernel.ArrayedCollection"); | ||
| loadObject(classLoader, "st.redline.kernel.String"); | ||
| loadObject(classLoader, "st.redline.kernel.Symbol"); | ||
| } | ||
| private void bootstrapObject(SmalltalkClassLoader smalltalkClassLoader) throws Exception { | ||
| ProtoObject Metaclass = smalltalkClassLoader.METACLASS; | ||
| ProtoObject Object = Metaclass.resolveObject("st.redline.kernel.Object"); | ||
| ProtoObject Class = Metaclass.resolveObject("st.redline.kernel.Class"); | ||
| ((ProtoClass) Object.selfclass).superclass(Class); | ||
| private void createPrimObject(SmalltalkClassLoader classLoader) { | ||
| PrimObject primObject = new PrimObject(); | ||
| primObject.selfClass(primObject); | ||
| classLoader.cacheObject("st.redline.kernel.PrimObject", primObject); | ||
| } | ||
| private void bootstrapSymbol(SmalltalkClassLoader smalltalkClassLoader) throws ClassNotFoundException { | ||
| smalltalkClassLoader.METACLASS.resolveObject("st.redline.kernel.Symbol"); | ||
| } | ||
| private void bootstrapMetaclass(SmalltalkClassLoader smalltalkClassLoader) { | ||
| ProtoClass Metaclass = new ProtoClass(); | ||
| ProtoClass metaclass = Metaclass.create("Metaclass"); | ||
| Metaclass.selfclass = metaclass; | ||
| smalltalkClassLoader.METACLASS = metaclass; | ||
| metaclass.addMethod("atSelector:put:", new PrimAtSelectorPutMethod()); | ||
| } | ||
| private ProtoObject bootstrapProtoObject(SmalltalkClassLoader smalltalkClassLoader) { | ||
| ProtoClass ProtoObject = makeProtoObject((ProtoClass) smalltalkClassLoader.METACLASS); | ||
| smalltalkClassLoader.registerProtoObject(ProtoObject); | ||
| addKernelImports(ProtoObject); | ||
| return ProtoObject; | ||
| } | ||
| private void addKernelImports(ProtoClass protoObject) { | ||
| for (Source source : sourceFinder.findIn("st.redline.kernel")) | ||
| addKernelImport(protoObject, source); | ||
| // TODO.JCL - Do we really have to add all roots? | ||
| for (Source source : sourceFinder.findIn("st.redline.test")) | ||
| addKernelImport(protoObject, source); | ||
| } | ||
| private void addKernelImport(ProtoClass protoObject, Source source) { | ||
| protoObject.importAtPut(source.className(), source.fullyQualifiedName()); | ||
| } | ||
| private ProtoClass makeProtoObject(ProtoClass metaclass) { | ||
| // Create ProtoObject class (metaclass) | ||
| ProtoClass protoObjectClass = new ProtoClass(); | ||
| protoObjectClass.selfclass = metaclass; | ||
| // Add methods needed during bootstrap. | ||
| protoObjectClass.addMethod("subclass:", new PrimSubclass()); | ||
| protoObjectClass.addMethod("import:", new PrimImport()); | ||
| protoObjectClass.addMethod("class", new PrimClass()); | ||
| protoObjectClass.addMethod("atSelector:put:", new PrimAtSelectorPutMethod()); | ||
| // Create ProtoObject sole instance. ProtoObject is a class. | ||
| ProtoClass protoObject = protoObjectClass.create("ProtoObject"); | ||
| return protoObject; | ||
| private void loadObject(ClassLoader classLoader, String name) { | ||
| try { | ||
| // Loading and instantiating the class causes the 'sendMessages' java method | ||
| // to be called which executes all the message sends of the Smalltalk source. | ||
| classLoader.loadClass(name).newInstance(); | ||
| } catch (Exception e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
| } | ||
| } |
| @@ -0,0 +1,8 @@ | ||
| /* Redline Smalltalk, Copyright (c) James C. Ladd. All rights reserved. See LICENSE in the root of this distribution. */ | ||
| package st.redline.classloader; | ||
| public interface LineTransformer { | ||
| String transform(String line); | ||
| String begin(); | ||
| String end(); | ||
| } |
| @@ -0,0 +1,9 @@ | ||
| /* Redline Smalltalk, Copyright (c) James C. Ladd. All rights reserved. See LICENSE in the root of this distribution. */ | ||
| package st.redline.classloader; | ||
| public class ObjectNotFoundException extends RuntimeException { | ||
| public ObjectNotFoundException(String message) { | ||
| super(message); | ||
| } | ||
| } |
| @@ -0,0 +1,208 @@ | ||
| /* Redline Smalltalk, Copyright (c) James C. Ladd. All rights reserved. See LICENSE in the root of this distribution. */ | ||
| package st.redline.classloader; | ||
| import java.io.File; | ||
| import java.util.regex.Matcher; | ||
| import java.util.regex.Pattern; | ||
| /* Note: Move the preprocessing responsibility into an inner class. */ | ||
| public class SmalltalkSourceFile implements Source, LineTransformer { | ||
| public static final String SEPARATOR = "/"; | ||
| public static final String SOURCE_EXTENSION = ".st"; | ||
| private static final Pattern METHOD_START_PATTERN = Pattern.compile("^[-+] .*\\s"); | ||
| private static final Pattern METHOD_UNARY_PATTERN = Pattern.compile("^\\w+[^:|\\s]"); | ||
| private static final Pattern METHOD_BINARY_PATTERN = Pattern.compile("^[-\\\\+*/=><,@%~|&?]+ \\w+"); | ||
| private static final Pattern METHOD_KEYWORD_PATTERN = Pattern.compile("(\\w+: \\w+)+"); | ||
| private static final String METHOD_START = "["; | ||
| private static final String METHOD_END = "]."; | ||
| private static final String CLASS_SELECTOR = "class"; | ||
| private static final String METHOD_AT_SELECTOR = "basicAddSelector:"; | ||
| private static final String METHOD_PUT_SELECTOR = "withMethod:"; | ||
| private static final String CLASS_METHOD_INDICATOR = "+ "; | ||
| private static final String NEWLINE = System.getProperty("line.separator"); | ||
| private final String name; | ||
| private final String filename; | ||
| private final File file; | ||
| private final String classpath; | ||
| private final SourceReader reader; | ||
| private boolean methods; | ||
| protected String className; | ||
| protected String selector; | ||
| protected String arguments; | ||
| protected String fullClassName; | ||
| protected String packageName; | ||
| public SmalltalkSourceFile(String name, String filename, File file, String classpath, SourceReader reader) { | ||
| this.name = name; | ||
| this.filename = filename; | ||
| this.file = file; | ||
| this.classpath = classpath; | ||
| this.reader = reader; | ||
| this.methods = false; | ||
| } | ||
| public boolean exists() { | ||
| return true; | ||
| } | ||
| public boolean hasContent() { | ||
| return file.length() > 0; | ||
| } | ||
| public String contents() { | ||
| return reader.contents(this); | ||
| } | ||
| public String transform(String line) { | ||
| if (isMethodDefinition(line)) | ||
| return methodDefinitionTransformation(line); | ||
| return line; | ||
| } | ||
| public String fileExtension() { | ||
| return SOURCE_EXTENSION; | ||
| } | ||
| private String methodDefinitionTransformation(String line) { | ||
| StringBuffer transformation = new StringBuffer(); | ||
| if (hasMethods()) | ||
| transformation.append(METHOD_END + " "); | ||
| transformation.append(className()); | ||
| if (isClassMethod(line)) | ||
| transformation.append(" " + CLASS_SELECTOR); | ||
| transformation.append(" " + METHOD_AT_SELECTOR); | ||
| extractSelectorAndArgumentsFrom(line); | ||
| transformation.append(" #" + selector()); | ||
| transformation.append(" " + METHOD_PUT_SELECTOR); | ||
| transformation.append(" " + METHOD_START); | ||
| if (arguments.length() > 0) | ||
| transformation.append(arguments + " |"); | ||
| transformation.append(NEWLINE); | ||
| methods = true; | ||
| return transformation.toString(); | ||
| } | ||
| private String selector() { | ||
| return selector; | ||
| } | ||
| private void extractSelectorAndArgumentsFrom(String line) { | ||
| String input = line; | ||
| if (input.startsWith("+ ") || input.startsWith("- ")) | ||
| input = input.substring(2); | ||
| arguments = ""; | ||
| if (!isKeywordSelector(input) && !isUnarySelector(input) && !isBinarySelector(input)) | ||
| selector = "UnknownSelector"; | ||
| } | ||
| private boolean isUnarySelector(String input) { | ||
| Matcher matcher = METHOD_UNARY_PATTERN.matcher(input); | ||
| if (matcher.find()) { | ||
| selector = matcher.group(); | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
| private boolean isBinarySelector(String input) { | ||
| Matcher matcher = METHOD_BINARY_PATTERN.matcher(input); | ||
| if (matcher.find()) { | ||
| selector = matcher.group(); | ||
| int space = selector.indexOf(' '); | ||
| arguments = " :" + selector.substring(space + 1); | ||
| selector = selector.substring(0, space); | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
| private boolean isKeywordSelector(String input) { | ||
| Matcher matcher = METHOD_KEYWORD_PATTERN.matcher(input); | ||
| selector = ""; | ||
| arguments = ""; | ||
| boolean found = false; | ||
| while (matcher.find()) { | ||
| String keyword = matcher.group(); | ||
| int space = keyword.indexOf(' '); | ||
| arguments = arguments + " :" + keyword.substring(space + 1); | ||
| selector = selector + keyword.substring(0, space); | ||
| found = true; | ||
| } | ||
| return found; | ||
| } | ||
| private boolean isClassMethod(String line) { | ||
| return line.startsWith(CLASS_METHOD_INDICATOR); | ||
| } | ||
| public String classpath() { | ||
| return classpath; | ||
| } | ||
| public String name() { | ||
| return name; | ||
| } | ||
| public String className() { | ||
| return name(); | ||
| } | ||
| public String fullClassName() { | ||
| if (fullClassName == null) | ||
| fullClassName = withoutClassPath(withoutExtension(filename())); | ||
| return fullClassName; | ||
| } | ||
| public String packageName() { | ||
| if (packageName == null) { | ||
| packageName = fullClassName(); | ||
| int index = packageName.lastIndexOf(File.separatorChar); | ||
| if (index != -1) | ||
| packageName = packageName.substring(0, index); | ||
| packageName = packageName.replaceAll(String.valueOf(File.separatorChar), "."); | ||
| } | ||
| return packageName; | ||
| } | ||
| private String withoutClassPath(String filename) { | ||
| if (classpath.length() > 0 && filename.startsWith(classpath)) | ||
| return filename.substring(classpath.length() + 1); | ||
| return filename; | ||
| } | ||
| protected String withoutExtension(String filename) { | ||
| return filename.substring(0, filename.lastIndexOf(".")); | ||
| } | ||
| private boolean isMethodDefinition(String line) { | ||
| return METHOD_START_PATTERN.matcher(line).matches(); | ||
| } | ||
| public String filename() { | ||
| return filename; | ||
| } | ||
| public String fullFilename() { | ||
| return file.getPath(); | ||
| } | ||
| public String begin() { | ||
| return ""; | ||
| } | ||
| public String end() { | ||
| return hasMethods() ? METHOD_END + NEWLINE : ""; | ||
| } | ||
| private String classInitializer() { | ||
| return className() + " initialize." + NEWLINE; | ||
| } | ||
| private boolean hasMethods() { | ||
| return methods; | ||
| } | ||
| } |
| @@ -0,0 +1,146 @@ | ||
| /* Redline Smalltalk, Copyright (c) James C. Ladd. All rights reserved. See LICENSE in the root of this distribution. */ | ||
| package st.redline.classloader; | ||
| import java.io.*; | ||
| import java.util.*; | ||
| import java.util.jar.*; | ||
| import static st.redline.classloader.SmalltalkSourceFile.*; | ||
| public class SmalltalkSourceFinder implements SourceFinder { | ||
| private final SourceFactory sourceFactory; | ||
| private final String[] classPaths; | ||
| public SmalltalkSourceFinder(SourceFactory sourceFactory, String[] classPaths) { | ||
| this.sourceFactory = sourceFactory; | ||
| this.classPaths = classPaths; | ||
| } | ||
| public Source find(String name) { | ||
| //System.out.println(">>> find: " + name); | ||
| String filename = toFilename(name); | ||
| File file = new File(filename); | ||
| if (file.exists()) | ||
| return sourceFile(filename, file, ""); | ||
| return new SourceNotFound(name); | ||
| } | ||
| public List<Source> findIn(String packageName) { | ||
| //System.out.println("** findIn: " + packageName); | ||
| return findInPath(packageName); | ||
| } | ||
| private List<Source> findInPath(String path) { | ||
| String packagePath = path.replace(".", SEPARATOR); | ||
| List<Source> sources = new ArrayList<>(); | ||
| for (String classPath : classPaths) | ||
| sources.addAll(findInPath(packagePath, classPath)); | ||
| return sources; | ||
| } | ||
| public List<Source> findInPath(String packagePath, String classPath) { | ||
| if (isJar(classPath)) { | ||
| return findSourceInInJar(packagePath, classPath); | ||
| } else | ||
| return findSourceInFile(packagePath, classPath); | ||
| } | ||
| @SuppressWarnings("unchecked") | ||
| private List<Source> findSourceInFile(String packagePath, String classPath) { | ||
| File folder = new File(classPath + SEPARATOR + packagePath); | ||
| if (!folder.isDirectory()) | ||
| return Collections.EMPTY_LIST; | ||
| List<Source> sources = new ArrayList<>(); | ||
| File[] files = folder.listFiles(); | ||
| if (files != null) | ||
| for (File file : files) | ||
| if (file.isFile() && file.getName().endsWith(SOURCE_EXTENSION)) | ||
| sources.add(sourceFile(packagePath + SEPARATOR + file.getName(), file, classPath)); | ||
| return sources; | ||
| } | ||
| private List<Source> findSourceInInJar(String packagePath, String classPath) { | ||
| List<Source> sources = new ArrayList<>(); | ||
| JarFile jarFile = tryCreateJarFile(classPath); | ||
| for (Enumeration em1 = jarFile.entries(); em1.hasMoreElements();) { | ||
| String entry = em1.nextElement().toString(); | ||
| int lastSlash = entry.lastIndexOf('/'); | ||
| int pathLength = packagePath.length(); | ||
| if (entry.startsWith(packagePath) && pathLength == lastSlash && entry.endsWith(".st")) | ||
| sources.add(sourceFactory.createFromJar(entry, classPath)); | ||
| } | ||
| return sources; | ||
| } | ||
| private JarFile tryCreateJarFile(String classPath) { | ||
| try { | ||
| return createJarFile(classPath); | ||
| } catch (IOException e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
| } | ||
| public JarFile createJarFile(String classPath) throws IOException { | ||
| return new JarFile(classPath); | ||
| } | ||
| private boolean isJar(String classPath) { | ||
| return classPath.endsWith(".jar") || classPath.endsWith(".JAR"); | ||
| } | ||
| private Source sourceFile(String filename, File file, String classpath) { | ||
| return sourceFactory.createFromFile(filename, file, classpath); | ||
| } | ||
| private String toFilename(String name) { | ||
| return name.replaceAll("\\.", File.separator) + SOURCE_EXTENSION; | ||
| } | ||
| public class SourceNotFound implements Source { | ||
| private final String name; | ||
| public SourceNotFound(String name) { | ||
| this.name = name; | ||
| } | ||
| public boolean exists() { | ||
| return false; | ||
| } | ||
| public boolean hasContent() { | ||
| return false; | ||
| } | ||
| public String contents() { | ||
| return ""; | ||
| } | ||
| public String className() { | ||
| int index = name.lastIndexOf("."); | ||
| if (index == -1) | ||
| return name; | ||
| return name.substring(index + 1); | ||
| } | ||
| public String fullClassName() { | ||
| return name; | ||
| } | ||
| public String fileExtension() { | ||
| return ""; | ||
| } | ||
| public String packageName() { | ||
| int index = name.lastIndexOf("."); | ||
| if (index == -1) | ||
| return ""; | ||
| return name.substring(0, index); | ||
| } | ||
| public String classpath() { | ||
| return ""; | ||
| } | ||
| } | ||
| } |
| @@ -1,72 +1,14 @@ | ||
| /* Redline Smalltalk, Copyright (c) James C. Ladd. All rights reserved. See LICENSE in the root of this distribution. */ | ||
| package st.redline.classloader; | ||
| import st.redline.RedlineFile; | ||
| import st.redline.classloader.io.SourceReader; | ||
| import java.io.File; | ||
| public class Source { | ||
| private final String name; | ||
| private final String path; | ||
| private final SourceReader reader; | ||
| private final String className; | ||
| private final String packageName; | ||
| public Source(String name, String path, SourceReader reader) { | ||
| this.name = name; | ||
| this.path = path; | ||
| this.reader = reader; | ||
| this.className = makeClassName(); | ||
| this.packageName = makePackageName(); | ||
| } | ||
| private String makePackageName() { | ||
| int index = name.lastIndexOf(RedlineFile.separator); | ||
| if (index == -1) | ||
| return ""; | ||
| return name.substring(0, index); | ||
| } | ||
| private String makeClassName() { | ||
| int index = name.lastIndexOf(RedlineFile.separator); | ||
| if (index == -1) | ||
| index = 0; | ||
| return name.substring(index + 1, name.lastIndexOf(".st")); | ||
| } | ||
| public String name() { | ||
| return name; | ||
| } | ||
| public String path() { | ||
| return path; | ||
| } | ||
| public String className() { | ||
| return className; | ||
| } | ||
| public String packageName() { | ||
| return packageName; | ||
| } | ||
| public String fullyQualifiedName() { | ||
| return packageName.replace(RedlineFile.separator, ".") + "." + className; | ||
| } | ||
| public SourceReader reader() { | ||
| return reader; | ||
| } | ||
| public String contents() { | ||
| return reader().contents(); | ||
| } | ||
| public boolean isNextTo(String otherFile) { | ||
| String filename = path + RedlineFile.separator + name.replace(className + ".st", otherFile + ".st"); | ||
| File file = new File(filename); | ||
| return file.exists(); | ||
| } | ||
| public interface Source { | ||
| boolean hasContent(); | ||
| boolean exists(); | ||
| String contents(); | ||
| String className(); | ||
| String fullClassName(); | ||
| String fileExtension(); | ||
| String packageName(); | ||
| String classpath(); | ||
| } |
| @@ -1,128 +1,10 @@ | ||
| /* Redline Smalltalk, Copyright (c) James C. Ladd. All rights reserved. See LICENSE in the root of this distribution. */ | ||
| package st.redline.classloader; | ||
| import st.redline.RedlineFile; | ||
| import java.util.*; | ||
| import java.io.File; | ||
| import java.io.IOException; | ||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.Enumeration; | ||
| import java.util.List; | ||
| import java.util.jar.JarFile; | ||
| import java.util.zip.ZipEntry; | ||
| public interface SourceFinder { | ||
| public class SourceFinder { | ||
| public static final String WINDOWS_PATH_SEPARATOR = "\\"; | ||
| public static final String JAR_PATH_SEPARATOR = "/"; | ||
| private static final String SOURCE_EXTENSION = ".st"; | ||
| private SourceFactory sourceFactory; | ||
| private final String[] classPaths; | ||
| public SourceFinder(SourceFactory sourceFactory, String[] classPaths) { | ||
| this.sourceFactory = sourceFactory; | ||
| this.classPaths = classPaths; | ||
| } | ||
| public List<Source> findIn(String path) { | ||
| String packagePath = path.replace(".", RedlineFile.separator); | ||
| List<Source> sources = new ArrayList<Source>(); | ||
| for (String classPath : classPaths) | ||
| sources.addAll(findIn(packagePath, classPath)); | ||
| return sources; | ||
| } | ||
| public List<Source> findIn(String packagePath, String classPath) { | ||
| if (isJar(classPath)) | ||
| return findSourceInInJar(packagePath, classPath); | ||
| else | ||
| return findSourceInFile(packagePath, classPath); | ||
| } | ||
| private List<Source> findSourceInInJar(String packagePath, String classPath) { | ||
| List<Source> sources = new ArrayList<Source>(); | ||
| JarFile jarFile = tryCreateJarFile(classPath); | ||
| for (Enumeration em1 = jarFile.entries(); em1.hasMoreElements();) { | ||
| String entry = em1.nextElement().toString(); | ||
| int lastSlash = entry.lastIndexOf('/'); | ||
| int pathLength = packagePath.length(); | ||
| if (entry.startsWith(packagePath) && pathLength == lastSlash && entry.endsWith(".st")) | ||
| sources.add(sourceFactory.createFromJar(entry, classPath)); | ||
| } | ||
| return sources; | ||
| } | ||
| private List<Source> findSourceInFile(String packagePath, String classPath) { | ||
| File folder = new File(classPath + RedlineFile.separator + packagePath); | ||
| if (!folder.isDirectory()) | ||
| return Collections.EMPTY_LIST; | ||
| List<Source> sources = new ArrayList<Source>(); | ||
| for (File file : folder.listFiles()) | ||
| if (file.isFile() && file.getName().endsWith(".st")) | ||
| sources.add(sourceFactory.createFromFile(packagePath + RedlineFile.separator + file.getName(), file.getAbsolutePath())); | ||
| return sources; | ||
| } | ||
| public Source find(String name) { | ||
| String sourceName = makeFilename(name); | ||
| return findSource(sourceName); | ||
| } | ||
| public Source findSource(String sourceName) { | ||
| Source source; | ||
| for (String classPath : classPaths) | ||
| if ((source = findSource(sourceName, classPath)) != null) | ||
| return source; | ||
| return null; | ||
| } | ||
| public Source findSource(String sourceName, String classPath) { | ||
| if (isJar(classPath)) | ||
| return findSourceInJar(sourceName, classPath); | ||
| else | ||
| return findSourceFile(sourceName, classPath); | ||
| } | ||
| private Source findSourceInJar(String sourceName, String classPath) { | ||
| JarFile jarFile = tryCreateJarFile(classPath); | ||
| String sourceNameForJar = sourceName.replace(WINDOWS_PATH_SEPARATOR, JAR_PATH_SEPARATOR); | ||
| ZipEntry entry = jarFile.getEntry(sourceNameForJar); | ||
| if (entry != null) | ||
| return sourceFactory.createFromJar(sourceNameForJar, classPath); | ||
| return null; | ||
| } | ||
| private JarFile tryCreateJarFile(String classPath) { | ||
| try { | ||
| return createJarFile(classPath); | ||
| } catch (IOException e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
| } | ||
| private boolean isJar(String classPath) { | ||
| return classPath.endsWith(".jar") || classPath.endsWith(".JAR"); | ||
| } | ||
| private Source findSourceFile(String sourceName, String classPath) { | ||
| File file = createFile(sourceName, classPath); | ||
| if (file.exists()) | ||
| return sourceFactory.createFromFile(sourceName, classPath); | ||
| return null; | ||
| } | ||
| public File createFile(String sourceName, String classPath) { | ||
| return new File(classPath + RedlineFile.separator + sourceName); | ||
| } | ||
| public JarFile createJarFile(String classPath) throws IOException { | ||
| return new JarFile(classPath); | ||
| } | ||
| private String makeFilename(String name) { | ||
| String filename = name.replace(".", RedlineFile.separator); | ||
| return filename + SOURCE_EXTENSION; | ||
| } | ||
| Source find(String name); | ||
| List<Source> findIn(String packageName); | ||
| } |
| @@ -1,6 +1,7 @@ | ||
| /* Redline Smalltalk, Copyright (c) James C. Ladd. All rights reserved. See LICENSE in the root of this distribution. */ | ||
| package st.redline.classloader.io; | ||
| package st.redline.classloader; | ||
| public interface SourceReader { | ||
| String contents(); | ||
| String contents(LineTransformer lineTransformer); | ||
| } |